@wemap/viewer 0.0.0-stage → 14.8.0-beta.3

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/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2026 Wemap SAS. All rights reserved.
2
+
3
+ This software and its associated source code, documentation, and other
4
+ materials (the "Software") are the proprietary and confidential property of
5
+ Wemap SAS. The Software is licensed, not sold.
6
+
7
+ No part of the Software may be used, copied, reproduced, modified, merged,
8
+ published, distributed, sublicensed, or sold, in whole or in part, in any form
9
+ or by any means, without the prior written permission of Wemap SAS, except as
10
+ expressly permitted under a separate written license or subscription agreement
11
+ with Wemap SAS. Use of the Software is governed by, and subject to the terms
12
+ of, that agreement.
13
+
14
+ Unauthorized use, reproduction, or distribution of the Software, or any portion
15
+ of it, may result in severe civil and criminal penalties, and will be
16
+ prosecuted to the maximum extent possible under the law.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL WEMAP
21
+ SAS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,168 @@
1
- # Temporary Holding Version
1
+ # @wemap/viewer
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Framework-agnostic **360° panorama viewer** for Wemap maps. It loads the
4
+ map's image graph (the 360° photos captured on site) and lets users look
5
+ around and walk from photo to photo. Built on three.js, with no UI framework.
6
+
7
+ Pair it with [`@wemap/viewer-map`](../viewer-map) to show the graph and the look
8
+ direction on a [`@wemap/map`](../map).
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @wemap/core @wemap/geo @wemap/viewer
14
+ ```
15
+
16
+ `three` and `@tweenjs/tween.js` are regular dependencies — nothing else to install.
17
+
18
+ ### No build step (`<script>` tag)
19
+
20
+ Load the UMD bundle `dist/cdn/wemap-viewer.umd.js` after the `@wemap/core` and
21
+ `@wemap/geo` bundles. It inlines three.js and exposes
22
+ `window.Wemap.Viewer`.
23
+
24
+ ## Requirements
25
+
26
+ - **`core.init()` first.** Identity (emmid), auth and the VPS endpoint all come
27
+ from `@wemap/core`; the viewer takes none of them.
28
+ - **A map with a VPS provider.** Without it, `open()` rejects.
29
+ - **A sized container.** The canvas fills its container and follows its size
30
+ (a `ResizeObserver` handles resizes), so give the container a height.
31
+
32
+ ## Quick start
33
+
34
+ ```ts
35
+ import { core } from '@wemap/core';
36
+ import { WemapViewer } from '@wemap/viewer';
37
+ import '@wemap/viewer/style.css';
38
+
39
+ await core.init({ emmid: '31668', token: '<token>' });
40
+
41
+ const viewer = new WemapViewer(document.getElementById('viewer')!, {
42
+ onNavigate: (nodeId) => console.log('now at', nodeId),
43
+ });
44
+
45
+ // Open on a node, looking east. Resolves once its image is on screen.
46
+ await viewer.open(1059484, { heading: 90 });
47
+ ```
48
+
49
+ `open()` takes a node id (a deep link, a click on the map graph) or a position,
50
+ in which case it opens on the nearest node, on the position's level when it has
51
+ one:
52
+
53
+ ```ts
54
+ import { Coordinates } from '@wemap/geo';
55
+
56
+ // Latitude, longitude, altitude (none), level 1.
57
+ await viewer.open(new Coordinates(43.60907, 3.91719, null, 1));
58
+ ```
59
+
60
+ The first call fetches the image graph. Later calls move there like
61
+ `navigateTo`, and `heading` turns the view with an animation. Without `heading`
62
+ the look direction is kept. Awaiting is optional: `ready` and `onError` report
63
+ the same outcome.
64
+
65
+ ## Moving around
66
+
67
+ - **Look around** by dragging (with inertia), or with the arrow keys once the
68
+ viewer has focus (press on it, or Tab to it). Zoom with the mouse wheel
69
+ (`zoom: false` disables it).
70
+ - **Walk** by clicking the floor: hovering shows a disc under the pointer and a
71
+ cross on the nearest node; clicking walks there. Disable with
72
+ `navigation: false`. Paused while the view moves to the next node.
73
+ - **Programmatically:** `viewer.open(target, { heading })` (awaitable), or
74
+ `viewer.navigateTo(nodeId)` (fire and forget), and
75
+ `viewer.nearestNode(coordinates, level?)` to find the node closest to a place
76
+ (after `viewer.loadGraph()`, which fetches the graph without opening a photo).
77
+ `viewer.lookAt(heading, pitch = 0)` turns the camera the short way round;
78
+ dragging the panorama cancels it.
79
+
80
+ The look direction is a real compass heading, so it is kept when you change
81
+ node: you keep facing the same way.
82
+
83
+ `navigateTo` updates `currentNode` and fires `navigate` right away, while the
84
+ new image loads and fades in. If that image fails to load, the viewer goes back
85
+ to the node still on screen (`navigate` fires again with it) and calls
86
+ `onError`.
87
+
88
+ ## Locating a point
89
+
90
+ In `locate` click mode, a click on the panorama gives the geo position of what
91
+ is under it (from the VPS depth estimate) instead of walking: to place a POI
92
+ where the user points, for example.
93
+
94
+ ```ts
95
+ viewer.setClickMode('locate');
96
+ viewer.on('locate', ({ coordinates, node }) => {
97
+ console.log(coordinates.lat, coordinates.lng, coordinates.alt, coordinates.level);
98
+ });
99
+
100
+ viewer.setClickMode('navigate'); // back to walking
101
+ ```
102
+
103
+ The cursor becomes a crosshair, and clicks during a transition are ignored. The
104
+ level is the clicked node's when the VPS gives none. Failures go to `onError`.
105
+
106
+ ## Measuring
107
+
108
+ In `measure` click mode, two clicks on the panorama give the distance between
109
+ the two points (from the VPS depth estimate). The viewer draws a dot on each
110
+ point and a line between them; a third click clears them, as do changing mode
111
+ and moving to another node.
112
+
113
+ ```ts
114
+ viewer.setClickMode('measure');
115
+ viewer.on('measure', ({ distance }) => showLabel(`${distance.toFixed(2)} m`));
116
+ ```
117
+
118
+ ## Events
119
+
120
+ Each constructor callback (`onNavigate`, `onHeadingChange`, `onReady`,
121
+ `onError`) takes one listener. For more, subscribe with `viewer.on()`, which
122
+ returns an unsubscribe function:
123
+
124
+ ```ts
125
+ const off = viewer.on('heading', (heading) => compass.rotate(heading));
126
+ off();
127
+ ```
128
+
129
+ | Event | Payload | When |
130
+ | ---------- | --------------------- | -------------------------------------------- |
131
+ | `ready` | — | The first image is on screen (once) |
132
+ | `navigate` | `ViewerNode` | A move to a node starts (click or code) |
133
+ | `heading` | `number` (degrees) | The look heading changes |
134
+ | `locate` | `LocatedPoint` | A click in `locate` mode was located |
135
+ | `measure` | `Measurement` | Two clicks in `measure` mode were measured |
136
+
137
+ ## Options
138
+
139
+ | Option | Type | Default | Description |
140
+ | -------------------- | --------------------------- | ------- | --------------------------------------------- |
141
+ | `zoom` | `boolean` | `true` | Mouse-wheel zoom |
142
+ | `navigation` | `boolean` | `true` | Click the floor to walk to the nearest node |
143
+ | `onNavigate` | `(nodeId: number) => void` | | A move to a node starts |
144
+ | `onHeadingChange` | `(heading: number) => void` | | The look heading changes |
145
+ | `onReady` | `() => void` | | The first image is on screen (once) |
146
+ | `onLocate` | `(point: LocatedPoint) => void` | | A click in `locate` mode was located |
147
+ | `onMeasure` | `(measurement: Measurement) => void` | | Two clicks in `measure` mode were measured |
148
+ | `onError` | `(error: Error) => void` | | Graph, image, located point or measurement failed |
149
+
150
+ ## API
151
+
152
+ | Member | Description |
153
+ | ----------------------------------- | ----------------------------------------------------------- |
154
+ | `open(target, { heading?, pitch? })` | Open on a node id or the nearest node to a position; resolves once its image is on screen, rejects if the node is unknown or the graph or image fails |
155
+ | `loadGraph(): Promise<ViewerNode[]>` | Fetch the image graph without opening a photo (once; shared with `open()`) |
156
+ | `getNodes(): ViewerNode[]` | All nodes (id, coordinates, level, image, capture heading); empty until the graph is loaded |
157
+ | `currentNode: ViewerNode \| null` | The node the viewer is at (set as soon as a move starts) |
158
+ | `navigateTo(nodeId)` | Move to a node; no-op if unknown or already there |
159
+ | `nearestNode(coordinates, level?)` | Closest node to a place, on `level` when given; null before the graph is loaded |
160
+ | `getHeading(): number` | Current look heading, degrees (0 = north, clockwise) |
161
+ | `lookAt(heading, pitch?)` | Turn to a heading/pitch, animated |
162
+ | `setClickMode(mode)` / `clickMode` | What a click does: `'navigate'` (walk, default), `'locate'` or `'measure'` |
163
+ | `getGraphGeojson()` | The graph as GeoJSON (used by `@wemap/viewer-map`) |
164
+ | `on(event, callback)` | Subscribe to an event; returns an unsubscribe function |
165
+ | `resize()` | Re-measure the container (automatic on resize) |
166
+ | `destroy()` | Release the renderer, images, listeners and canvas |
167
+
168
+ `ViewerDataClient` (graph and image URLs) is exported for standalone use too.
@@ -0,0 +1,2 @@
1
+ .wemap-viewer__canvas{touch-action:none;-webkit-user-select:none;user-select:none;cursor:grab;width:100%;height:100%;display:block}.wemap-viewer__canvas:active{cursor:grabbing}.wemap-viewer__canvas:focus:not(:focus-visible){outline:none}
2
+ /*$vite$:1*/