@osm-editor-kit/street-imagery-react 0.1.0-alpha.2 → 0.1.0-alpha.5
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 +68 -0
- package/dist/MapillaryPanel-I5GKT6YT.js +2 -0
- package/dist/StreetLevelImagerySourcesAndLayers.d.ts +6 -1
- package/dist/StreetLevelImageryViewer.d.ts +10 -1
- package/dist/chunk-72HJV6II.js +300 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +574 -10
- package/dist/mapillary/useMapillaryFeatures.d.ts +36 -0
- package/dist/panels/MapillaryPanel.d.ts +13 -1
- package/dist/panels/mapillaryLookAt.d.ts +41 -0
- package/dist/viewpoints/FloatingPhotoViewer.d.ts +31 -0
- package/dist/viewpoints/ViewpointLayer.d.ts +17 -0
- package/dist/viewpoints/useViewSuggestions.d.ts +23 -0
- package/dist/viewpoints/useViewpointSessionStore.d.ts +39 -0
- package/dist/viewpoints/viewDirectionLabels.d.ts +4 -0
- package/package.json +3 -3
- package/dist/MapillaryPanel-QX6NESX7.js +0 -2
- package/dist/chunk-BS6LPDMY.js +0 -143
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ React + MapLibre UI on top of `@osm-editor-kit/street-imagery`:
|
|
|
12
12
|
- TanStack Query hooks to load provider data for the viewport
|
|
13
13
|
- Lazy Mapillary and Panoramax viewer panels
|
|
14
14
|
- Helpers for selection, thumbnails, and easing the map to a photo
|
|
15
|
+
- Viewpoints + floating photo viewer: suggested views per direction, history, map layer
|
|
15
16
|
|
|
16
17
|
## Usage
|
|
17
18
|
|
|
@@ -52,3 +53,70 @@ const hits = queryStreetImageryFeatures(event)
|
|
|
52
53
|
**Mapillary:** `createStreetImageryConfig({ mapillaryToken })` or `setStreetImageryConfig` at boot (`@osm-editor-kit/street-imagery`). **Panoramax + Vite:** see `app/vite.config.ts` for the consuming-app setup.
|
|
53
54
|
|
|
54
55
|
Also: `useAllProviderPhotos`, `useProviderPhotos` / `useProviderSequences` / `useProviderMapFeatures`, `usePhotoThumbnails`, `resolveSelectedSequence`, `StreetLevelImageryViewCone`, `useViewerBearing` / `useViewerActions`, `MapillaryPanel`, `PanoramaxPanel`.
|
|
56
|
+
|
|
57
|
+
## Viewpoints and floating viewer
|
|
58
|
+
|
|
59
|
+
Click → viewpoints → ranked Mapillary photos per view direction → floating viewer with chips and history.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { viewpointFromPoint, viewpointsFromLine } from '@osm-editor-kit/street-imagery'
|
|
63
|
+
import {
|
|
64
|
+
FloatingPhotoViewer,
|
|
65
|
+
getViewpointSession,
|
|
66
|
+
useViewpoints,
|
|
67
|
+
useViewSuggestions,
|
|
68
|
+
ViewpointLayer,
|
|
69
|
+
VIEWPOINT_DIRECTION_LAYER_ID,
|
|
70
|
+
viewDirectionKeyFromFeatures,
|
|
71
|
+
} from '@osm-editor-kit/street-imagery-react'
|
|
72
|
+
|
|
73
|
+
// On map click (add VIEWPOINT_DIRECTION_LAYER_ID to interactiveLayerIds):
|
|
74
|
+
getViewpointSession().actions.open({ viewpoints: [viewpointFromPoint([lng, lat])] })
|
|
75
|
+
// or for a clicked line: open({ viewpoints: viewpointsFromLine(line, [lng, lat]), line })
|
|
76
|
+
|
|
77
|
+
const viewpoints = useViewpoints()
|
|
78
|
+
const { suggestions } = useViewSuggestions(viewpoints, { maxAgeYears: 2 })
|
|
79
|
+
// <ViewpointLayer viewpoints={viewpoints} suggestions={suggestions} activeDirectionKey={…} zoom={zoom} />
|
|
80
|
+
// <FloatingPhotoViewer suggestions={suggestions} … >{viewer}</FloatingPhotoViewer>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Render `FloatingPhotoViewer` inside a positioned map container. Photos from suggestions open in
|
|
84
|
+
`StreetLevelImageryViewer` with `lookAtBearing={suggestion.direction.bearing}`.
|
|
85
|
+
|
|
86
|
+
**Tailwind:** components use Tailwind classes. Tailwind does not scan `node_modules`, so add to your CSS:
|
|
87
|
+
|
|
88
|
+
```css
|
|
89
|
+
@source '../node_modules/@osm-editor-kit/street-imagery-react';
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Look at a place, outline detections
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { JUNCTION_DETECTION_GROUPS, matchesAnyGroup, PLACE_TARGET } from '@osm-editor-kit/street-imagery'
|
|
96
|
+
import {
|
|
97
|
+
StreetLevelImageryViewer,
|
|
98
|
+
useMapillaryImageDetections,
|
|
99
|
+
useMapillaryMapFeatureImages,
|
|
100
|
+
useMapillaryMapFeaturesNear,
|
|
101
|
+
} from '@osm-editor-kit/street-imagery-react'
|
|
102
|
+
|
|
103
|
+
// Turn any Mapillary photo (360° or flat) towards a junction:
|
|
104
|
+
<StreetLevelImageryViewer photo={photo} lookAt={{ lngLat: node, shape: PLACE_TARGET }} … />
|
|
105
|
+
|
|
106
|
+
// Turn to a detected sign or object and outline it. `value` lets the viewer find the image's own
|
|
107
|
+
// detection when Mapillary linked no outline for this image:
|
|
108
|
+
const { data } = useMapillaryMapFeatureImages(featureId)
|
|
109
|
+
<StreetLevelImageryViewer photo={…} lookAt={{ lngLat: data.feature.lngLat, value: data.feature.value }} … />
|
|
110
|
+
|
|
111
|
+
// Features around a point, and only the outlines you care about in the shown image:
|
|
112
|
+
const { data: features } = useMapillaryMapFeaturesNear(node, { radiusMeters: 40 })
|
|
113
|
+
const { data: detections } = useMapillaryImageDetections(photo.photoId, {
|
|
114
|
+
filter: matchesAnyGroup(JUNCTION_DETECTION_GROUPS),
|
|
115
|
+
filterKey: 'junction',
|
|
116
|
+
})
|
|
117
|
+
<StreetLevelImageryViewer photo={photo} outlines={detections?.map((d) => ({ id: d.id, outline: d.outline }))} … />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- Map layers: `filter.mapFeatureValue` limits which signs/objects are drawn (`matchesAnyGroup(SIGN_GROUPS)`).
|
|
121
|
+
- Suggested views: `useViewSuggestions(viewpoints, { sources: [mapillaryPhotoSource, streetViewPhotoSource] })`.
|
|
122
|
+
- `turnMapillaryViewerTo` / `setMapillaryViewerOutlines` work on a mapillary-js `Viewer` you own.
|
|
@@ -6,6 +6,11 @@ import type { DataDrivenPropertyValueSpecification } from 'maplibre-gl';
|
|
|
6
6
|
export type PhotoFilter = {
|
|
7
7
|
photoTypes?: PhotoTypeFilter[];
|
|
8
8
|
date?: DateRange;
|
|
9
|
+
/**
|
|
10
|
+
* Which map features (signs, objects) to draw, by Mapillary value. Use `matchesAnyGroup(...)`
|
|
11
|
+
* for groups like traffic lights or bike signs. Keep the function identity stable.
|
|
12
|
+
*/
|
|
13
|
+
mapFeatureValue?: (value: string) => boolean;
|
|
9
14
|
};
|
|
10
15
|
export type StreetLevelImagerySourcesAndLayersProps = {
|
|
11
16
|
providers: ProviderId[];
|
|
@@ -15,7 +20,7 @@ export type StreetLevelImagerySourcesAndLayersProps = {
|
|
|
15
20
|
options: {
|
|
16
21
|
config?: StreetImageryConfig;
|
|
17
22
|
showSequences?: boolean;
|
|
18
|
-
/**
|
|
23
|
+
/** Heading wedges / 360° disks per photo, from zoom 16, capped for dense areas. Default true. */
|
|
19
24
|
showViewfields?: boolean;
|
|
20
25
|
showViewCone?: boolean;
|
|
21
26
|
showSelectionHighlight?: boolean;
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { NormalizedPhoto } from '@osm-editor-kit/street-imagery';
|
|
2
|
+
import type { MapillaryLookAt, MapillaryViewerOutline } from './panels/mapillaryLookAt';
|
|
2
3
|
import type { StreetImageryPhotoSelection } from './types';
|
|
3
4
|
type StreetLevelImageryViewerProps = {
|
|
4
5
|
photo: NormalizedPhoto;
|
|
5
6
|
groupPhotos: NormalizedPhoto[];
|
|
6
7
|
onPhotoSelected: (selection: StreetImageryPhotoSelection) => void;
|
|
7
8
|
onEaseMapToPoint: (lng: number, lat: number) => void;
|
|
9
|
+
/** Mapillary only: full photo data for each image the viewer shows. */
|
|
10
|
+
onViewerPhoto?: (photo: NormalizedPhoto) => void;
|
|
11
|
+
/** Mapillary 360° photos: initial view bearing (e.g. a suggested view direction). */
|
|
12
|
+
lookAtBearing?: number | null;
|
|
13
|
+
/** Mapillary: turn and zoom to a place (sign, traffic light, junction); wins over `lookAtBearing`. */
|
|
14
|
+
lookAt?: MapillaryLookAt | null;
|
|
15
|
+
/** Mapillary: outlines to draw in the shown image. */
|
|
16
|
+
outlines?: MapillaryViewerOutline[];
|
|
8
17
|
};
|
|
9
|
-
export declare const StreetLevelImageryViewer: ({ photo, groupPhotos, onPhotoSelected, onEaseMapToPoint, }: StreetLevelImageryViewerProps) => import("react").JSX.Element | null;
|
|
18
|
+
export declare const StreetLevelImageryViewer: ({ photo, groupPhotos, onPhotoSelected, onEaseMapToPoint, onViewerPhoto, lookAtBearing, lookAt, outlines, }: StreetLevelImageryViewerProps) => import("react").JSX.Element | null;
|
|
10
19
|
export {};
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { useViewerActions } from './chunk-25UGGXIE.js';
|
|
2
|
+
import { viewFromOutline, SIGN_TARGET, viewFromFlatCamera, cameraPitch, panoX, bearingDeg, viewFromLocation, getStreetImageryConfig, fetchImageDetections } from '@osm-editor-kit/street-imagery';
|
|
3
|
+
import 'mapillary-js/dist/mapillary.css';
|
|
4
|
+
import { OutlineTag, PolygonGeometry, Viewer } from 'mapillary-js';
|
|
5
|
+
import { useRef, useEffectEvent, useEffect } from 'react';
|
|
6
|
+
import { jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
var isSpherical = (image) => image.cameraType === "spherical" || image.cameraType === "equirectangular";
|
|
9
|
+
var mapillaryViewFor = async (viewer, image, { lngLat, shape = SIGN_TARGET, outline }) => {
|
|
10
|
+
const spherical = isSpherical(image);
|
|
11
|
+
if (outline?.length) {
|
|
12
|
+
return viewFromOutline(outline, spherical, shape.maxZoom);
|
|
13
|
+
}
|
|
14
|
+
const position = image.lngLat ?? image.originalLngLat;
|
|
15
|
+
const imageLngLat = [position.lng, position.lat];
|
|
16
|
+
const compass = image.computedCompassAngle ?? image.compassAngle;
|
|
17
|
+
if (!spherical) {
|
|
18
|
+
const focal = image.cameraParameters?.[0];
|
|
19
|
+
return focal ? viewFromFlatCamera(
|
|
20
|
+
{
|
|
21
|
+
focal,
|
|
22
|
+
width: image.width,
|
|
23
|
+
height: image.height,
|
|
24
|
+
compassAngle: compass,
|
|
25
|
+
pitch: cameraPitch(image.rotation)
|
|
26
|
+
},
|
|
27
|
+
imageLngLat,
|
|
28
|
+
lngLat,
|
|
29
|
+
shape
|
|
30
|
+
) : void 0;
|
|
31
|
+
}
|
|
32
|
+
let x = panoX(bearingDeg(imageLngLat, lngLat), compass);
|
|
33
|
+
try {
|
|
34
|
+
viewer.setCenter([x, 0.5]);
|
|
35
|
+
const pixel = await viewer.project({ lng: lngLat[0], lat: lngLat[1] });
|
|
36
|
+
const basic = pixel ? await viewer.unprojectToBasic(pixel) : null;
|
|
37
|
+
const projected = basic?.[0];
|
|
38
|
+
if (projected != null && projected >= 0 && projected <= 1) {
|
|
39
|
+
x = projected;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
return viewFromLocation(x, { lngLat: imageLngLat, isPano: true }, lngLat, shape);
|
|
44
|
+
};
|
|
45
|
+
var xDistance = (a, b, spherical) => {
|
|
46
|
+
const distance = Math.abs(a - b);
|
|
47
|
+
return spherical ? Math.min(distance, 1 - distance) : distance;
|
|
48
|
+
};
|
|
49
|
+
var findOwnDetection = async (image, value, estimate) => {
|
|
50
|
+
const spherical = isSpherical(image);
|
|
51
|
+
const candidates = await fetchImageDetections(image.id, { filter: (v) => v === value }).catch(
|
|
52
|
+
() => []
|
|
53
|
+
);
|
|
54
|
+
const centerX = (detection) => viewFromOutline(detection.outline, spherical)?.center[0] ?? 0.5;
|
|
55
|
+
return candidates.sort(
|
|
56
|
+
(a, b) => estimate ? xDistance(centerX(a), estimate.center[0], spherical) - xDistance(centerX(b), estimate.center[0], spherical) : 0
|
|
57
|
+
)[0];
|
|
58
|
+
};
|
|
59
|
+
var turnMapillaryViewerTo = async (viewer, image, lookAt) => {
|
|
60
|
+
let outline = lookAt.outline?.length ? lookAt.outline : void 0;
|
|
61
|
+
let outlineId = `look-at-${image.id}`;
|
|
62
|
+
let view;
|
|
63
|
+
if (!outline) {
|
|
64
|
+
view = await mapillaryViewFor(viewer, image, lookAt);
|
|
65
|
+
const own = lookAt.value ? await findOwnDetection(image, lookAt.value, view) : void 0;
|
|
66
|
+
if (own) {
|
|
67
|
+
outline = own.outline;
|
|
68
|
+
outlineId = own.id;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (outline) {
|
|
72
|
+
view = viewFromOutline(outline, isSpherical(image), (lookAt.shape ?? SIGN_TARGET).maxZoom);
|
|
73
|
+
}
|
|
74
|
+
if (!view) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
viewer.setCenter(view.center);
|
|
78
|
+
viewer.setZoom(view.zoom);
|
|
79
|
+
return outline ? { id: outlineId, outline, label: lookAt.label } : null;
|
|
80
|
+
};
|
|
81
|
+
var setMapillaryViewerOutlines = (viewer, outlines) => {
|
|
82
|
+
const tags = viewer.getComponent("tag");
|
|
83
|
+
tags.removeAll();
|
|
84
|
+
tags.add(
|
|
85
|
+
outlines.filter(({ outline }) => outline.length >= 3).map(({ id, outline, label, color = 16776960 }) => {
|
|
86
|
+
const first = outline[0];
|
|
87
|
+
const last = outline[outline.length - 1];
|
|
88
|
+
const closed = first[0] === last[0] && first[1] === last[1] ? outline : [...outline, first];
|
|
89
|
+
return new OutlineTag(id, new PolygonGeometry(closed), {
|
|
90
|
+
text: label,
|
|
91
|
+
textColor: color,
|
|
92
|
+
lineColor: color,
|
|
93
|
+
lineWidth: 2,
|
|
94
|
+
fillColor: color,
|
|
95
|
+
fillOpacity: 0.3
|
|
96
|
+
});
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
var panoCenterX = (bearing, compassAngle) => ((0.5 + (bearing - compassAngle) / 360) % 1 + 1) % 1;
|
|
101
|
+
var MapillaryPanel = ({
|
|
102
|
+
photo,
|
|
103
|
+
onPhotoSelected,
|
|
104
|
+
onEaseMapToPoint,
|
|
105
|
+
onViewerPhoto,
|
|
106
|
+
lookAtBearing,
|
|
107
|
+
lookAt,
|
|
108
|
+
outlines
|
|
109
|
+
}) => {
|
|
110
|
+
const containerRef = useRef(null);
|
|
111
|
+
const viewerRef = useRef(null);
|
|
112
|
+
const navigableRef = useRef(false);
|
|
113
|
+
const pendingImageIdRef = useRef(null);
|
|
114
|
+
const lastViewerPhotoIdRef = useRef(null);
|
|
115
|
+
const bearingRafRef = useRef(null);
|
|
116
|
+
const pendingBearingRef = useRef(null);
|
|
117
|
+
const actions = useViewerActions();
|
|
118
|
+
const initialPhotoIdRef = useRef(photo.photoId);
|
|
119
|
+
const emitPhotoSelected = useEffectEvent(onPhotoSelected);
|
|
120
|
+
const emitEaseMapToPoint = useEffectEvent(onEaseMapToPoint);
|
|
121
|
+
const turnToWantedBearing = useEffectEvent((viewer, image) => {
|
|
122
|
+
if (image.id !== photo.photoId) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (lookAt) {
|
|
126
|
+
void turnMapillaryViewerTo(viewer, image, lookAt).then((aimedAt) => {
|
|
127
|
+
if (aimedAt && !outlines && lastViewerPhotoIdRef.current === image.id) {
|
|
128
|
+
setMapillaryViewerOutlines(viewer, [aimedAt]);
|
|
129
|
+
}
|
|
130
|
+
}).catch(() => {
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const spherical = image.cameraType === "spherical" || image.cameraType === "equirectangular";
|
|
135
|
+
const compass = image.computedCompassAngle ?? image.compassAngle;
|
|
136
|
+
if (lookAtBearing != null && spherical && compass != null) {
|
|
137
|
+
viewer.setCenter([panoCenterX(lookAtBearing, compass), 0.5]);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const drawOutlines = useEffectEvent((viewer) => {
|
|
141
|
+
setMapillaryViewerOutlines(viewer, outlines ?? []);
|
|
142
|
+
});
|
|
143
|
+
const emitViewerPhoto = useEffectEvent(
|
|
144
|
+
(viewerPhoto) => onViewerPhoto?.(viewerPhoto)
|
|
145
|
+
);
|
|
146
|
+
useEffect(
|
|
147
|
+
function resetViewerStoreOnProviderChange() {
|
|
148
|
+
return () => {
|
|
149
|
+
actions.reset();
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
[actions, photo.providerId]
|
|
153
|
+
);
|
|
154
|
+
useEffect(
|
|
155
|
+
function mountMapillaryViewer() {
|
|
156
|
+
const container = containerRef.current;
|
|
157
|
+
if (!container) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const viewer = new Viewer({
|
|
161
|
+
accessToken: getStreetImageryConfig().mapillaryToken,
|
|
162
|
+
container,
|
|
163
|
+
imageId: initialPhotoIdRef.current,
|
|
164
|
+
component: {
|
|
165
|
+
cover: false,
|
|
166
|
+
sequence: { visible: true },
|
|
167
|
+
tag: true
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
viewerRef.current = viewer;
|
|
171
|
+
lastViewerPhotoIdRef.current = initialPhotoIdRef.current;
|
|
172
|
+
const flushPendingMove = () => {
|
|
173
|
+
const pendingId = pendingImageIdRef.current;
|
|
174
|
+
if (!pendingId) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
pendingImageIdRef.current = null;
|
|
178
|
+
void viewer.moveTo(pendingId).catch(() => {
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
const onNavigable = (event) => {
|
|
182
|
+
navigableRef.current = event.navigable;
|
|
183
|
+
if (event.navigable) {
|
|
184
|
+
flushPendingMove();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const onImage = (event) => {
|
|
188
|
+
const { image } = event;
|
|
189
|
+
lastViewerPhotoIdRef.current = image.id;
|
|
190
|
+
turnToWantedBearing(viewer, image);
|
|
191
|
+
drawOutlines(viewer);
|
|
192
|
+
emitPhotoSelected({
|
|
193
|
+
provider: "mapillary",
|
|
194
|
+
sequenceId: image.sequenceId,
|
|
195
|
+
photoId: image.id
|
|
196
|
+
});
|
|
197
|
+
const position = image.lngLat ?? image.originalLngLat;
|
|
198
|
+
if (!position) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
emitViewerPhoto({
|
|
202
|
+
providerId: "mapillary",
|
|
203
|
+
photoId: image.id,
|
|
204
|
+
sequenceId: image.sequenceId,
|
|
205
|
+
capturedAt: image.capturedAt,
|
|
206
|
+
isPano: image.cameraType === "spherical" || image.cameraType === "equirectangular",
|
|
207
|
+
heading: image.computedCompassAngle ?? image.compassAngle,
|
|
208
|
+
lngLat: [position.lng, position.lat]
|
|
209
|
+
});
|
|
210
|
+
actions.setPov({ lngLat: [position.lng, position.lat] });
|
|
211
|
+
emitEaseMapToPoint(position.lng, position.lat);
|
|
212
|
+
};
|
|
213
|
+
const flushBearing = () => {
|
|
214
|
+
bearingRafRef.current = null;
|
|
215
|
+
if (pendingBearingRef.current != null) {
|
|
216
|
+
actions.setPov({ bearing: pendingBearingRef.current });
|
|
217
|
+
pendingBearingRef.current = null;
|
|
218
|
+
}
|
|
219
|
+
void viewer.getFieldOfView().then((fov) => {
|
|
220
|
+
actions.setPov({ hfov: fov });
|
|
221
|
+
}).catch(() => {
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
const onBearing = (event) => {
|
|
225
|
+
pendingBearingRef.current = event.bearing;
|
|
226
|
+
if (bearingRafRef.current == null) {
|
|
227
|
+
bearingRafRef.current = requestAnimationFrame(flushBearing);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
viewer.on("navigable", onNavigable);
|
|
231
|
+
viewer.on("image", onImage);
|
|
232
|
+
viewer.on("bearing", onBearing);
|
|
233
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
234
|
+
viewer.resize();
|
|
235
|
+
});
|
|
236
|
+
resizeObserver.observe(container);
|
|
237
|
+
return () => {
|
|
238
|
+
if (bearingRafRef.current != null) {
|
|
239
|
+
cancelAnimationFrame(bearingRafRef.current);
|
|
240
|
+
bearingRafRef.current = null;
|
|
241
|
+
}
|
|
242
|
+
resizeObserver.disconnect();
|
|
243
|
+
viewer.off("navigable", onNavigable);
|
|
244
|
+
viewer.off("image", onImage);
|
|
245
|
+
viewer.off("bearing", onBearing);
|
|
246
|
+
viewer.remove();
|
|
247
|
+
viewerRef.current = null;
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
[actions]
|
|
251
|
+
);
|
|
252
|
+
useEffect(
|
|
253
|
+
function syncExternalPhotoSelection() {
|
|
254
|
+
const viewer = viewerRef.current;
|
|
255
|
+
if (!viewer || photo.photoId === lastViewerPhotoIdRef.current) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
actions.reset();
|
|
259
|
+
lastViewerPhotoIdRef.current = photo.photoId;
|
|
260
|
+
if (navigableRef.current) {
|
|
261
|
+
void viewer.moveTo(photo.photoId).catch(() => {
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
pendingImageIdRef.current = photo.photoId;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
[actions, photo.photoId]
|
|
268
|
+
);
|
|
269
|
+
const lookAtKey = lookAt ? `${lookAt.lngLat.join(",")}|${lookAt.outline?.length ?? 0}|${lookAt.value ?? ""}` : String(lookAtBearing);
|
|
270
|
+
useEffect(
|
|
271
|
+
function turnToTargetOnChange() {
|
|
272
|
+
const viewer = viewerRef.current;
|
|
273
|
+
if (!viewer || lastViewerPhotoIdRef.current !== photo.photoId) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
void viewer.getImage().then((image) => turnToWantedBearing(viewer, image)).catch(() => {
|
|
277
|
+
});
|
|
278
|
+
},
|
|
279
|
+
[lookAtKey, photo.photoId]
|
|
280
|
+
);
|
|
281
|
+
useEffect(
|
|
282
|
+
function redrawOutlinesOnChange() {
|
|
283
|
+
const viewer = viewerRef.current;
|
|
284
|
+
if (viewer && navigableRef.current) {
|
|
285
|
+
drawOutlines(viewer);
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
[outlines]
|
|
289
|
+
);
|
|
290
|
+
return /* @__PURE__ */ jsx(
|
|
291
|
+
"div",
|
|
292
|
+
{
|
|
293
|
+
ref: containerRef,
|
|
294
|
+
className: "min-h-48 overflow-hidden rounded-lg border border-slate-200 bg-slate-900",
|
|
295
|
+
style: { aspectRatio: "4 / 3" }
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export { MapillaryPanel, mapillaryViewFor, setMapillaryViewerOutlines, turnMapillaryViewerTo };
|
package/dist/index.d.ts
CHANGED
|
@@ -12,3 +12,10 @@ export * from './hooks/usePhotoThumbnails';
|
|
|
12
12
|
export { MapillaryPanel } from './panels/MapillaryPanel';
|
|
13
13
|
export { PanoramaxPanel } from './panels/PanoramaxPanel';
|
|
14
14
|
export * from './useViewerStore';
|
|
15
|
+
export { FloatingPhotoViewer, type FloatingPhotoViewerProps, } from './viewpoints/FloatingPhotoViewer';
|
|
16
|
+
export { VIEWPOINT_DEFAULT_COLOR, VIEWPOINT_DIRECTION_LAYER_ID, ViewpointLayer, viewDirectionKeyFromFeatures, type ViewpointLayerProps, } from './viewpoints/ViewpointLayer';
|
|
17
|
+
export { viewpointRoleLabel, viewSuggestionLabel } from './viewpoints/viewDirectionLabels';
|
|
18
|
+
export * from './viewpoints/useViewpointSessionStore';
|
|
19
|
+
export * from './viewpoints/useViewSuggestions';
|
|
20
|
+
export { mapillaryViewFor, setMapillaryViewerOutlines, turnMapillaryViewerTo, type MapillaryLookAt, type MapillaryViewerOutline, } from './panels/mapillaryLookAt';
|
|
21
|
+
export * from './mapillary/useMapillaryFeatures';
|