Architecture

MapLibre ArcGIS is built as a modular TypeScript library that integrates with MapLibre GL JS through the IControl interface. The architecture follows the same patterns as maplibre-gl-components.

graph TB
    subgraph Core["Core Layer"]
        AUTH[ArcGISAuthManager]
        CONFIG[MapConfigLoader]
    end
    
    subgraph Controls["MapLibre Controls"]
        LOGIN[ArcGISLoginControl]
        FS[EsriFeatureServiceControl]
        DMS[EsriDynamicMapServiceControl]
        WEBMAP[WebMapControl]
        CATALOG[CatalogLayerControl]
        COG[CogS3Control]
        COGA[CogAnalyticsControl]
        LIDAR[CopcLidarControl]
        DUCKDB[DuckDBLayerControl]
        IMAGERY[OrientedImageryControl]
    end
    
    subgraph Loaders["Data Loaders"]
        WMLOADER[WebMapLoader]
        S3RESOLVER[S3UrlResolver]
        DUCKLOADER[DuckDBGISLoader]
        GEOPROC[DuckDBGeoprocessor]
    end
    
    subgraph External["External Services"]
        ARCGIS[ArcGIS Platform]
        S3[AWS S3]
    end
    
    Core --> Controls
    Controls --> Loaders
    Loaders --> External
    
    AUTH --> ARCGIS
    S3RESOLVER --> S3
          

Design Principles

  • Modularity - Each feature is a separate module that can be imported independently
  • Tree-shaking - ESM exports enable bundlers to exclude unused code
  • Token Injection - All controls accept an auth parameter for automatic token handling
  • Consistent API - All controls follow the MapLibre IControl interface
  • React Optional - React wrappers are provided but not required

Project Structure

Directory Structure
src/
├── auth/                    # Authentication
│   ├── ArcGISAuthManager.ts # Singleton: OAuth2, API Key, Token, IWA
│   ├── ArcGISLoginControl.ts
│   └── index.ts
├── layers/                  # ESRI Service Controls
│   ├── EsriFeatureServiceControl.ts
│   ├── EsriMapServiceControls.ts
│   └── index.ts
├── webmap/                  # WebMap + WebScene Loading
│   ├── WebMapLoader.ts
│   ├── WebMapControl.ts
│   ├── LayerTypeDispatcher.ts
│   └── index.ts
├── catalog/                 # Catalog Layer
│   ├── CatalogLayerControl.ts
│   └── index.ts
├── cloud/                   # Cloud-native data
│   ├── CogS3Control.ts
│   ├── CopcLidarControl.ts
│   ├── CogAnalyticsControl.ts
│   ├── S3UrlResolver.ts
│   ├── analytics/
│   │   ├── colormaps.ts
│   │   └── pyodideWorkerSource.ts
│   └── index.ts
├── duckdb/                  # DuckDB WASM
│   ├── DuckDBGISLoader.ts
│   ├── DuckDBLayerControl.ts
│   ├── DuckDBGeoprocessor.ts
│   ├── DuckDBGeoprocessingControl.ts
│   ├── DuckDBAttributeTableControl.ts
│   └── index.ts
├── imagery/                 # Oriented Imagery
│   ├── OrientedImageryControl.ts
│   ├── ImageViewer.ts
│   ├── SpatialTransforms.ts
│   └── index.ts
├── config/                  # No-Code Config
│   ├── MapConfigSchema.ts
│   ├── MapConfigLoader.ts
│   └── index.ts
├── react/                   # React Wrappers
│   ├── components.tsx
│   └── index.ts
├── styles/
│   └── maplibre-arcgis.css
└── index.ts                 # Master barrel export

cli/
└── maplibre_arcgis_cli/
    ├── main.py              # Typer CLI entry point
    ├── schema.py            # Pydantic v2 models
    ├── builder.py           # HTML renderer (Jinja2)
    ├── converter.py         # WebMap → YAML
    ├── server.py            # Dev HTTP server
    └── templates/
        └── index.html.j2

Dependencies

Core Dependencies

Package Purpose
maplibre-gl Map rendering engine (peer dependency)
maplibre-gl-components Base controls (COG, STAC, terrain, etc.)
@esri/arcgis-rest-request ArcGIS REST auth + token management
mapbox-gl-arcgis-featureserver Tiled FeatureServer/MapServer queries
mapbox-gl-esri-sources Dynamic/Tiled MapService, ImageService sources
maplibre-gl-lidar COPC LiDAR via deck.gl
geotiff COG parsing
@duckdb/duckdb-wasm In-browser SQL engine with spatial extension
@aws-sdk/client-s3 S3 pre-signed URL generation

Building

Development

Terminal
# Install dependencies
npm install

# Start development server
npm run dev

# Type checking
npm run typecheck

# Linting
npm run lint

Production Build

Terminal
# Build library (ESM + CJS)
npm run build

# Run tests
npm run test

API Reference

ArcGISAuthManager

Singleton class managing all ArcGIS authentication modes.

TypeScript
interface ArcGISAuthManagerOptions {
  portalUrl: string;
  authMethod: 'oauth2' | 'apikey' | 'token' | 'app';
  clientId?: string;
  clientSecret?: string;
  apiKey?: string;
  username?: string;
  password?: string;
}

class ArcGISAuthManager {
  constructor(options: ArcGISAuthManagerOptions);
  
  // Get the current access token
  getToken(): Promise<string>;
  
  // Check if authenticated
  isAuthenticated(): boolean;
  
  // Get current user info
  getUser(): Promise<UserInfo>;
  
  // Logout
  logout(): void;
  
  // Events
  on(event: 'login' | 'logout' | 'tokenrefresh' | 'error', callback: Function): void;
}

EsriFeatureServiceControl

TypeScript
interface EsriFeatureServiceControlOptions {
  id: string;
  url: string;
  auth?: ArcGISAuthManager | string;
  where?: string;
  outFields?: string[];
  simplifyFactor?: number;
  minzoom?: number;
  maxzoom?: number;
  opacity?: number;
  visible?: boolean;
}

class EsriFeatureServiceControl implements IControl {
  constructor(options: EsriFeatureServiceControlOptions);
  onAdd(map: Map): HTMLElement;
  onRemove(): void;
  setVisibility(visible: boolean): void;
  setOpacity(opacity: number): void;
}

WebMapLoader

TypeScript
interface WebMapLoadResult {
  basemap: any;
  layers: Layer[];
  extent: LngLatBoundsLike;
}

class WebMapLoader {
  constructor(auth: ArcGISAuthManager);
  
  // Load a WebMap by item ID
  load(itemId: string): Promise<WebMapLoadResult>;
  
  // Load and apply to map
  loadAndApply(map: Map, itemId: string): Promise<void>;
}

DuckDBGISLoader

TypeScript
class DuckDBGISLoader {
  static create(): Promise<DuckDBGISLoader>;
  
  // Load a file (File object or URL)
  loadFile(file: File): Promise<FeatureCollection>;
  loadUrl(url: string): Promise<FeatureCollection>;
  
  // Configure S3 access
  configureS3(region: string, keyId: string, secret: string): Promise<void>;
  
  // Execute SQL query
  query(sql: string): Promise<any[]>;
  
  // Get table metadata
  describeTable(name: string): Promise<ColumnInfo[]>;
  listTables(): string[];
  
  // Export to GeoJSON
  viewToGeoJSON(tableName: string): Promise<FeatureCollection>;
}

React Integration

React wrappers are provided as thin useEffect-based components.

TypeScript
import {
  ArcGISLoginReact,
  EsriFeatureServiceReact,
  WebMapReact,
  DuckDBLayerReact,
} from 'maplibre-arcgis/react';

function MapComponent({ map, auth }) {
  return (
    <>
      <ArcGISLoginReact map={map} auth={auth} position="top-right" />
      <EsriFeatureServiceReact
        map={map}
        id="parcels"
        url="https://.../FeatureServer/0"
        auth={auth}
      />
      <DuckDBLayerReact map={map} position="top-left" />
    </>
  );
}

Creating Custom Controls

Follow the MapLibre IControl interface pattern.

TypeScript
import { IControl, Map } from 'maplibre-gl';
import './styles/control.css';

interface MyControlOptions {
  title?: string;
  onAction: () => void;
}

export class MyControl implements IControl {
  private map: Map;
  private container: HTMLElement;
  private options: MyControlOptions;

  constructor(options: MyControlOptions) {
    this.options = options;
  }

  onAdd(map: Map): HTMLElement {
    this.map = map;
    this.container = document.createElement('div');
    this.container.className = 'maplibregl-ctrl my-control';
    
    // Build your UI here
    const button = document.createElement('button');
    button.textContent = this.options.title || 'Action';
    button.addEventListener('click', () => this.options.onAction());
    this.container.appendChild(button);
    
    return this.container;
  }

  onRemove(): void {
    this.container.remove();
    this.map = undefined;
  }

  getDefaultPosition(): string {
    return 'top-right';
  }
}

CSS Naming Convention

Use the .ma- prefix for all CSS classes to avoid conflicts:

control.css
.my-control {
  background: var(--ma-bg, #fff);
  border: 1px solid var(--ma-border, #ccc);
  border-radius: 4px;
}

Testing

Tests are written using Vitest.

Terminal
# Run all tests
npm run test

# Run tests in watch mode
npm run test -- --watch

# Run specific test file
npm run test -- auth.test.ts

Example Test

auth.test.ts
import { describe, it, expect, vi } from 'vitest';
import { ArcGISAuthManager } from '../src/auth';

describe('ArcGISAuthManager', () => {
  it('should initialize with API key', () => {
    const auth = new ArcGISAuthManager({
      portalUrl: 'https://www.arcgis.com',
      authMethod: 'apikey',
      apiKey: 'test-key',
    });
    
    expect(auth.isAuthenticated()).toBe(true);
  });
});

Contributing

Development Workflow

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make your changes
  4. Run tests and linting: npm run typecheck && npm run lint && npm run test
  5. Commit your changes with descriptive messages
  6. Push to your fork and create a pull request

Code Style

  • Use TypeScript for all source files
  • Follow existing naming conventions
  • Add JSDoc comments for public APIs
  • Keep functions focused and testable
  • Use const over let when possible

Pull Request Guidelines

  • Reference any related issues
  • Include tests for new functionality
  • Update documentation as needed
  • Ensure CI passes before requesting review
🔗 Related Resources

MapLibre GL JS Docs: maplibre.org/maplibre-gl-js/docs/
ArcGIS REST API: developers.arcgis.com/rest/
DuckDB WASM: duckdb.org/docs/api/wasm