maplibre-gl-lidar 0.6.0 → 0.6.2

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
@@ -50,51 +50,53 @@ npm install maplibre-gl-lidar
50
50
  ### Basic Usage (Vanilla JS/TypeScript)
51
51
 
52
52
  ```typescript
53
- import maplibregl from 'maplibre-gl';
54
- import { LidarControl } from 'maplibre-gl-lidar';
55
- import 'maplibre-gl-lidar/style.css';
56
- import 'maplibre-gl/dist/maplibre-gl.css';
53
+ import maplibregl from "maplibre-gl";
54
+ import { LidarControl } from "maplibre-gl-lidar";
55
+ import "maplibre-gl-lidar/style.css";
56
+ import "maplibre-gl/dist/maplibre-gl.css";
57
57
 
58
58
  const map = new maplibregl.Map({
59
- container: 'map',
60
- style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
59
+ container: "map",
60
+ style: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
61
61
  center: [-122.4, 37.8],
62
62
  zoom: 12,
63
63
  pitch: 60,
64
64
  maxPitch: 85, // Allow higher pitch for better 3D viewing
65
65
  });
66
66
 
67
- map.on('load', () => {
67
+ map.on("load", () => {
68
68
  // Add the LiDAR control
69
69
  const lidarControl = new LidarControl({
70
- title: 'LiDAR Viewer',
70
+ title: "LiDAR Viewer",
71
71
  collapsed: true,
72
72
  pointSize: 2,
73
- colorScheme: 'elevation',
73
+ colorScheme: "elevation",
74
74
  pickable: true, // Enable point picking for hover tooltips
75
75
  });
76
76
 
77
- map.addControl(lidarControl, 'top-right');
77
+ map.addControl(lidarControl, "top-right");
78
78
 
79
79
  // Listen for events
80
- lidarControl.on('load', (event) => {
81
- console.log('Point cloud loaded:', event.pointCloud);
80
+ lidarControl.on("load", (event) => {
81
+ console.log("Point cloud loaded:", event.pointCloud);
82
82
  lidarControl.flyToPointCloud();
83
83
  });
84
84
 
85
85
  // Load a point cloud programmatically
86
- lidarControl.loadPointCloud('https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz');
86
+ lidarControl.loadPointCloud(
87
+ "https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz"
88
+ );
87
89
  });
88
90
  ```
89
91
 
90
92
  ### React Usage
91
93
 
92
94
  ```tsx
93
- import { useEffect, useRef, useState } from 'react';
94
- import maplibregl, { Map } from 'maplibre-gl';
95
- import { LidarControlReact, useLidarState } from 'maplibre-gl-lidar/react';
96
- import 'maplibre-gl-lidar/style.css';
97
- import 'maplibre-gl/dist/maplibre-gl.css';
95
+ import { useEffect, useRef, useState } from "react";
96
+ import maplibregl, { Map } from "maplibre-gl";
97
+ import { LidarControlReact, useLidarState } from "maplibre-gl-lidar/react";
98
+ import "maplibre-gl-lidar/style.css";
99
+ import "maplibre-gl/dist/maplibre-gl.css";
98
100
 
99
101
  function App() {
100
102
  const mapContainer = useRef<HTMLDivElement>(null);
@@ -106,28 +108,28 @@ function App() {
106
108
 
107
109
  const mapInstance = new maplibregl.Map({
108
110
  container: mapContainer.current,
109
- style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
111
+ style: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
110
112
  center: [-122.4, 37.8],
111
113
  zoom: 12,
112
114
  pitch: 60,
113
115
  maxPitch: 85, // Allow higher pitch for better 3D viewing
114
116
  });
115
117
 
116
- mapInstance.on('load', () => setMap(mapInstance));
118
+ mapInstance.on("load", () => setMap(mapInstance));
117
119
 
118
120
  return () => mapInstance.remove();
119
121
  }, []);
120
122
 
121
123
  return (
122
- <div style={{ width: '100%', height: '100vh' }}>
123
- <div ref={mapContainer} style={{ width: '100%', height: '100%' }} />
124
+ <div style={{ width: "100%", height: "100vh" }}>
125
+ <div ref={mapContainer} style={{ width: "100%", height: "100%" }} />
124
126
  {map && (
125
127
  <LidarControlReact
126
128
  map={map}
127
129
  title="LiDAR Viewer"
128
130
  pointSize={state.pointSize}
129
131
  colorScheme={state.colorScheme}
130
- onLoad={(pc) => console.log('Loaded:', pc)}
132
+ onLoad={(pc) => console.log("Loaded:", pc)}
131
133
  defaultUrl="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz"
132
134
  />
133
135
  )}
@@ -147,37 +149,37 @@ The main control class implementing MapLibre's `IControl` interface.
147
149
  ```typescript
148
150
  interface LidarControlOptions {
149
151
  // Panel settings
150
- collapsed?: boolean; // Start collapsed (default: true)
151
- position?: string; // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
152
- title?: string; // Panel title (default: 'LiDAR Viewer')
153
- panelWidth?: number; // Panel width in pixels (default: 365)
154
- panelMaxHeight?: number; // Panel max height with scrollbar (default: 500)
155
- className?: string; // Custom CSS class
152
+ collapsed?: boolean; // Start collapsed (default: true)
153
+ position?: string; // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
154
+ title?: string; // Panel title (default: 'LiDAR Viewer')
155
+ panelWidth?: number; // Panel width in pixels (default: 365)
156
+ panelMaxHeight?: number; // Panel max height with scrollbar (default: 500)
157
+ className?: string; // Custom CSS class
156
158
 
157
159
  // Point cloud styling
158
- pointSize?: number; // Point size in pixels (default: 2)
159
- opacity?: number; // Opacity 0-1 (default: 1.0)
160
- colorScheme?: ColorScheme; // Color scheme (default: 'elevation')
161
- usePercentile?: boolean; // Use 2-98% percentile for coloring (default: true)
162
- pointBudget?: number; // Max points to display (default: 1000000)
160
+ pointSize?: number; // Point size in pixels (default: 2)
161
+ opacity?: number; // Opacity 0-1 (default: 1.0)
162
+ colorScheme?: ColorScheme; // Color scheme (default: 'elevation')
163
+ usePercentile?: boolean; // Use 2-98% percentile for coloring (default: true)
164
+ pointBudget?: number; // Max points to display (default: 1000000)
163
165
 
164
166
  // Filters and adjustments
165
- elevationRange?: [number, number] | null; // Elevation filter
166
- zOffsetEnabled?: boolean; // Enable Z offset adjustment (default: false)
167
- zOffset?: number; // Z offset in meters (default: 0)
167
+ elevationRange?: [number, number] | null; // Elevation filter
168
+ zOffsetEnabled?: boolean; // Enable Z offset adjustment (default: false)
169
+ zOffset?: number; // Z offset in meters (default: 0)
168
170
 
169
171
  // Interaction
170
- pickable?: boolean; // Enable point picking/hover tooltips (default: false)
171
- pickInfoFields?: string[]; // Fields to show in tooltip (default: all)
172
+ pickable?: boolean; // Enable point picking/hover tooltips (default: false)
173
+ pickInfoFields?: string[]; // Fields to show in tooltip (default: all)
172
174
 
173
175
  // Behavior
174
- autoZoom?: boolean; // Auto zoom to data after loading (default: true)
176
+ autoZoom?: boolean; // Auto zoom to data after loading (default: true)
175
177
 
176
178
  // COPC Streaming (dynamic loading)
177
- copcLoadingMode?: 'full' | 'dynamic'; // Loading mode for COPC files (default: 'dynamic')
178
- streamingPointBudget?: number; // Max points for streaming (default: 5000000)
179
+ copcLoadingMode?: "full" | "dynamic"; // Loading mode for COPC files (default: 'dynamic')
180
+ streamingPointBudget?: number; // Max points for streaming (default: 5000000)
179
181
  streamingMaxConcurrentRequests?: number; // Concurrent node requests (default: 4)
180
- streamingViewportDebounceMs?: number; // Viewport change debounce (default: 150)
182
+ streamingViewportDebounceMs?: number; // Viewport change debounce (default: 150)
181
183
  }
182
184
  ```
183
185
 
@@ -261,8 +263,8 @@ By default, elevation and intensity coloring uses the 2nd-98th percentile range
261
263
  ```typescript
262
264
  // Percentile coloring is enabled by default
263
265
  const control = new LidarControl({
264
- colorScheme: 'elevation',
265
- usePercentile: true, // default
266
+ colorScheme: "elevation",
267
+ usePercentile: true, // default
266
268
  });
267
269
 
268
270
  // Disable to use full value range (min-max)
@@ -298,7 +300,12 @@ const control = new LidarControl({ pickable: true });
298
300
  control.setPickable(true);
299
301
 
300
302
  // Optionally filter which fields to display
301
- control.setPickInfoFields(['Classification', 'Intensity', 'GpsTime', 'ReturnNumber']);
303
+ control.setPickInfoFields([
304
+ "Classification",
305
+ "Intensity",
306
+ "GpsTime",
307
+ "ReturnNumber",
308
+ ]);
302
309
  ```
303
310
 
304
311
  ### Z Offset
@@ -331,6 +338,7 @@ When using the "Classification" color scheme, an interactive legend appears show
331
338
  - A checkbox to toggle visibility
332
339
 
333
340
  **Features:**
341
+
334
342
  - **Show All / Hide All buttons** - Quickly toggle all classifications at once
335
343
  - **Individual toggles** - Show or hide specific classification types
336
344
  - **Auto-detection** - Classifications are automatically detected from loaded data
@@ -341,24 +349,24 @@ When using the "Classification" color scheme, an interactive legend appears show
341
349
  // The legend automatically appears with checkboxes for each class
342
350
 
343
351
  // Programmatically control visibility
344
- control.setColorScheme('classification');
352
+ control.setColorScheme("classification");
345
353
 
346
354
  // Hide specific classifications (e.g., hide noise points)
347
- control.setClassificationVisibility(7, false); // Hide "Low Point (Noise)"
355
+ control.setClassificationVisibility(7, false); // Hide "Low Point (Noise)"
348
356
  control.setClassificationVisibility(18, false); // Hide "High Noise"
349
357
 
350
358
  // Show only ground and buildings
351
359
  control.hideAllClassifications();
352
- control.setClassificationVisibility(2, true); // Ground
353
- control.setClassificationVisibility(6, true); // Building
360
+ control.setClassificationVisibility(2, true); // Ground
361
+ control.setClassificationVisibility(6, true); // Building
354
362
 
355
363
  // Get available classifications in the data
356
364
  const available = control.getAvailableClassifications();
357
- console.log('Classifications:', available); // [2, 3, 4, 5, 6, ...]
365
+ console.log("Classifications:", available); // [2, 3, 4, 5, 6, ...]
358
366
 
359
367
  // Get currently hidden classifications
360
368
  const hidden = control.getHiddenClassifications();
361
- console.log('Hidden:', hidden); // [7, 18]
369
+ console.log("Hidden:", hidden); // [7, 18]
362
370
  ```
363
371
 
364
372
  **ASPRS Classification Codes:**
@@ -378,12 +386,14 @@ console.log('Hidden:', hidden); // [7, 18]
378
386
  For large COPC (Cloud Optimized Point Cloud) files, dynamic streaming loads only the points visible in the current viewport, dramatically reducing initial load time and memory usage.
379
387
 
380
388
  **Key features:**
389
+
381
390
  - **Viewport-based loading** - Only loads octree nodes visible in the current map view
382
391
  - **Level-of-detail (LOD)** - Automatically selects appropriate detail level based on zoom
383
392
  - **Center-first priority** - Points near the viewport center load first
384
393
  - **Point budget** - Limits total points in memory (default: 5 million)
385
394
 
386
395
  **How it works:**
396
+
387
397
  1. When loading a COPC file (from URL or local file), dynamic mode is used by default
388
398
  2. As you pan/zoom the map, new nodes are streamed based on viewport
389
399
  3. Deeper octree levels (more detail) load as you zoom in
@@ -392,20 +402,21 @@ For large COPC (Cloud Optimized Point Cloud) files, dynamic streaming loads only
392
402
  ```typescript
393
403
  // Dynamic loading is the default for COPC files
394
404
  const control = new LidarControl();
395
- control.loadPointCloud('https://example.com/large-pointcloud.copc.laz');
405
+ control.loadPointCloud("https://example.com/large-pointcloud.copc.laz");
396
406
 
397
407
  // Explicitly set loading mode
398
408
  const control = new LidarControl({
399
- copcLoadingMode: 'dynamic', // or 'full' for complete load
400
- streamingPointBudget: 10_000_000, // 10 million points max
409
+ copcLoadingMode: "dynamic", // or 'full' for complete load
410
+ streamingPointBudget: 10_000_000, // 10 million points max
401
411
  });
402
412
 
403
413
  // Override per-load
404
- control.loadPointCloud(file, { loadingMode: 'full' }); // Force full load
405
- control.loadPointCloud(url, { loadingMode: 'dynamic' }); // Force streaming
414
+ control.loadPointCloud(file, { loadingMode: "full" }); // Force full load
415
+ control.loadPointCloud(url, { loadingMode: "dynamic" }); // Force streaming
406
416
  ```
407
417
 
408
418
  **Loading modes:**
419
+
409
420
  - `'dynamic'` (default for COPC) - Stream nodes based on viewport, ideal for large files
410
421
  - `'full'` - Load entire point cloud upfront, better for small files
411
422
 
@@ -416,6 +427,7 @@ control.loadPointCloud(url, { loadingMode: 'dynamic' }); // Force streaming
416
427
  maplibre-gl-lidar supports [Entwine Point Tile (EPT)](https://entwine.io/en/latest/entwine-point-tile.html) datasets, a widely-used format for serving large point clouds over HTTP with viewport-based streaming.
417
428
 
418
429
  **Key features:**
430
+
419
431
  - **Directory-based format** - Metadata in ept.json, hierarchy in ept-hierarchy/, data in ept-data/
420
432
  - **Viewport-based streaming** - Points load dynamically based on current map view
421
433
  - **LAZ compression** - Efficient data transfer using LAZ compression
@@ -425,15 +437,19 @@ maplibre-gl-lidar supports [Entwine Point Tile (EPT)](https://entwine.io/en/late
425
437
 
426
438
  ```typescript
427
439
  // Load EPT dataset by URL (automatically detected via ept.json)
428
- lidarControl.loadPointCloud('https://na-c.entwine.io/dublin/ept.json');
440
+ lidarControl.loadPointCloud("https://na-c.entwine.io/dublin/ept.json");
429
441
 
430
442
  // Or load programmatically
431
- lidarControl.loadPointCloudEptStreaming('https://na-c.entwine.io/dublin/ept.json', {
432
- pointBudget: 5_000_000, // Max points in memory
433
- });
443
+ lidarControl.loadPointCloudEptStreaming(
444
+ "https://na-c.entwine.io/dublin/ept.json",
445
+ {
446
+ pointBudget: 5_000_000, // Max points in memory
447
+ }
448
+ );
434
449
  ```
435
450
 
436
451
  **Sample EPT datasets:**
452
+
437
453
  - Dublin, Ireland: `https://na-c.entwine.io/dublin/ept.json`
438
454
  - New York City (4.7B points): `https://na-c.entwine.io/nyc/ept.json`
439
455
  - Red Rocks: `https://na-c.entwine.io/red-rocks/ept.json`
@@ -515,12 +531,11 @@ docker run -p 8080:80 maplibre-gl-lidar
515
531
 
516
532
  ### Available Tags
517
533
 
518
- | Tag | Description |
519
- |-----|-------------|
520
- | `latest` | Latest release |
521
- | `x.y.z` | Specific version (e.g., `1.0.0`) |
522
- | `x.y` | Minor version (e.g., `1.0`) |
523
-
534
+ | Tag | Description |
535
+ | -------- | -------------------------------- |
536
+ | `latest` | Latest release |
537
+ | `x.y.z` | Specific version (e.g., `1.0.0`) |
538
+ | `x.y` | Minor version (e.g., `1.0`) |
524
539
 
525
540
  ## Dependencies
526
541
 
@@ -537,4 +552,8 @@ MIT
537
552
 
538
553
  ## Credits
539
554
 
540
- The sample dataset [autzen-classified.copc.laz](https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz) is from [Hobu, Inc.](https://hobu.co). Credits to [Howard Butler](https://github.com/hobu).
555
+ - Microsoft Planetary Computer: [USGS 3DEP Lidar Point Cloud Dataset](https://planetarycomputer.microsoft.com/dataset/usgs-3dep-lidar)
556
+ - AWS Open Data: [USGS 3DEP LiDAR Point Clouds](https://registry.opendata.aws/usgs-lidar)
557
+ - [Hobu, Inc.](https://hobu.co)
558
+ - [COPC.io](https://copc.io)
559
+ - [Entwine](https://entwine.io)
@@ -34221,6 +34221,7 @@ class PointCloudLoader {
34221
34221
  };
34222
34222
  }
34223
34223
  }
34224
+ proj4.defs("EPSG:2180", "+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs");
34224
34225
  function createBufferGetter(buffer) {
34225
34226
  const uint8 = new Uint8Array(buffer);
34226
34227
  return async (begin, end) => {
@@ -34332,6 +34333,32 @@ function getVerticalUnitConversionFactor$1(wkt2) {
34332
34333
  }
34333
34334
  return 1;
34334
34335
  }
34336
+ function clampLatLng$1(lng, lat, context = "") {
34337
+ let clampedLng = lng;
34338
+ let clampedLat = lat;
34339
+ let wasClamped = false;
34340
+ if (lat > 90) {
34341
+ clampedLat = 90;
34342
+ wasClamped = true;
34343
+ } else if (lat < -90) {
34344
+ clampedLat = -90;
34345
+ wasClamped = true;
34346
+ }
34347
+ if (lng > 180) {
34348
+ clampedLng = 180;
34349
+ wasClamped = true;
34350
+ } else if (lng < -180) {
34351
+ clampedLng = -180;
34352
+ wasClamped = true;
34353
+ }
34354
+ if (wasClamped) {
34355
+ console.warn(
34356
+ `COPC: Clamped transformed coordinates to valid WGS84 range${context ? ` (${context})` : ""}:`,
34357
+ `[${lng.toFixed(6)}, ${lat.toFixed(6)}] -> [${clampedLng.toFixed(6)}, ${clampedLat.toFixed(6)}]`
34358
+ );
34359
+ }
34360
+ return [clampedLng, clampedLat];
34361
+ }
34335
34362
  const DEFAULT_OPTIONS$2 = {
34336
34363
  pointBudget: 5e6,
34337
34364
  maxConcurrentRequests: 4,
@@ -34439,10 +34466,30 @@ class CopcStreamingLoader {
34439
34466
  } catch (e) {
34440
34467
  console.warn("Failed to setup coordinate transformation:", e);
34441
34468
  }
34469
+ } else {
34470
+ const minX = header2.min[0];
34471
+ const minY = header2.min[1];
34472
+ const maxX = header2.max[0];
34473
+ const maxY = header2.max[1];
34474
+ let detectedEPSG = null;
34475
+ if (minX >= 1e5 && maxX <= 9e5 && minY >= 1e5 && maxY <= 8e5) {
34476
+ detectedEPSG = "EPSG:2180";
34477
+ }
34478
+ if (detectedEPSG) {
34479
+ try {
34480
+ const projConverter = proj4(detectedEPSG, "EPSG:4326");
34481
+ this._transformer = (coord) => projConverter.forward(coord);
34482
+ this._needsTransform = true;
34483
+ } catch (e) {
34484
+ console.warn(`Failed to setup coordinate transformation from ${detectedEPSG}:`, e);
34485
+ }
34486
+ }
34442
34487
  }
34443
34488
  if (this._needsTransform && this._transformer) {
34444
- const [minLng, minLat] = this._transformer([header2.min[0], header2.min[1]]);
34445
- const [maxLng, maxLat] = this._transformer([header2.max[0], header2.max[1]]);
34489
+ const [rawMinLng, rawMinLat] = this._transformer([header2.min[0], header2.min[1]]);
34490
+ const [rawMaxLng, rawMaxLat] = this._transformer([header2.max[0], header2.max[1]]);
34491
+ const [minLng, minLat] = clampLatLng$1(rawMinLng, rawMinLat, "header bounds min");
34492
+ const [maxLng, maxLat] = clampLatLng$1(rawMaxLng, rawMaxLat, "header bounds max");
34446
34493
  this._bounds = {
34447
34494
  minX: Math.min(minLng, maxLng),
34448
34495
  minY: Math.min(minLat, maxLat),
@@ -34532,8 +34579,10 @@ class CopcStreamingLoader {
34532
34579
  };
34533
34580
  let boundsWgs84 = bounds2;
34534
34581
  if (this._needsTransform && this._transformer) {
34535
- const [sw_lng, sw_lat] = this._transformer([minX, minY]);
34536
- const [ne_lng, ne_lat] = this._transformer([minX + nodeSize, minY + nodeSize]);
34582
+ const [rawSwLng, rawSwLat] = this._transformer([minX, minY]);
34583
+ const [rawNeLng, rawNeLat] = this._transformer([minX + nodeSize, minY + nodeSize]);
34584
+ const [sw_lng, sw_lat] = clampLatLng$1(rawSwLng, rawSwLat, "node bounds SW");
34585
+ const [ne_lng, ne_lat] = clampLatLng$1(rawNeLng, rawNeLat, "node bounds NE");
34537
34586
  boundsWgs84 = {
34538
34587
  minX: Math.min(sw_lng, ne_lng),
34539
34588
  minY: Math.min(sw_lat, ne_lat),
@@ -34765,7 +34814,8 @@ class CopcStreamingLoader {
34765
34814
  const y = yGetter(i);
34766
34815
  const z = zGetter(i);
34767
34816
  if (this._needsTransform && this._transformer) {
34768
- const [lng, lat] = this._transformer([x, y]);
34817
+ const [rawLng, rawLat] = this._transformer([x, y]);
34818
+ const [lng, lat] = clampLatLng$1(rawLng, rawLat, "");
34769
34819
  this._positions[pointIndex * 3] = lng - this._coordinateOrigin[0];
34770
34820
  this._positions[pointIndex * 3 + 1] = lat - this._coordinateOrigin[1];
34771
34821
  this._positions[pointIndex * 3 + 2] = z * this._verticalUnitFactor;
@@ -34986,6 +35036,32 @@ function createAttributeArray(type, length) {
34986
35036
  return new Float32Array(length);
34987
35037
  }
34988
35038
  }
35039
+ function clampLatLng(lng, lat, context = "") {
35040
+ let clampedLng = lng;
35041
+ let clampedLat = lat;
35042
+ let wasClamped = false;
35043
+ if (lat > 90) {
35044
+ clampedLat = 90;
35045
+ wasClamped = true;
35046
+ } else if (lat < -90) {
35047
+ clampedLat = -90;
35048
+ wasClamped = true;
35049
+ }
35050
+ if (lng > 180) {
35051
+ clampedLng = 180;
35052
+ wasClamped = true;
35053
+ } else if (lng < -180) {
35054
+ clampedLng = -180;
35055
+ wasClamped = true;
35056
+ }
35057
+ if (wasClamped && context) {
35058
+ console.warn(
35059
+ `EPT: Clamped transformed coordinates to valid WGS84 range${context ? ` (${context})` : ""}:`,
35060
+ `[${lng.toFixed(6)}, ${lat.toFixed(6)}] -> [${clampedLng.toFixed(6)}, ${clampedLat.toFixed(6)}]`
35061
+ );
35062
+ }
35063
+ return [clampedLng, clampedLat];
35064
+ }
34989
35065
  function extractProjcsFromWkt(wkt2) {
34990
35066
  if (wkt2.startsWith("COMPD_CS[")) {
34991
35067
  const projcsStart = wkt2.indexOf("PROJCS[");
@@ -35125,17 +35201,19 @@ class EptStreamingLoader {
35125
35201
  }
35126
35202
  const [minX, minY, minZ, maxX, maxY, maxZ] = this._metadata.boundsConforming;
35127
35203
  if (this._needsTransform && this._transformer) {
35128
- const [minLng, minLat] = this._transformer([minX, minY]);
35129
- const [maxLng, maxLat] = this._transformer([maxX, maxY]);
35130
- if (isNaN(minLng) || isNaN(minLat) || isNaN(maxLng) || isNaN(maxLat) || !isFinite(minLng) || !isFinite(minLat) || !isFinite(maxLng) || !isFinite(maxLat)) {
35204
+ const [rawMinLng, rawMinLat] = this._transformer([minX, minY]);
35205
+ const [rawMaxLng, rawMaxLat] = this._transformer([maxX, maxY]);
35206
+ if (isNaN(rawMinLng) || isNaN(rawMinLat) || isNaN(rawMaxLng) || isNaN(rawMaxLat) || !isFinite(rawMinLng) || !isFinite(rawMinLat) || !isFinite(rawMaxLng) || !isFinite(rawMaxLat)) {
35131
35207
  console.error("EPT coordinate transformation produced invalid bounds:", {
35132
35208
  input: { minX, minY, maxX, maxY },
35133
- output: { minLng, minLat, maxLng, maxLat }
35209
+ output: { rawMinLng, rawMinLat, rawMaxLng, rawMaxLat }
35134
35210
  });
35135
35211
  this._bounds = { minX, minY, minZ, maxX, maxY, maxZ };
35136
35212
  this._needsTransform = false;
35137
35213
  this._transformer = null;
35138
35214
  } else {
35215
+ const [minLng, minLat] = clampLatLng(rawMinLng, rawMinLat, "header bounds min");
35216
+ const [maxLng, maxLat] = clampLatLng(rawMaxLng, rawMaxLat, "header bounds max");
35139
35217
  this._bounds = {
35140
35218
  minX: Math.min(minLng, maxLng),
35141
35219
  minY: Math.min(minLat, maxLat),
@@ -35286,8 +35364,10 @@ class EptStreamingLoader {
35286
35364
  };
35287
35365
  let boundsWgs84 = bounds2;
35288
35366
  if (this._needsTransform && this._transformer) {
35289
- const [sw_lng, sw_lat] = this._transformer([minX, minY]);
35290
- const [ne_lng, ne_lat] = this._transformer([minX + nodeSize, minY + nodeSize]);
35367
+ const [rawSwLng, rawSwLat] = this._transformer([minX, minY]);
35368
+ const [rawNeLng, rawNeLat] = this._transformer([minX + nodeSize, minY + nodeSize]);
35369
+ const [sw_lng, sw_lat] = clampLatLng(rawSwLng, rawSwLat, "node bounds SW");
35370
+ const [ne_lng, ne_lat] = clampLatLng(rawNeLng, rawNeLat, "node bounds NE");
35291
35371
  boundsWgs84 = {
35292
35372
  minX: Math.min(sw_lng, ne_lng),
35293
35373
  minY: Math.min(sw_lat, ne_lat),
@@ -35571,7 +35651,8 @@ class EptStreamingLoader {
35571
35651
  const y = positions[i * 3 + 1];
35572
35652
  const z = positions[i * 3 + 2];
35573
35653
  if (this._needsTransform && this._transformer) {
35574
- const [lng, lat] = this._transformer([x, y]);
35654
+ const [rawLng, rawLat] = this._transformer([x, y]);
35655
+ const [lng, lat] = clampLatLng(rawLng, rawLat, "");
35575
35656
  this._positions[pointIndex * 3] = lng - this._coordinateOrigin[0];
35576
35657
  this._positions[pointIndex * 3 + 1] = lat - this._coordinateOrigin[1];
35577
35658
  this._positions[pointIndex * 3 + 2] = z * this._verticalUnitFactor;
@@ -35651,7 +35732,8 @@ class EptStreamingLoader {
35651
35732
  const y = yGetter(dataView, byteOffset);
35652
35733
  const z = zGetter(dataView, byteOffset);
35653
35734
  if (this._needsTransform && this._transformer) {
35654
- const [lng, lat] = this._transformer([x, y]);
35735
+ const [rawLng, rawLat] = this._transformer([x, y]);
35736
+ const [lng, lat] = clampLatLng(rawLng, rawLat, "");
35655
35737
  this._positions[pointIndex * 3] = lng - this._coordinateOrigin[0];
35656
35738
  this._positions[pointIndex * 3 + 1] = lat - this._coordinateOrigin[1];
35657
35739
  this._positions[pointIndex * 3 + 2] = z * this._verticalUnitFactor;
@@ -38431,10 +38513,14 @@ class LidarControl {
38431
38513
  });
38432
38514
  viewportManager.start();
38433
38515
  if (this._options.autoZoom) {
38516
+ const clampedMinY = Math.max(-90, Math.min(90, bounds2.minY));
38517
+ const clampedMaxY = Math.max(-90, Math.min(90, bounds2.maxY));
38518
+ const clampedMinX = Math.max(-180, Math.min(180, bounds2.minX));
38519
+ const clampedMaxX = Math.max(-180, Math.min(180, bounds2.maxX));
38434
38520
  (_c = this._map) == null ? void 0 : _c.fitBounds(
38435
38521
  [
38436
- [bounds2.minX, bounds2.minY],
38437
- [bounds2.maxX, bounds2.maxY]
38522
+ [clampedMinX, clampedMinY],
38523
+ [clampedMaxX, clampedMaxY]
38438
38524
  ],
38439
38525
  {
38440
38526
  padding: 50,
@@ -38591,10 +38677,14 @@ class LidarControl {
38591
38677
  });
38592
38678
  viewportManager.start();
38593
38679
  if (this._options.autoZoom) {
38680
+ const clampedMinY = Math.max(-90, Math.min(90, bounds2.minY));
38681
+ const clampedMaxY = Math.max(-90, Math.min(90, bounds2.maxY));
38682
+ const clampedMinX = Math.max(-180, Math.min(180, bounds2.minX));
38683
+ const clampedMaxX = Math.max(-180, Math.min(180, bounds2.maxX));
38594
38684
  (_d = this._map) == null ? void 0 : _d.fitBounds(
38595
38685
  [
38596
- [bounds2.minX, bounds2.minY],
38597
- [bounds2.maxX, bounds2.maxY]
38686
+ [clampedMinX, clampedMinY],
38687
+ [clampedMaxX, clampedMaxY]
38598
38688
  ],
38599
38689
  {
38600
38690
  padding: 50,
@@ -38975,6 +39065,9 @@ class LidarControl {
38975
39065
  var _a;
38976
39066
  this._state.pickable = pickable;
38977
39067
  (_a = this._pointCloudManager) == null ? void 0 : _a.setPickable(pickable);
39068
+ if (!pickable && this._tooltip) {
39069
+ this._tooltip.style.display = "none";
39070
+ }
38978
39071
  this._emit("stylechange");
38979
39072
  this._emit("statechange");
38980
39073
  }
@@ -39701,4 +39794,4 @@ exports.generateId = generateId;
39701
39794
  exports.getClassificationName = getClassificationName;
39702
39795
  exports.getFilename = getFilename;
39703
39796
  exports.throttle = throttle;
39704
- //# sourceMappingURL=LidarLayerAdapter-CHu9adLN.cjs.map
39797
+ //# sourceMappingURL=LidarLayerAdapter-Sw_plY5a.cjs.map