esri-gl 1.0.2 โ†’ 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,805 +1,217 @@
1
1
  # esri-gl
2
2
 
3
- A TypeScript library that bridges Esri ArcGIS REST services with MapLibre GL JS and Mapbox GL JS. It replicates Esri Leaflet's architecture patterns while being compatible with modern WebGL mapping libraries.
3
+ A TypeScript library that bridges Esri ArcGIS REST services with MapLibre GL JS and Mapbox GL JS.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/esri-gl.svg)](https://badge.fury.io/js/esri-gl)
6
+ [![CI](https://github.com/muimsd/esri-gl/actions/workflows/ci.yml/badge.svg)](https://github.com/muimsd/esri-gl/actions/workflows/ci.yml)
6
7
  [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
7
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+ [![Buy Me A Coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=&slug=muimsd&button_colour=00ff04&font_colour=000000&font_family=Inter&outline_colour=000000&coffee_colour=FFDD00)](https://www.buymeacoffee.com/muimsd)
8
10
 
9
- ## ๐Ÿ”— Links
11
+ **[Documentation](https://esri-gl.pages.dev/)** ยท **[Live Demos](https://esri-gl-demo.pages.dev/)** ยท **[npm](https://www.npmjs.com/package/esri-gl)**
10
12
 
11
- - **๐Ÿ“š [Documentation](https://esri-gl.netlify.app/)** - Complete API reference and guides
12
- - **๐ŸŽฎ [Live Demos](https://esri-gl-demo.netlify.app/)** - Interactive examples and code samples
13
+ ## Features
13
14
 
14
- **Note**: This library is compatible with both **MapLibre GL JS** and **Mapbox GL JS**.
15
+ ### Services
16
+ - **DynamicMapService** โ€” Server-rendered raster tiles with dynamic styling, filtering, labeling, and identify
17
+ - **TiledMapService** โ€” Pre-cached tiles for fast basemaps
18
+ - **ImageService** โ€” Analytical raster data with rendering rules and temporal support
19
+ - **FeatureService** โ€” Vector data with smart vector tile detection, GeoJSON fallback, editing, and attachments
20
+ - **VectorTileService** โ€” Client-rendered vector tiles
21
+ - **VectorBasemapStyle** โ€” Esri's professional basemap styles
15
22
 
16
- ## Features
23
+ ### Tasks
24
+ - **IdentifyFeatures / IdentifyImage** โ€” Query features and pixel values at map locations
25
+ - **Query** โ€” Spatial and attribute queries with automatic pagination
26
+ - **Find** โ€” Text-based feature search across layers
17
27
 
18
- ### Supported Services
19
-
20
- - **Dynamic Map Services** - Server-rendered raster tiles from ArcGIS Map Services
21
- - **Tiled Map Services** - Pre-cached tile services for optimal performance
22
- - **Image Services** - Analytical raster data with server-side processing
23
- - **Vector Tile Services** - Client-rendered vector tiles for fast styling
24
- - **Vector Basemap Styles** - Esri's vector basemap styles with custom styling
25
- - **Feature Services** - Vector data with smart vector tile detection and GeoJSON fallback
26
-
27
- ### Advanced Features
28
-
29
- - **Smart Vector Tile Detection** - Automatically detects vector tile endpoints with GeoJSON fallback
30
- - **Dynamic Layer Management** - Add/remove layers dynamically with proper cleanup
31
- - **Identify Tasks** - Query features and raster data at specific locations
32
- - **Bounding Box Filtering** - Optimize performance with viewport-based data loading
33
- - **Layer Definitions** - Filter data server-side using SQL-like expressions
34
- - **Time-aware Services** - Support for temporal data visualization
35
- - **Attribution Management** - Automatic service attribution handling
36
- - **React Integration** - Hooks and components for React applications
37
- - **React Map GL Support** - Direct integration with react-map-gl
38
- - **TypeScript Support** - Full type safety with comprehensive interfaces
39
- - **ArcGIS Online (AGOL) Support** - Proper JSON error handling, token auth, and API key support
40
- - **Feature Editing** - Add, update, delete features and apply batch edits
41
- - **Attachments** - Query, add, and delete feature attachments
42
- - **Query Pagination** - Automatic pagination through large result sets
28
+ ### Integrations
29
+ - **React Hooks** โ€” `useDynamicMapService`, `useFeatureService`, `useQuery`, etc.
30
+ - **react-map-gl** โ€” Declarative `<EsriDynamicLayer>`, `<EsriFeatureLayer>`, etc.
31
+ - **TypeScript** โ€” Full type safety with comprehensive interfaces
43
32
 
44
33
  ## Installation
45
34
 
46
- ### Stable Release
47
35
  ```bash
48
36
  npm install esri-gl
49
37
  ```
50
38
 
51
39
  ### Entry Points
52
40
 
53
- esri-gl provides three entry points for different use cases:
54
-
55
- | Entry Point | Import | Use Case |
56
- |-------------|--------|----------|
57
- | `esri-gl` | `import { DynamicMapService, Query, ... } from 'esri-gl'` | Core services and tasks (vanilla JS/TS) |
58
- | `esri-gl/react` | `import { useDynamicMapService, useQuery, ... } from 'esri-gl/react'` | React hooks for service lifecycle management |
59
- | `esri-gl/react-map-gl` | `import { EsriDynamicLayer, ... } from 'esri-gl/react-map-gl'` | Declarative react-map-gl components |
41
+ | Import | Use Case |
42
+ |--------|----------|
43
+ | `esri-gl` | Core services and tasks |
44
+ | `esri-gl/react` | React hooks |
45
+ | `esri-gl/react-map-gl` | react-map-gl components |
60
46
 
61
- ## Quick Start
62
-
63
- ### Basic Usage with MapLibre GL JS
47
+ ### CDN (UMD)
64
48
 
65
- ```typescript
66
- import { Map } from 'maplibre-gl';
67
- import { DynamicMapService, VectorTileService, FeatureService } from 'esri-gl';
68
-
69
- const map = new Map({
70
- container: 'map',
71
- style: 'https://demotiles.maplibre.org/style.json',
72
- center: [-95, 37],
73
- zoom: 4
74
- });
75
-
76
- map.on('load', () => {
77
- // Add a dynamic map service
78
- const dynamicService = new DynamicMapService('census-source', map, {
79
- url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer'
80
- });
81
-
82
- map.addLayer({
83
- id: 'census-layer',
84
- type: 'raster',
85
- source: 'census-source'
86
- });
87
-
88
- // Add a feature service with smart vector tile detection
89
- const featureService = new FeatureService('parcels-source', map, {
90
- url: 'https://services.arcgis.com/.../FeatureServer/0',
91
- useVectorTiles: true, // Automatically detects and falls back to GeoJSON if needed
92
- useBoundingBox: true, // Optimize with viewport filtering
93
- where: "STATUS = 'Active'", // Server-side filtering
94
- outFields: '*'
95
- });
96
-
97
- map.addLayer({
98
- id: 'parcels-layer',
99
- type: 'fill',
100
- source: 'parcels-source',
101
- paint: {
102
- 'fill-color': '#007cbf',
103
- 'fill-opacity': 0.5
104
- }
105
- });
106
- });
49
+ ```html
50
+ <script src="https://unpkg.com/esri-gl@latest/dist/index.umd.js"></script>
51
+ <script>
52
+ const service = new esrigl.DynamicMapService('source', map, { url: '...' });
53
+ </script>
107
54
  ```
108
55
 
109
- ### Using with Mapbox GL JS
56
+ ## Quick Start
110
57
 
111
58
  ```typescript
112
- import mapboxgl from 'mapbox-gl';
113
- import { VectorBasemapStyle, ImageService } from 'esri-gl';
114
-
115
- mapboxgl.accessToken = 'your-mapbox-token';
59
+ import { DynamicMapService } from 'esri-gl';
116
60
 
117
- const map = new mapboxgl.Map({
61
+ const map = new maplibregl.Map({
118
62
  container: 'map',
63
+ style: 'https://demotiles.maplibre.org/style.json',
119
64
  center: [-95, 37],
120
65
  zoom: 4
121
66
  });
122
67
 
123
68
  map.on('load', () => {
124
- // Add Esri vector basemap style
125
- const basemapService = new VectorBasemapStyle('basemap-source', map, {
126
- style: 'arcgis/streets'
127
- });
128
-
129
- // Add image service for analytical raster data
130
- const imageService = new ImageService('elevation-source', map, {
131
- url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/ImageServer',
132
- renderingRule: {
133
- rasterFunction: 'Hillshade'
134
- }
135
- });
136
-
137
- map.addLayer({
138
- id: 'elevation-layer',
139
- type: 'raster',
140
- source: 'elevation-source'
69
+ const service = new DynamicMapService('usa-source', map, {
70
+ url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer',
71
+ layers: [0, 1, 2],
72
+ transparent: true
141
73
  });
142
- });
143
- ```
144
-
145
- ## Service Classes
146
-
147
- ### DynamicMapService
148
- Server-rendered raster tiles from ArcGIS Map Services.
149
74
 
150
- ```typescript
151
- const service = new DynamicMapService('source-id', map, {
152
- url: 'https://example.com/arcgis/rest/services/MyService/MapServer',
153
- layers: [0, 1, 2], // Specific layers to display
154
- layerDefs: {
155
- 0: "POP2000 > 100000", // Filter layer 0
156
- 1: "STATE_NAME = 'California'" // Filter layer 1
157
- },
158
- transparent: true,
159
- format: 'png32'
75
+ map.addLayer({ id: 'usa-layer', type: 'raster', source: 'usa-source' });
160
76
  });
161
77
  ```
162
78
 
163
- ### TiledMapService
164
- Pre-cached tile services for optimal performance.
165
-
166
- ```typescript
167
- const service = new TiledMapService('source-id', map, {
168
- url: 'https://example.com/arcgis/rest/services/MyTiledService/MapServer'
169
- });
170
- ```
79
+ ## Service Examples
171
80
 
172
81
  ### FeatureService
173
- Efficient tile-based feature loading from ArcGIS FeatureServers with PBF (Protocol Buffer) support for minimal payload size.
174
-
175
- ```typescript
176
- const service = new FeatureService('source-id', map, {
177
- url: 'https://example.com/arcgis/rest/services/MyService/FeatureServer/0',
178
- where: '1=1',
179
- outFields: '*',
180
- // PBF options (requires ArcGIS Server 10.7+)
181
- minZoom: 2, // Minimum zoom level for data requests
182
- simplifyFactor: 0.3, // Geometry simplification (0-1)
183
- precision: 8, // Coordinate decimal precision
184
- useServiceBounds: true, // Limit requests to service extent
185
- setAttributionFromService: true // Auto-set attribution
186
- });
187
-
188
- // Update filters dynamically
189
- service.setWhere("STATUS = 'Active'");
190
- service.setDate(new Date(), new Date('2024-01-01'));
191
- service.setToken('your-token');
192
-
193
- // Query features by location
194
- const features = await service.getFeaturesByLonLat({ lng: -118, lat: 34 }, 100);
195
-
196
- // Query by object IDs
197
- const specific = await service.getFeaturesByObjectIds([1, 2, 3], true);
198
- ```
199
-
200
- #### Feature Editing (AGOL)
201
82
 
202
83
  ```typescript
203
- // Edit features on an AGOL Feature Service
204
- const service = new FeatureService('editable-source', map, {
84
+ const service = new FeatureService('features-source', map, {
205
85
  url: 'https://services.arcgis.com/.../FeatureServer/0',
206
- token: 'your-agol-token'
86
+ where: "STATUS = 'Active'",
87
+ outFields: 'NAME,STATUS',
88
+ token: 'your-token' // optional
207
89
  });
208
90
 
209
- // Add features
210
- const addResults = await service.addFeatures([
211
- { type: 'Feature', geometry: { type: 'Point', coordinates: [-95, 37] }, properties: { name: 'New Point' } }
212
- ]);
213
-
214
- // Update features
215
- const updateResults = await service.updateFeatures([
216
- { type: 'Feature', geometry: { type: 'Point', coordinates: [-95, 37] }, properties: { OBJECTID: 1, name: 'Updated' } }
217
- ]);
218
-
219
- // Delete features
220
- const deleteResults = await service.deleteFeatures({ objectIds: [1, 2, 3] });
221
-
222
- // Batch edits
223
- const batchResults = await service.applyEdits({
224
- adds: [newFeature],
225
- updates: [updatedFeature],
226
- deletes: [4, 5]
91
+ map.addLayer({
92
+ id: 'features-layer',
93
+ type: 'circle',
94
+ source: 'features-source',
95
+ paint: { 'circle-radius': 5, 'circle-color': '#007cbf' }
227
96
  });
228
- ```
229
-
230
- ### VectorTileService
231
- Client-rendered vector tiles for fast styling and interaction.
232
97
 
233
- ```typescript
234
- const service = new VectorTileService('source-id', map, {
235
- url: 'https://example.com/arcgis/rest/services/MyService/VectorTileServer'
236
- });
98
+ // Edit features
99
+ await service.addFeatures([feature]);
100
+ await service.applyEdits({ adds: [], updates: [], deletes: [1, 2] });
237
101
  ```
238
102
 
239
103
  ### ImageService
240
- Analytical raster data with server-side processing capabilities.
241
104
 
242
105
  ```typescript
243
- const service = new ImageService('source-id', map, {
244
- url: 'https://example.com/arcgis/rest/services/MyImageService/ImageServer',
245
- renderingRule: {
246
- rasterFunction: 'Stretch',
247
- rasterFunctionArguments: {
248
- StretchType: 6,
249
- NumberOfStandardDeviations: 2
250
- }
251
- },
252
- mosaicRule: {
253
- mosaicMethod: 'esriMosaicLockRaster',
254
- lockRasterIds: [1, 2, 3]
255
- }
106
+ const service = new ImageService('landsat-source', map, {
107
+ url: 'https://landsat2.arcgis.com/arcgis/rest/services/Landsat/MS/ImageServer',
108
+ renderingRule: { rasterFunction: 'Natural Color' }
256
109
  });
257
110
  ```
258
111
 
259
112
  ### VectorBasemapStyle
260
- Esri's vector basemap styles with customization options.
261
-
262
- ```typescript
263
- const service = new VectorBasemapStyle('source-id', map, {
264
- style: 'arcgis/streets', // or 'arcgis/navigation', 'arcgis/topographic', etc.
265
- language: 'en',
266
- worldview: 'USA'
267
- });
268
- ```
269
113
 
270
114
  ```typescript
271
- // Custom portal item style
272
- const customBasemap = new VectorBasemapStyle('source-id', map, {
273
- style: 'arcgis/streets',
274
- itemId: 'your-portal-item-id',
275
- token: 'your-token'
276
- });
115
+ VectorBasemapStyle.applyStyle(map, 'arcgis/streets', { apiKey: 'YOUR_KEY' });
277
116
  ```
278
117
 
279
- ## Task-Based Operations
280
-
281
- Modeled after Esri Leaflet's chainable task pattern for querying and identifying features.
282
-
283
- ### IdentifyFeatures
284
- Query vector features at specific locations.
118
+ ## Tasks
285
119
 
286
120
  ```typescript
287
- import { IdentifyFeatures } from 'esri-gl';
121
+ import { IdentifyFeatures, query } from 'esri-gl';
288
122
 
289
- // Identify features at a point using the fluent API
290
- const results = await new IdentifyFeatures('https://example.com/arcgis/rest/services/MyService/MapServer')
123
+ // Identify features at a point
124
+ const results = await new IdentifyFeatures('https://.../MapServer')
291
125
  .at({ lng: -95, lat: 37 })
292
126
  .on(map)
293
127
  .layers('all')
294
128
  .tolerance(5)
295
- .returnGeometry(true)
296
129
  .run();
297
130
 
298
- console.log('Identified features:', results.features);
299
- ```
300
-
301
- ### IdentifyImage
302
- Query raster values from image services.
303
-
304
- ```typescript
305
- import { IdentifyImage } from 'esri-gl';
306
-
307
- const results = await new IdentifyImage('https://example.com/arcgis/rest/services/Elevation/ImageServer')
308
- .at({ lng: -120, lat: 40 })
309
- .run();
310
-
311
- console.log('Pixel value:', results.results[0]?.value);
312
- ```
313
-
314
- ### Query
315
- Advanced feature querying with spatial and attribute filters.
316
-
317
- ```typescript
318
- import { query } from 'esri-gl';
319
-
320
- const results = await query({
321
- url: 'https://example.com/arcgis/rest/services/MyService/FeatureServer/0'
322
- })
323
- .where("STATE_NAME = 'California'")
324
- .intersects({
325
- type: 'Point',
326
- coordinates: [-118, 34]
327
- })
328
- .run();
329
- ```
330
-
331
- ```typescript
332
- // Automatic pagination through all results
333
- import { query } from 'esri-gl';
334
-
335
- const allResults = await query({
336
- url: 'https://example.com/arcgis/rest/services/MyService/FeatureServer/0'
337
- })
338
- .where("STATE_NAME = 'California'")
339
- .runAll(); // Automatically paginates through all pages
131
+ // Query with pagination
132
+ const all = await query({ url: 'https://.../FeatureServer/0' })
133
+ .where("STATE_NAME = 'California'")
134
+ .runAll();
340
135
  ```
341
136
 
342
137
  ## React Integration
343
138
 
344
- esri-gl provides first-class React.js support with comprehensive hooks, components, and seamless integration with **react-map-gl**. Whether you're building with vanilla React + MapLibre/Mapbox or using the react-map-gl wrapper, esri-gl has you covered.
345
-
346
- ### Installation for React Projects
347
-
348
- ```bash
349
- # Core library
350
- npm install esri-gl
351
-
352
- # For React hooks and components
353
- npm install react react-dom @types/react @types/react-dom
354
-
355
- # For react-map-gl integration (recommended)
356
- npm install react-map-gl mapbox-gl
357
- # OR for MapLibre
358
- npm install react-map-gl maplibre-gl
359
- ```
360
-
361
- ### React Hooks Pattern
362
-
363
- Perfect for custom React components with full control over map lifecycle:
139
+ ### Hooks
364
140
 
365
141
  ```typescript
366
- import React, { useState, useRef, useEffect } from 'react';
367
- import { Map } from 'maplibre-gl';
368
- import { useDynamicMapService, useIdentifyFeatures, useFeatureService } from 'esri-gl/react';
369
-
370
- function CustomMapComponent() {
371
- const mapRef = useRef<HTMLDivElement>(null);
372
- const [map, setMap] = useState<Map | null>(null);
373
-
374
- // Initialize MapLibre map
375
- useEffect(() => {
376
- if (!mapRef.current) return;
377
- const mapInstance = new Map({
378
- container: mapRef.current,
379
- style: 'https://demotiles.maplibre.org/style.json',
380
- center: [-95, 37],
381
- zoom: 4
382
- });
383
- setMap(mapInstance);
384
- return () => mapInstance.remove();
385
- }, []);
386
-
387
- // Use esri-gl React hooks
388
- const { service: dynamicService, loading, error } = useDynamicMapService({
389
- sourceId: 'usa-service',
390
- map,
391
- options: {
392
- url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer',
393
- layers: [0, 1, 2],
394
- transparent: true
395
- }
396
- });
397
-
398
- const { service: featureService } = useFeatureService({
399
- sourceId: 'states-service',
400
- map,
401
- options: {
402
- url: 'https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Census_States/FeatureServer/0',
403
- useVectorTiles: true,
404
- useBoundingBox: true
405
- }
406
- });
142
+ import { useDynamicMapService, useIdentifyFeatures } from 'esri-gl/react';
407
143
 
408
- const { identify } = useIdentifyFeatures({
144
+ const { service, loading, error } = useDynamicMapService({
145
+ sourceId: 'usa-service',
146
+ map,
147
+ options: {
409
148
  url: 'https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer',
410
- tolerance: 3
411
- });
412
-
413
- // Handle map clicks for identify
414
- useEffect(() => {
415
- if (!map) return;
416
- const handleClick = async (e: any) => {
417
- const results = await identify({ lng: e.lngLat.lng, lat: e.lngLat.lat }, map);
418
- console.log('Identify results:', results);
419
- };
420
- map.on('click', handleClick);
421
- return () => map.off('click', handleClick);
422
- }, [map, identify]);
423
-
424
- return (
425
- <div>
426
- <div ref={mapRef} style={{ width: '100%', height: '500px' }} />
427
- {loading && <div>Loading Esri services...</div>}
428
- {error && <div>Error: {error.message}</div>}
429
- </div>
430
- );
431
- }
432
- ```
433
-
434
- ### React Map GL Components (Recommended)
435
-
436
- For the smoothest React experience with declarative layer management:
437
-
438
- ```typescript
439
- import React, { useState } from 'react';
440
- import { Map } from 'react-map-gl/mapbox';
441
- import {
442
- EsriDynamicLayer,
443
- EsriFeatureLayer,
444
- EsriVectorTileLayer,
445
- EsriImageLayer
446
- } from 'esri-gl/react-map-gl';
447
-
448
- function MapWithEsriLayers() {
449
- const [viewState, setViewState] = useState({
450
- longitude: -95,
451
- latitude: 37,
452
- zoom: 4
453
- });
454
-
455
- const [selectedStates, setSelectedStates] = useState<string[]>([]);
456
-
457
- return (
458
- <Map
459
- {...viewState}
460
- onMove={evt => setViewState(evt.viewState)}
461
- mapStyle="mapbox://styles/mapbox/streets-v11"
462
- style={{ width: '100%', height: '600px' }}
463
- >
464
- {/* Dynamic Map Service Layer */}
465
- <EsriDynamicLayer
466
- id="usa-demographics"
467
- url="https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer"
468
- layers={[0, 1, 2]}
469
- layerDefs={{
470
- 0: "POP2000 > 100000",
471
- 1: "STATE_NAME IN ('California', 'Texas', 'New York')"
472
- }}
473
- opacity={0.8}
474
- beforeId="waterway-label"
475
- />
476
-
477
- {/* Feature Service with Vector Tiles */}
478
- <EsriFeatureLayer
479
- id="us-states"
480
- url="https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Census_States/FeatureServer/0"
481
- useVectorTiles={true}
482
- useBoundingBox={true}
483
- where={selectedStates.length > 0 ? `STATE_NAME IN ('${selectedStates.join("','")}')` : undefined}
484
- paint={{
485
- 'fill-color': [
486
- 'case',
487
- ['in', ['get', 'STATE_NAME'], ['literal', selectedStates]],
488
- '#ff6b6b',
489
- '#627BC1'
490
- ],
491
- 'fill-opacity': 0.6,
492
- 'fill-outline-color': '#ffffff'
493
- }}
494
- onClick={(feature) => {
495
- const stateName = feature.properties?.STATE_NAME;
496
- if (stateName) {
497
- setSelectedStates(prev =>
498
- prev.includes(stateName)
499
- ? prev.filter(s => s !== stateName)
500
- : [...prev, stateName]
501
- );
502
- }
503
- }}
504
- />
505
-
506
- {/* Vector Tile Service */}
507
- <EsriVectorTileLayer
508
- id="world-imagery-labels"
509
- url="https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/VectorTileServer"
510
- beforeId="usa-demographics"
511
- />
512
-
513
- {/* Image Service for Analytical Data */}
514
- <EsriImageLayer
515
- id="elevation-hillshade"
516
- url="https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/ImageServer"
517
- renderingRule={{
518
- rasterFunction: "Hillshade",
519
- rasterFunctionArguments: {
520
- Azimuth: 315,
521
- Altitude: 45
522
- }
523
- }}
524
- opacity={0.5}
525
- />
526
- </Map>
527
- );
528
- }
529
- ```
530
-
531
- ### Why Choose React Integration?
532
-
533
- #### React Hooks Benefits
534
- - **๐ŸŽ›๏ธ Full Control** - Direct access to map instance and service lifecycle
535
- - **๐Ÿ”„ State Management** - Seamless integration with React state and effects
536
- - **๐ŸŽฏ Custom Logic** - Perfect for complex interactions and custom components
537
- - **๐Ÿ“ฆ Lightweight** - Use only what you need
538
-
539
- #### React Map GL Benefits
540
- - **๐Ÿ“‹ Declarative** - Define layers as JSX components
541
- - **๐Ÿ”„ Automatic Updates** - Props changes automatically update layers
542
- - **๐ŸŽจ Built-in Styling** - Direct paint and layout prop support
543
- - **๐Ÿ‘† Event Handling** - onClick, onHover events built-in
544
- - **๐Ÿ—๏ธ Component Ecosystem** - Works with all react-map-gl features
545
-
546
- ### TypeScript Support
547
-
548
- Full TypeScript support with comprehensive type definitions:
549
-
550
- ```typescript
551
- import type {
552
- DynamicMapServiceOptions,
553
- FeatureServiceOptions,
554
- IdentifyResult,
555
- EsriLayerProps
556
- } from 'esri-gl';
557
- import type { MapRef } from 'react-map-gl/mapbox';
558
-
559
- // Fully typed component props
560
- interface MapComponentProps {
561
- serviceUrl: string;
562
- initialLayers?: number[];
563
- onFeatureClick?: (feature: IdentifyResult) => void;
564
- }
565
-
566
- const TypedMapComponent: React.FC<MapComponentProps> = ({
567
- serviceUrl,
568
- initialLayers = [0],
569
- onFeatureClick
570
- }) => {
571
- // Component implementation with full type safety
572
- };
573
- ```
574
-
575
- See [REACT.md](REACT.md) for complete React integration documentation and advanced patterns.
576
-
577
- ## Advanced Features
578
-
579
- ### Dynamic Layer Management
580
-
581
- ```typescript
582
- // Add layers dynamically
583
- const service = new DynamicMapService('census-source', map, {
584
- url: 'https://example.com/MapServer'
585
- });
586
-
587
- // Update visible layers
588
- service.setLayers([0, 2, 5]);
589
-
590
- // Update layer definitions
591
- service.setLayerDefs({
592
- 0: "POP2000 > 50000",
593
- 2: "STATE_NAME IN ('California', 'Nevada')"
594
- });
595
-
596
- // Clean up
597
- service.remove();
598
- ```
599
-
600
- ### Smart Vector Tile Detection
601
-
602
- The `FeatureService` automatically detects vector tile availability:
603
-
604
- ```typescript
605
- const service = new FeatureService('smart-source', map, {
606
- url: 'https://example.com/FeatureServer/0',
607
- useVectorTiles: true // Will automatically:
608
- // 1. Check for VectorTileServer endpoint
609
- // 2. Test various URL patterns
610
- // 3. Fall back to GeoJSON if vector tiles unavailable
611
- // 4. Log the decision process to console
612
- });
613
- ```
614
-
615
- ### Performance Optimization
616
-
617
- ```typescript
618
- const service = new FeatureService('optimized-source', map, {
619
- url: 'https://example.com/FeatureServer/0',
620
- useBoundingBox: true, // Only load features in current viewport
621
- maxRecordCount: 1000, // Limit records per request
622
- where: 'STATUS = "Active"', // Server-side filtering
623
- outFields: ['OBJECTID', 'NAME', 'STATUS'] // Limit fields
149
+ layers: [0, 1, 2]
150
+ }
624
151
  });
625
152
  ```
626
153
 
627
- ### Authentication
628
-
629
- ```typescript
630
- // Token-based authentication (URL parameter)
631
- const service = new DynamicMapService('secure-source', map, {
632
- url: 'https://example.com/arcgis/rest/services/SecureService/MapServer',
633
- token: 'your-auth-token'
634
- });
154
+ ### react-map-gl Components
635
155
 
636
- // Update token dynamically
637
- service.setToken('new-token');
156
+ ```tsx
157
+ import { EsriDynamicLayer, EsriFeatureLayer } from 'esri-gl/react-map-gl';
638
158
 
639
- // API Key authentication (X-Esri-Authorization header)
640
- const featureService = new FeatureService('api-key-source', map, {
641
- url: 'https://services.arcgis.com/.../FeatureServer/0',
642
- apiKey: 'your-api-key' // Sent as X-Esri-Authorization: Bearer header
643
- });
159
+ <Map mapStyle="mapbox://styles/mapbox/streets-v11">
160
+ <EsriDynamicLayer
161
+ id="census"
162
+ url="https://.../Census/MapServer"
163
+ layers={[0, 1]}
164
+ opacity={0.8}
165
+ />
166
+ <EsriFeatureLayer
167
+ id="states"
168
+ url="https://.../FeatureServer/0"
169
+ paint={{ 'fill-color': '#627BC1', 'fill-opacity': 0.6 }}
170
+ />
171
+ </Map>
644
172
  ```
645
173
 
646
174
  ## Examples
647
175
 
648
- Working examples are available in the [`examples/`](examples/) directory:
649
-
650
- | Example | Entry Point | Description |
651
- |---------|-------------|-------------|
652
- | [maplibre-esm](examples/maplibre-esm/) | `esri-gl` | All services and tasks with vanilla MapLibre GL JS |
653
- | [maplibre-react-hooks](examples/maplibre-react-hooks/) | `esri-gl/react` | All React hooks with MapLibre GL JS |
654
- | [maplibre-react-map-gl](examples/maplibre-react-map-gl/) | `esri-gl/react-map-gl` | All react-map-gl components and tasks |
176
+ Working examples in [`examples/`](examples/):
655
177
 
656
- To run an example:
178
+ | Example | Description |
179
+ |---------|-------------|
180
+ | [maplibre-esm](examples/maplibre-esm/) | All services and tasks with vanilla MapLibre |
181
+ | [maplibre-react-hooks](examples/maplibre-react-hooks/) | React hooks with MapLibre |
182
+ | [maplibre-react-map-gl](examples/maplibre-react-map-gl/) | react-map-gl components |
657
183
 
658
184
  ```bash
659
- cd examples/maplibre-esm # or maplibre-react-hooks, maplibre-react-map-gl
660
- npm install
661
- npm run dev
185
+ cd examples/maplibre-esm && npm install && npm run dev
662
186
  ```
663
187
 
664
188
  ## Development
665
189
 
666
- ### Build System
667
- - **Library Build**: Rollup with TypeScript, Babel, and Terser (UMD + ESM outputs)
668
- - **Type Declarations**: Generated with rollup-plugin-dts in `dist/` directory
669
- - **Demo Development**: Vite dev server with React and TypeScript
670
- - **Documentation**: Docusaurus build system
671
- - **Test Coverage**: 727 tests across 31 test suites
672
-
673
- ### Development Commands
674
-
675
190
  ```bash
676
- # Start demo development server
677
- npm run dev
678
-
679
- # Build library (both UMD and ESM)
680
- npm run build
681
-
682
- # Watch mode for library development
683
- npm run build:watch
684
-
685
- # Type checking
686
- npm run type-check
687
-
688
- # Linting and formatting
689
- npm run lint
690
- npm run format
691
-
692
- # Run tests
693
- npm run test
694
- npm run test:watch
695
- npm run test:coverage
696
-
697
- # Build documentation
698
- npm run build-docs
699
- npm run dev:docs
700
- ```
701
-
702
- ### Project Structure
703
-
704
- ```
705
- src/
706
- โ”œโ”€โ”€ Services/ # Core service classes
707
- โ”‚ โ”œโ”€โ”€ DynamicMapService.ts
708
- โ”‚ โ”œโ”€โ”€ FeatureService.ts
709
- โ”‚ โ”œโ”€โ”€ ImageService.ts
710
- โ”‚ โ”œโ”€โ”€ TiledMapService.ts
711
- โ”‚ โ”œโ”€โ”€ VectorTileService.ts
712
- โ”‚ โ””โ”€โ”€ VectorBasemapStyle.ts
713
- โ”œโ”€โ”€ Tasks/ # Task-based operations
714
- โ”‚ โ”œโ”€โ”€ Find.ts
715
- โ”‚ โ”œโ”€โ”€ IdentifyFeatures.ts
716
- โ”‚ โ”œโ”€โ”€ IdentifyImage.ts
717
- โ”‚ โ””โ”€โ”€ Query.ts
718
- โ”œโ”€โ”€ demo/ # Demo React components
719
- โ”œโ”€โ”€ tests/ # Comprehensive test suite (92.94% coverage)
720
- โ””โ”€โ”€ types.ts # TypeScript interfaces
721
-
722
- dist/
723
- โ”œโ”€โ”€ index.d.ts # Main TypeScript declarations
724
- โ”œโ”€โ”€ react.d.ts # React hooks declarations
725
- โ”œโ”€โ”€ react-map-gl.d.ts # React Map GL declarations
726
- โ”œโ”€โ”€ index.js # ESM build
727
- โ”œโ”€โ”€ index.umd.js # UMD build
728
- โ”œโ”€โ”€ react.js # React hooks ESM build
729
- โ”œโ”€โ”€ react-map-gl.js # React Map GL ESM build
730
- โ””โ”€โ”€ package.json # Package metadata with exports map
731
- ```
732
-
733
- ## Browser Support
734
-
735
- - **Modern Browsers**: Chrome, Firefox, Safari, Edge (ES2018+)
736
- - **MapLibre GL JS**: v2.0+ (recommended), v1.15+
737
- - **Mapbox GL JS**: v2.0+ (recommended), v1.13+
738
-
739
- ## TypeScript Support
740
-
741
- esri-gl is written in TypeScript and provides full type definitions consolidated in a single directory:
742
-
743
- ```typescript
744
- import type {
745
- DynamicMapServiceOptions,
746
- FeatureServiceOptions,
747
- IdentifyResult,
748
- EsriGeoJSONFeatureCollection
749
- } from 'esri-gl';
750
-
751
- const options: FeatureServiceOptions = {
752
- url: 'https://example.com/FeatureServer/0',
753
- useVectorTiles: true,
754
- where: '1=1'
755
- };
191
+ npm run dev # Start demo dev server
192
+ npm run build # Build library (ESM + UMD)
193
+ npm run test # Run tests (727 tests, 31 suites)
194
+ npm run type-check # TypeScript check
195
+ npm run lint # ESLint
196
+ npm run build:docs # Build documentation site
197
+ npm run build:demo # Build demo site
756
198
  ```
757
199
 
758
- All type declarations are available in the `dist/` directory after building.
759
-
760
200
  ## Contributing
761
201
 
762
- We welcome contributions! Please follow these steps:
202
+ 1. Fork and create a feature branch
203
+ 2. Make changes and add tests
204
+ 3. Run `npm run test && npm run type-check && npm run lint`
205
+ 4. Submit a pull request
763
206
 
764
- 1. Fork the repository
765
- 2. Create a feature branch: `git checkout -b feature/my-feature`
766
- 3. Make changes and add tests
767
- 4. Run validation: `npm run validate`
768
- 5. Commit changes: `git commit -m 'Add new feature'`
769
- 6. Push to branch: `git push origin feature/my-feature`
770
- 7. Submit a pull request
771
-
772
- ### Pre-commit Hooks
773
-
774
- This project uses [Husky](https://typicode.github.io/husky/) to automatically run quality checks before each commit:
775
-
776
- - **Formatting & Linting**: Automatically formats and lints staged files using Prettier and ESLint
777
- - **Testing**: Runs the full test suite to ensure no regressions
778
-
779
- The hooks are automatically installed when you run `npm install`. If you need to skip them (not recommended), you can use `git commit --no-verify`.
780
-
781
- For more details, see [.husky/README.md](.husky/README.md).
207
+ Pre-commit hooks automatically run formatting, linting, and tests via [Husky](https://typicode.github.io/husky/).
782
208
 
783
209
  ## License
784
210
 
785
- MIT License - see [LICENSE](LICENSE) file for details.
211
+ MIT โ€” see [LICENSE](LICENSE)
786
212
 
787
213
  ## Acknowledgements
788
214
 
789
- - **Esri Leaflet** - Inspiration for API design and architectural patterns
790
- - **MapLibre GL JS** & **Mapbox GL JS** - Incredible WebGL mapping libraries
791
- - **Esri ArcGIS Platform** - Comprehensive GIS services and APIs
792
- - **[mapbox-gl-esri-sources](https://github.com/frontiersi/mapbox-gl-esri-sources/)** - Reference implementation for Esri service integration patterns
793
- - **[mapbox-gl-arcgis-featureserver](https://github.com/rowanwins/mapbox-gl-arcgis-featureserver)** by **Rowan Winsemius** - The FeatureService implementation with PBF support is based on this excellent library. It provides efficient tile-based feature loading with Protocol Buffer Format (PBF) support for minimal payload size.
794
-
795
- ## Fork Notice
796
-
797
- This project originated as a fork of **[frontiersi/mapbox-gl-esri-sources](https://github.com/frontiersi/mapbox-gl-esri-sources/)**. It has been substantially refactored and expanded:
798
-
799
- - Migrated to a service + task architecture similar to Esri Leaflet
800
- - Added unified TypeScript typing and consolidated build outputs (ESM + UMD)
801
- - Introduced additional services (Dynamic, Image, Vector Basemap Styles, Identify / Query tasks, smart FeatureService vector tile detection, etc.)
802
- - Implemented a React/Vite demo suite and extended test coverage
803
-
804
- All original credit for the foundational concept and early integration patterns goes to the maintainers of the upstream repository. If you need the simpler original implementation, or want to compare behavior, please visit the upstream project.
805
-
215
+ - **[Esri Leaflet](https://esri.github.io/esri-leaflet/)** โ€” API design inspiration
216
+ - **[mapbox-gl-esri-sources](https://github.com/frontiersi/mapbox-gl-esri-sources/)** โ€” Foundational integration patterns (this project originated as a fork)
217
+ - **[mapbox-gl-arcgis-featureserver](https://github.com/rowanwins/mapbox-gl-arcgis-featureserver)** by Rowan Winsemius โ€” FeatureService PBF implementation
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esri-gl",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A module for making it easier to use Esri services in mapbox-gl or maplibre-gl.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -33,7 +33,7 @@
33
33
  "bugs": {
34
34
  "url": "https://github.com/muimsd/esri-gl/issues"
35
35
  },
36
- "homepage": "https://esri-gl.netlify.app",
36
+ "homepage": "https://esri-gl.pages.dev",
37
37
  "keywords": [
38
38
  "mapbox",
39
39
  "maplibre",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esri-gl",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "description": "A module for making it easier to use Esri services in mapbox-gl or maplibre-gl.",
6
6
  "main": "dist/index.js",
@@ -124,8 +124,8 @@
124
124
  "bugs": {
125
125
  "url": "https://github.com/muimsd/esri-gl/issues"
126
126
  },
127
- "homepage": "https://esri-gl.netlify.app",
128
- "demo": "https://esri-gl-demo.netlify.app",
127
+ "homepage": "https://esri-gl.pages.dev",
128
+ "demo": "https://esri-gl-demo.pages.dev",
129
129
  "author": "Muhammad Imran Siddique 2025, Rowan Winsemius 2020-2025",
130
130
  "license": "MIT",
131
131
  "keywords": [