@mappedin/blue-dot 6.14.0-beta.0 → 6.16.0-beta.0

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
@@ -33,20 +33,26 @@ blueDot.enable();
33
33
  // Listening to device position updates from the browser
34
34
  blueDot.watchDevicePosition(true);
35
35
 
36
- // Send custom position updates from a different source
37
- blueDot.update({
38
- // Latitude of the update
39
- latitude,
40
- // Longitude of the update
41
- longitude,
42
- // Optional accuracy in meters
43
- accuracy,
44
- // Optional heading in degrees from north
45
- heading,
46
- // Optional floor or floor ID
47
- floorOrFloorId,
36
+ // Report a position from an external source (e.g., custom IPS)
37
+ blueDot.reportPosition({
38
+ latitude: 43.6532,
39
+ longitude: -79.3832,
40
+ confidence: 0.8, // 0-1 confidence score
41
+ heading: 90, // Optional heading in degrees
42
+ floorLevel: 0, // Optional floor level
48
43
  });
49
44
 
45
+ // Force position to a known location (overrides all other sources)
46
+ blueDot.forcePosition(
47
+ {
48
+ latitude: 43.6532,
49
+ longitude: -79.3832,
50
+ heading: 90,
51
+ floorLevel: 0,
52
+ },
53
+ 30000, // How long to hold this position in ms
54
+ );
55
+
50
56
  // Attach the camera to the BlueDot
51
57
  blueDot.follow('position-only');
52
58
  ```
@@ -59,7 +65,7 @@ blueDot.follow('position-only');
59
65
 
60
66
  ```ts
61
67
  // Browser will prompt for permission
62
- await blueDot.watchDevicePosition(true);
68
+ blueDot.watchDevicePosition(true);
63
69
 
64
70
  // Listen for errors (including permission denied)
65
71
  blueDot.on('error', error => {
@@ -115,7 +121,24 @@ Disable and hide the BlueDot. Stops all position tracking.
115
121
  blueDot.disable();
116
122
  ```
117
123
 
118
- #### `watchDevicePosition(watch: boolean): Promise<void>`
124
+ #### `getState(): ReadonlyDeep<BlueDotState>`
125
+
126
+ Returns the current BlueDot configuration (radius, colors, timeout, etc.).
127
+
128
+ ```ts
129
+ const state = blueDot.getState();
130
+ console.log(state.radius, state.color);
131
+ ```
132
+
133
+ #### `updateState(options: BlueDotUpdateState): void`
134
+
135
+ Update BlueDot options after it has been enabled (e.g. colors, timeout, accuracy threshold).
136
+
137
+ ```ts
138
+ blueDot.updateState({ color: '#ff0000', timeout: 60000 });
139
+ ```
140
+
141
+ #### `watchDevicePosition(watch: boolean): void`
119
142
 
120
143
  Start or stop listening to device GPS position.
121
144
 
@@ -124,8 +147,8 @@ Start or stop listening to device GPS position.
124
147
  - Emits `error` events on geolocation errors
125
148
 
126
149
  ```ts
127
- await blueDot.watchDevicePosition(true); // Start tracking
128
- await blueDot.watchDevicePosition(false); // Stop tracking
150
+ blueDot.watchDevicePosition(true); // Start tracking
151
+ blueDot.watchDevicePosition(false); // Stop tracking
129
152
  ```
130
153
 
131
154
  #### `watchDeviceOrientation(watch: boolean): Promise<void>`
@@ -143,9 +166,61 @@ button.addEventListener('click', async () => {
143
166
  });
144
167
  ```
145
168
 
146
- #### `update(position, options?)`
169
+ #### `reportPosition(options)`
170
+
171
+ Report a position from an external source (e.g., custom Indoor Positioning System). The position is fed into the fusion engine with a confidence score that influences how much weight it receives relative to other sources like GPS.
172
+
173
+ ```ts
174
+ blueDot.reportPosition({
175
+ latitude: 43.6532,
176
+ longitude: -79.3832,
177
+ confidence: 0.8, // 0-1: higher = more influence on fused position
178
+ heading: 90, // Optional: degrees from north
179
+ floorLevel: 0, // Optional: floor level
180
+ });
181
+ ```
182
+
183
+ Parameters:
184
+
185
+ - `latitude: number` - Latitude coordinate (required)
186
+ - `longitude: number` - Longitude coordinate (required)
187
+ - `confidence?: number` - Confidence score from 0 to 1 (default: 0.5)
188
+ - `accuracy?: number` - Accuracy in meters
189
+ - `heading?: number` - Heading in degrees from north
190
+ - `floorLevel?: number` - Floor level
191
+ - `timestamp?: number` - Timestamp in milliseconds (default: Date.now())
192
+
193
+ #### `forcePosition(position, durationMs?)`
194
+
195
+ Force the BlueDot to a specific position, overriding all other data sources for a specified duration. Use this when you have an authoritative position from a calibration source like Visual Positioning (VPS) or AI Localizer.
196
+
197
+ ```ts
198
+ blueDot.forcePosition(
199
+ {
200
+ latitude: 43.6532,
201
+ longitude: -79.3832,
202
+ heading: 90, // Optional: degrees from north
203
+ floorLevel: 0, // Optional: floor level
204
+ },
205
+ 30000, // How long to hold (default: 30 seconds)
206
+ );
207
+ ```
208
+
209
+ Parameters:
147
210
 
148
- Manually set or override position properties.
211
+ - `position.latitude: number` - Latitude coordinate (required)
212
+ - `position.longitude: number` - Longitude coordinate (required)
213
+ - `position.heading?: number` - Heading in degrees from north
214
+ - `position.floorLevel?: number` - Floor level
215
+ - `durationMs?: number` - Duration in milliseconds (default: 30000)
216
+
217
+ During the forced period, GPS and other position sources are ignored. After expiration, the BlueDot transitions back to using fused position data. Emits `anchor-set` when activated and `anchor-expired` when the duration ends.
218
+
219
+ #### `update(position, options?)` _(deprecated)_
220
+
221
+ > **Deprecated**: Use `reportPosition()` for feeding positions into the fusion engine, or `forcePosition()` to set an authoritative position anchor.
222
+
223
+ Legacy method to manually set or override position properties.
149
224
 
150
225
  ```ts
151
226
  // Set full position
@@ -184,11 +259,13 @@ blueDot.follow('position-and-path-direction');
184
259
  blueDot.follow(false);
185
260
  ```
186
261
 
187
- Camera options:
262
+ Camera options (`FollowCameraOptions`):
188
263
 
189
- - `zoomLevel?: number` - Target zoom level
190
- - `pitch?: number` - Camera pitch angle
191
- - `animationDuration?: number` - Animation duration in ms
264
+ - `zoomLevel?: number` - Target zoom level (default: 21)
265
+ - `pitch?: number` - Camera pitch angle (default: 45)
266
+ - `bearing?: number` - Camera bearing in degrees (position-only mode only)
267
+ - `duration?: number` - Animation duration in ms (default: 1000)
268
+ - `easing?: 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear'` - Animation easing (default: 'ease-in-out')
192
269
 
193
270
  #### `setPositionProcessor(processor?)`
194
271
 
@@ -272,8 +349,8 @@ Fired when BlueDot status changes.
272
349
 
273
350
  ```ts
274
351
  blueDot.on('status-change', event => {
275
- console.log(event.status); // New status
276
- console.log(event.previousStatus); // Previous status
352
+ console.log(event.status); // New status: 'hidden' | 'active' | 'inactive' | 'disabled'
353
+ console.log(event.action); // Action that caused the change: 'timeout' | 'error' | 'position-update' | 'enable' | 'disable' | 'initialize'
277
354
  });
278
355
  ```
279
356
 
@@ -303,7 +380,8 @@ Fired when follow mode changes.
303
380
 
304
381
  ```ts
305
382
  blueDot.on('follow-change', event => {
306
- console.log(event.mode); // Current follow mode or false
383
+ console.log(event.following); // Whether the camera is following the BlueDot
384
+ console.log(event.mode); // Follow mode when following: 'position-only' | 'position-and-heading' | 'position-and-path-direction'
307
385
  });
308
386
  ```
309
387
 
@@ -327,10 +405,59 @@ blueDot.on('hover', event => {
327
405
  });
328
406
  ```
329
407
 
408
+ ### `anchor-set`
409
+
410
+ Fired when a position anchor is set via `forcePosition()` or a custom sensor.
411
+
412
+ ```ts
413
+ blueDot.on('anchor-set', event => {
414
+ console.log('Anchor set:', event.anchor);
415
+ console.log('Position:', event.anchor.latitude, event.anchor.longitude);
416
+ console.log('From sensor:', event.anchor.sensorId);
417
+ });
418
+ ```
419
+
420
+ ### `anchor-expired`
421
+
422
+ Fired when a position anchor expires after its duration.
423
+
424
+ ```ts
425
+ blueDot.on('anchor-expired', event => {
426
+ console.log('Anchor expired:', event.anchor.sensorId);
427
+ // BlueDot will now use fused position data again
428
+ });
429
+ ```
430
+
431
+ ## Options
432
+
433
+ ```ts
434
+ blueDot.on('anchor-set', event => {
435
+ console.log('Anchor set:', event.anchor);
436
+ console.log('Position:', event.anchor.latitude, event.anchor.longitude);
437
+ console.log('From sensor:', event.anchor.sensorId);
438
+ });
439
+ ```
440
+
441
+ ### `anchor-expired`
442
+
443
+ Fired when a position anchor expires after its duration.
444
+
445
+ ```ts
446
+ blueDot.on('anchor-expired', event => {
447
+ console.log('Anchor expired:', event.anchor.sensorId);
448
+ // BlueDot will now use fused position data again
449
+ });
450
+ ```
451
+
330
452
  ## Options
331
453
 
454
+ Options for `enable()` and `updateState()`. All properties are optional. The type is exported as `BlueDotUpdateState` from `@mappedin/blue-dot`.
455
+
332
456
  ```ts
333
- export type BlueDotOptions = {
457
+ import type { BlueDotUpdateState } from '@mappedin/blue-dot';
458
+
459
+ // BlueDotUpdateState structure (all properties optional)
460
+ interface Options {
334
461
  /**
335
462
  * The radius of the BlueDot in pixels. The BlueDot will maintain this size clamped to a minimum of 0.35 metres.
336
463
  * @default 10
@@ -399,12 +526,12 @@ export type BlueDotOptions = {
399
526
  initialState?: 'hidden' | 'inactive';
400
527
  /**
401
528
  * Whether to prevent position updates outside the map bounds.
402
- * @default false
529
+ * @default true
403
530
  */
404
531
  preventOutOfBounds?: boolean;
405
532
  /**
406
- * Maximum accuracy (in meters) to accept. Updates with lower accuracy are ignored.
407
- * @default Infinity
533
+ * Maximum accuracy (in meters) to accept. Updates exceeding this value are dropped.
534
+ * @default 50
408
535
  */
409
536
  accuracyThreshold?: number;
410
537
  /**
@@ -412,9 +539,114 @@ export type BlueDotOptions = {
412
539
  * @default false
413
540
  */
414
541
  debug?: boolean;
415
- };
542
+ }
543
+ ```
544
+
545
+ ## Custom Sensors
546
+
547
+ You can create custom sensors to provide position data from your own sources (e.g., BLE beacons, Wi-Fi triangulation, visual positioning). Custom sensors extend `BaseSensor` and can publish position updates or set authoritative position anchors.
548
+
549
+ ### Creating a Custom Sensor
550
+
551
+ ```ts
552
+ import { BaseSensor } from '@mappedin/blue-dot';
553
+
554
+ class MyPositioningSensor extends BaseSensor {
555
+ readonly id = 'my-ips';
556
+ readonly requiresPermission = false;
557
+
558
+ protected async start(): Promise<void> {
559
+ // Start your positioning system
560
+ this.myIps.onPosition((lat, lng, confidence) => {
561
+ // Publish absolute position updates via the internal event system
562
+ this.publish('absolute-update', {
563
+ sensorId: this.id,
564
+ update: {
565
+ latitude: lat,
566
+ longitude: lng,
567
+ confidence, // 0-1
568
+ timestamp: Date.now(),
569
+ },
570
+ });
571
+ });
572
+ }
573
+
574
+ protected stop(): void {
575
+ // Clean up your positioning system
576
+ this.myIps.stop();
577
+ }
578
+
579
+ async checkPermission(): Promise<'granted' | 'denied' | 'prompt' | 'unavailable'> {
580
+ return 'granted';
581
+ }
582
+
583
+ async requestPermission(): Promise<'granted' | 'denied' | 'prompt' | 'unavailable'> {
584
+ return 'granted';
585
+ }
586
+ }
587
+ ```
588
+
589
+ ### Setting Position Anchors from Custom Sensors
590
+
591
+ Custom sensors can set authoritative position anchors using `this.setAnchor()`. This is useful for calibration sources like Visual Positioning (VPS) or AI localization.
592
+
593
+ ```ts
594
+ class MyLocalizerSensor extends BaseSensor {
595
+ readonly id = 'my-localizer';
596
+ readonly requiresPermission = false;
597
+
598
+ async localize(imageData: Blob): Promise<void> {
599
+ const result = await this.callLocalizerApi(imageData);
600
+
601
+ // Set an anchor - this overrides other position sources
602
+ this.setAnchor({
603
+ latitude: result.latitude,
604
+ longitude: result.longitude,
605
+ heading: result.bearing,
606
+ floorLevel: result.floor,
607
+ ttl: 60000, // Anchor valid for 60 seconds
608
+ confidence: 0.95, // High confidence
609
+ anchorOnlyPeriodMs: 5000, // Ignore GPS for first 5 seconds
610
+ });
611
+ }
612
+
613
+ clearLocalization(): void {
614
+ this.clearAnchor();
615
+ }
616
+
617
+ // ... other required methods
618
+ }
619
+ ```
620
+
621
+ ### Registering Custom Sensors
622
+
623
+ ```ts
624
+ const blueDot = new BlueDot(mapView);
625
+
626
+ // Create and register your sensor
627
+ const mySensor = new MyPositioningSensor();
628
+ blueDot.Sensors.register(mySensor);
629
+
630
+ // Enable the sensor
631
+ mySensor.enable();
632
+
633
+ // Access built-in sensors via .get()
634
+ blueDot.Sensors.get('geolocation').enable();
635
+ blueDot.Sensors.get('deviceorientation').enable();
416
636
  ```
417
637
 
638
+ ### Anchor Options
639
+
640
+ When calling `setAnchor()`, you can configure:
641
+
642
+ - `latitude: number` - Latitude coordinate (required)
643
+ - `longitude: number` - Longitude coordinate (required)
644
+ - `heading?: number` - Heading in degrees from north
645
+ - `floorLevel?: number` - Floor level
646
+ - `ttl?: number` - Time-to-live in milliseconds (default: 30000)
647
+ - `confidence?: number` - Confidence score 0-1 (default: 1.0)
648
+ - `anchorOnlyPeriodMs?: number` - Duration to ignore other sources (default: 5000)
649
+
418
650
  ## React Native
419
651
 
420
652
  ```tsx
@@ -442,15 +674,17 @@ function BlueDotDisplay() {
442
674
  const {
443
675
  isReady,
444
676
  isEnabled,
445
- state,
446
- position,
677
+ status,
678
+ coordinate,
447
679
  floor,
448
680
  isFollowing,
449
681
  accuracy,
450
682
  heading,
451
683
  enable,
452
684
  disable,
453
- update,
685
+ update, // Deprecated: use reportPosition or forcePosition
686
+ reportPosition,
687
+ forcePosition,
454
688
  follow,
455
689
  } = useBlueDot();
456
690
 
@@ -462,11 +696,11 @@ function BlueDotDisplay() {
462
696
  }, []),
463
697
  );
464
698
 
465
- // Listen for state changes
699
+ // Listen for status changes
466
700
  useBlueDotEvent(
467
- 'state-change',
701
+ 'status-change',
468
702
  useCallback(event => {
469
- console.log('State changed:', event.state);
703
+ console.log('Status changed:', event.status);
470
704
  }, []),
471
705
  );
472
706
 
@@ -478,6 +712,21 @@ function BlueDotDisplay() {
478
712
  }, []),
479
713
  );
480
714
 
715
+ // Listen for anchor events (from forcePosition or custom sensors)
716
+ useBlueDotEvent(
717
+ 'anchor-set',
718
+ useCallback(event => {
719
+ console.log('Anchor set from:', event.anchor.sensorId);
720
+ }, []),
721
+ );
722
+
723
+ useBlueDotEvent(
724
+ 'anchor-expired',
725
+ useCallback(event => {
726
+ console.log('Anchor expired:', event.anchor.sensorId);
727
+ }, []),
728
+ );
729
+
481
730
  useEffect(() => {
482
731
  if (isReady && !isEnabled) {
483
732
  // All methods are async - use await or .then()
@@ -489,15 +738,14 @@ function BlueDotDisplay() {
489
738
  }
490
739
  }, [isReady, isEnabled, enable]);
491
740
 
492
- const handleUpdatePosition = useCallback(async () => {
741
+ const handleReportPosition = useCallback(async () => {
493
742
  try {
494
- // Update position manually
495
- await update({
743
+ // Report position from external IPS
744
+ await reportPosition({
496
745
  latitude: 43.6532,
497
746
  longitude: -79.3832,
498
- accuracy: 5,
747
+ confidence: 0.8,
499
748
  heading: 90,
500
- floorOrFloorId: floor,
501
749
  });
502
750
 
503
751
  // Enable follow mode
@@ -505,25 +753,45 @@ function BlueDotDisplay() {
505
753
  zoomLevel: 19,
506
754
  });
507
755
  } catch (error) {
508
- console.error('Failed to update position:', error);
756
+ console.error('Failed to report position:', error);
509
757
  }
510
- }, [update, follow, floor]);
758
+ }, [reportPosition, follow]);
759
+
760
+ const handleForcePosition = useCallback(async () => {
761
+ try {
762
+ // Force position from calibration source
763
+ await forcePosition(
764
+ {
765
+ latitude: 43.6532,
766
+ longitude: -79.3832,
767
+ heading: 90,
768
+ floorLevel: 0,
769
+ },
770
+ 30000,
771
+ );
772
+ } catch (error) {
773
+ console.error('Failed to force position:', error);
774
+ }
775
+ }, [forcePosition]);
511
776
 
512
777
  return (
513
778
  <View>
514
779
  <Text>Is Ready: {isReady ? 'Yes' : 'No'}</Text>
515
780
  <Text>Is Enabled: {isEnabled ? 'Yes' : 'No'}</Text>
516
- <Text>State: {state}</Text>
781
+ <Text>Status: {status}</Text>
517
782
  <Text>Following: {isFollowing ? 'Yes' : 'No'}</Text>
518
- {position && (
783
+ {coordinate && (
519
784
  <Text>
520
- Position: {position.latitude.toFixed(4)}, {position.longitude.toFixed(4)}
785
+ Position: {coordinate.latitude.toFixed(4)}, {coordinate.longitude.toFixed(4)}
521
786
  </Text>
522
787
  )}
523
788
  {accuracy && <Text>Accuracy: {accuracy.toFixed(1)}m</Text>}
524
789
  {heading && <Text>Heading: {heading.toFixed(0)}°</Text>}
525
- <TouchableOpacity onPress={handleUpdatePosition}>
526
- <Text>Update Position & Follow</Text>
790
+ <TouchableOpacity onPress={handleReportPosition}>
791
+ <Text>Report Position & Follow</Text>
792
+ </TouchableOpacity>
793
+ <TouchableOpacity onPress={handleForcePosition}>
794
+ <Text>Force Position (Calibration)</Text>
527
795
  </TouchableOpacity>
528
796
  </View>
529
797
  );
@@ -532,6 +800,6 @@ function BlueDotDisplay() {
532
800
 
533
801
  **Key Differences from Vanilla JS:**
534
802
 
535
- - **All methods are async**: `enable()`, `disable()`, `update()`, and `follow()` return Promises
803
+ - **All methods are async**: `enable()`, `disable()`, `reportPosition()`, `forcePosition()`, and `follow()` return Promises
536
804
  - **Rich state**: Hook returns `isReady`, `state`, `position`, `floor`, `isFollowing`, `accuracy`, `heading` for real-time updates
537
- - **Separate event hook**: Use `useBlueDotEvent` for listening to position-update, state-change, and follow-change events
805
+ - **Separate event hook**: Use `useBlueDotEvent` for listening to position-update, state-change, follow-change, anchor-set, and anchor-expired events