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
authparameter for automatic token handling - Consistent API - All controls follow the MapLibre
IControlinterface - 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
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Run tests and linting:
npm run typecheck && npm run lint && npm run test - Commit your changes with descriptive messages
- 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
constoverletwhen 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