Standard Toolkit
Toolkits@accelint/map-toolkitDeckGL

BaseMap

Deck.gl-powered base map with MapLibre GL integration, event bus coordination, and mode management.

A React component that provides a Deck.gl-powered base map with MapLibre GL integration.

Serves as the foundation for building interactive map applications with support for click and hover events through a centralized event bus. Integrates Deck.gl for 3D visualizations with MapLibre GL for base map tiles.

Usage

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { uuid } from '@accelint/core';

// Create id at module level for stability and easy sharing
const MAIN_MAP_ID = uuid();

export function MapView() {
  return <BaseMap className="w-full h-full" id={MAIN_MAP_ID} />;
}

Reference

interface BaseMapProps extends DeckglProps {
  // Required
  id: UniqueId;
  
  // Layout
  className?: string;
  
  // Map configuration
  defaultView?: '2D' | '2.5D' | '3D';
  styleUrl?: string;
  initialViewState?: Partial<ViewState>;
  mapLibreOptions?: MapLibreOptions;
  
  // Interaction
  enableControlEvents?: boolean;
  boxZoom?: boolean;
  enableRbz?: boolean;
  rbzOptions?: RbzOptions;
  
  // Event handlers
  onClick?: (info: PickingInfo, event: MjolnirGestureEvent) => void;
  onHover?: (info: PickingInfo, event: MjolnirPointerEvent) => void;
  onViewStateChange?: (params: ViewStateChangeParameters) => void;
  
  // All Deck.gl props (controller, parameters, layers, etc.)
}

Props

PropTypeDefaultRequired
idUniqueId-Yes
classNamestring-No
childrenReact.ReactNode-No
defaultView'2D' | '2.5D' | '3D''2D'No
styleUrlstringDark Matter styleNo
initialViewStatePartial<ViewState>{ zoom: 2, lat: 0, lon: 0 }No
enableControlEventsbooleantrueNo
boxZoombooleantrue (unless RBZ enabled)No
enableRbzbooleanfalseNo
rbzOptionsRbzOptions-No
onClick(info, event) => void-No
onHover(info, event) => void-No
onViewStateChange(params) => void-No
mapLibreOptionsMapLibreOptions-No

id

Required. Unique identifier for this map instance. Used to isolate map mode state between multiple map instances (e.g., main map vs minimap).

Must be a UUID generated using uuid() from @accelint/core. Should be a module-level constant for stability and easy sharing with useMapMode().

defaultView

Initial view mode for the map:

  • '2D' - Flat map with no pitch (default)
  • '2.5D' - Slight pitch for depth perception
  • '3D' - Full 3D navigation with pitch and rotation

styleUrl

MapLibre style URL for base map tiles. Defaults to Carto's Dark Matter style.

Common options:

  • 'https://tiles.basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json' (default)
  • 'https://tiles.basemaps.cartocdn.com/gl/positron-gl-style/style.json' (light)
  • Custom Mapbox or MapLibre style URLs

enableRbz & rbzOptions

Enable rubber band zoom (Shift + drag to select area for zoom). Automatically disables boxZoom when enabled.

Important: These options are read once on map load. To change dynamically, update the map's key prop to force a remount.

type RbzOptions = {
  fillColor?: string;
  strokeColor?: string;
  strokeWidth?: number;
  shouldActivate?: () => boolean;
};

mapLibreOptions

Additional MapLibre MapOptions forwarded to the underlying map (e.g., transformRequest, maxBounds, minZoom, maxZoom).

Keys BaseMap manages internally are stripped:

  • Camera/view state: center, zoom, pitch, bearing, projection, bounds, fitBoundsOptions
  • Controller gestures: doubleClickZoom, dragRotate, pitchWithRotate, rollEnabled, boxZoom
  • WebGL context: canvasContextAttributes
  • Already exposed props: style, container, maxPitch

Map Mode Integration

BaseMap automatically creates a MapProvider internally, setting up map mode state management for this instance.

Children vs Siblings:

  • Children: Only Deck.gl layer components can be children. They use useMapMode() without parameters (context-based).
  • Siblings: UI components (buttons, toolbars) must be siblings and pass id to useMapMode(id).
// ✅ Correct: Deck.gl layer as child
<BaseMap id={mapId}>
  <ScatterplotLayer {...props} />
</BaseMap>

// ✅ Correct: UI as sibling
<>
  <Toolbar mapId={mapId} />
  <BaseMap id={mapId} />
</>

// ❌ Wrong: UI as child
<BaseMap id={mapId}>
  <Toolbar /> {/* Won't render - not a Deck.gl layer */}
</BaseMap>

Event Bus

Click, hover, and viewport events are emitted through the event bus with id included in the payload, allowing multiple map instances to coexist without interference.

// Events
MapEvents.click    // MapClickEvent
MapEvents.hover    // MapHoverEvent
MapEvents.viewport // MapViewportEvent

// Control events
MapEvents.enablePan
MapEvents.disablePan
MapEvents.enableZoom
MapEvents.disableZoom

Inherited Props

BaseMap inherits all props from Deck.gl's DeckglProps, including:

  • controller - Enable/disable map controls
  • interleaved - Interleaved rendering with MapLibre
  • parameters - WebGL parameters
  • widgets - Deck.gl widgets
  • useDevicePixels - High-DPI rendering
  • pickingRadius - Picking tolerance in pixels

See Deck.gl Documentation for full API.

Examples

Example: Basic map with id

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { uuid } from '@accelint/core';

// Module-level constant for stability
const MAIN_MAP_ID = uuid();

export function SimpleMap() {
  return (
    <div className="w-full h-screen">
      <BaseMap id={MAIN_MAP_ID} className="w-full h-full" />
    </div>
  );
}

Example: With event handlers and mode management

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { useMapMode } from '@accelint/map-toolkit/map-mode';
import { uuid } from '@accelint/core';
import type { PickingInfo } from '@deck.gl/core';
import type { MjolnirGestureEvent } from 'mjolnir.js';

const MAIN_MAP_ID = uuid();

function Toolbar() {
  const { mode, requestModeChange } = useMapMode(MAIN_MAP_ID);
  
  return (
    <div className="absolute top-4 left-4 z-10">
      <button onClick={() => requestModeChange('default', 'toolbar')}>
        Navigate
      </button>
      <button onClick={() => requestModeChange('drawing', 'toolbar')}>
        Draw
      </button>
      <div>Current mode: {mode}</div>
    </div>
  );
}

export function InteractiveMap() {
  const handleClick = (info: PickingInfo, event: MjolnirGestureEvent) => {
    if (info.object) {
      console.log('Clicked:', info.object);
    }
  };
  
  const handleHover = (info: PickingInfo) => {
    console.log('Hovering:', info.coordinate);
  };
  
  return (
    <div className="relative w-full h-screen">
      <Toolbar />
      <BaseMap
        id={MAIN_MAP_ID}
        className="w-full h-full"
        onClick={handleClick}
        onHover={handleHover}
      />
    </div>
  );
}

Example: With custom initial view

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { uuid } from '@accelint/core';

const MAP_ID = uuid();

export function CenteredMap() {
  return (
    <BaseMap
      id={MAP_ID}
      className="w-full h-full"
      defaultView="2.5D"
      initialViewState={{
        latitude: 37.7749,
        longitude: -122.4194,
        zoom: 12,
      }}
    />
  );
}

Example: With Deck.gl layers

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { ScatterplotLayer } from '@deck.gl/layers';
import { uuid } from '@accelint/core';

const MAP_ID = uuid();

const data = [
  { position: [-122.4, 37.8], name: 'San Francisco' },
  { position: [-74.0, 40.7], name: 'New York' },
];

export function MapWithLayers() {
  return (
    <BaseMap id={MAP_ID} className="w-full h-full">
      <ScatterplotLayer
        id="cities"
        data={data}
        getPosition={(d) => d.position}
        getRadius={10000}
        getFillColor={[255, 0, 0]}
      />
    </BaseMap>
  );
}

Example: Listening to viewport changes

import { useOn } from '@accelint/bus/react';
import { MapEvents } from '@accelint/map-toolkit/deckgl';
import type { MapViewportEvent } from '@accelint/map-toolkit/deckgl';
import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { uuid } from '@accelint/core';

const MAP_ID = uuid();

function ViewportLogger() {
  useOn<MapViewportEvent>(MapEvents.viewport, (event) => {
    if (event.payload.id === MAP_ID) {
      console.log('Viewport:', {
        zoom: event.payload.zoom,
        bounds: event.payload.bounds,
        center: [event.payload.latitude, event.payload.longitude],
      });
    }
  });
  
  return null;
}

export function TrackedMap() {
  return (
    <>
      <ViewportLogger />
      <BaseMap id={MAP_ID} className="w-full h-full" />
    </>
  );
}

Example: Multiple map instances

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { uuid } from '@accelint/core';

const MAIN_MAP_ID = uuid();
const MINI_MAP_ID = uuid();

export function DualMapView() {
  return (
    <div className="flex h-screen">
      <div className="flex-1">
        <BaseMap id={MAIN_MAP_ID} className="w-full h-full" />
      </div>
      <div className="w-64">
        <BaseMap
          id={MINI_MAP_ID}
          className="w-full h-full"
          initialViewState={{ zoom: 0 }}
        />
      </div>
    </div>
  );
}

Example: With rubber band zoom

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { uuid } from '@accelint/core';

const MAP_ID = uuid();

export function RbzMap() {
  return (
    <BaseMap
      id={MAP_ID}
      className="w-full h-full"
      enableRbz={true}
      rbzOptions={{
        fillColor: 'rgba(0, 120, 255, 0.1)',
        strokeColor: '#0078ff',
        strokeWidth: 2,
      }}
    />
  );
}

Good to know: When enableRbz is true, boxZoom is automatically disabled to prevent gesture conflicts.

Example: Custom MapLibre options

import { BaseMap } from '@accelint/map-toolkit/deckgl';
import { uuid } from '@accelint/core';

const MAP_ID = uuid();

export function RestrictedMap() {
  return (
    <BaseMap
      id={MAP_ID}
      className="w-full h-full"
      mapLibreOptions={{
        maxBounds: [[-130, 20], [-60, 55]], // Restrict to US
        minZoom: 4,
        maxZoom: 18,
        transformRequest: (url, resourceType) => {
          // Add auth headers for custom tile server
          if (url.startsWith('https://tiles.example.com/')) {
            return {
              url,
              headers: { Authorization: `Bearer ${token}` },
            };
          }
          return { url };
        },
      }}
    />
  );
}

On this page