@osm-editor-kit/street-imagery-react 0.1.0-alpha.7 → 0.1.0-alpha.9

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
@@ -40,6 +40,8 @@ const bbox = useMapViewportBbox('main', map)
40
40
  selectedPhoto,
41
41
  photoCircleColor: '#3b82f6',
42
42
  mapFeatureCircleColor: '#94a3b8',
43
+ beforeId: 'road-label', // optional: draw everything below this layer of your style
44
+ minZoom: 10, // optional: below this zoom nothing is requested or drawn
43
45
  }}
44
46
  />
45
47
 
@@ -50,9 +52,11 @@ const hits = queryStreetImageryFeatures(event)
50
52
  onPhotoSelected={setSelection} onEaseMapToPoint={(lng, lat) => map.easeTo({ center: [lng, lat] })} />
51
53
  ```
52
54
 
55
+ **Before photos are loaded:** where a provider has own vector tiles with its track lines (Mapillary: zoom 6 to 15), MapLibre reads those itself. Same colours and filters; the lines are not clickable. Zoomed far out these tiles are several MB each in dense areas; set `options.minZoom` to skip them.
56
+
53
57
  **Mapillary:** `createStreetImageryConfig({ mapillaryToken })` or `setStreetImageryConfig` at boot (`@osm-editor-kit/street-imagery`). **Panoramax + Vite:** see `app/vite.config.ts` for the consuming-app setup.
54
58
 
55
- Also: `useAllProviderPhotos`, `useProviderPhotos` / `useProviderSequences` / `useProviderMapFeatures`, `usePhotoThumbnails`, `resolveSelectedSequence`, `StreetLevelImageryViewCone`, `useViewerBearing` / `useViewerActions`, `MapillaryPanel`, `PanoramaxPanel`.
59
+ Also: `useAllProviderPhotos`, `useProviderPhotos` / `useProviderSequences` / `useProviderMapFeatures`, `usePhotoThumbnails`, `resolveSelectedSequence`, `StreetLevelImageryViewCone`, `useViewerBearing` / `useViewerActions`. The panels themselves (`MapillaryPanel`, `PanoramaxPanel`) are in `@osm-editor-kit/street-imagery-react/viewer-panels`; `StreetLevelImageryViewer` loads them on demand, so `mapillary-js` and `@panoramax/web-viewer` are only loaded when a photo is shown.
56
60
 
57
61
  ## Viewpoints and floating viewer
58
62
 
@@ -117,9 +121,48 @@ const { data: detections } = useMapillaryImageDetections(photo.photoId, {
117
121
  <StreetLevelImageryViewer photo={photo} outlines={detections?.map((d) => ({ id: d.id, outline: d.outline }))} … />
118
122
  ```
119
123
 
120
- - Map layers: `filter.mapFeatureValue` limits which signs/objects are drawn (`matchesAnyGroup(SIGN_GROUPS)`).
124
+ - Map layers: `filter.mapFeatureValue` limits which signs/objects are drawn: `signGroupFilter(['parking', 'bike'])` for sign groups, or `matchesAnyGroup(…)`.
121
125
  - Suggested views: `useViewSuggestions(viewpoints, { sources: [mapillaryPhotoSource, streetViewPhotoSource] })`.
122
- - `turnMapillaryViewerTo` / `setMapillaryViewerOutlines` work on a mapillary-js `Viewer` you own.
126
+ - `turnMapillaryViewerTo` / `setMapillaryViewerOutlines` (from `@osm-editor-kit/street-imagery-react/viewer-panels`) work on a mapillary-js `Viewer` you own.
127
+
128
+ ## A selected sign: viewer, bar and map highlight
129
+
130
+ Keep the selected sign (`featureId`) and its shown photo in your own state or URL:
131
+
132
+ ```tsx
133
+ const { data, shownImage, firstImage } = useSelectedMapillaryFeature({
134
+ featureId,
135
+ shownPhotoId, // the photo the viewer shows now
136
+ minCapturedAt: fromMs, // optional: open the best photo from this time on
137
+ })
138
+ // Open `firstImage` first: showPhoto(targetImageToPhoto(firstImage))
139
+ <MapillaryFeatureBar data={data} shownImage={shownImage} onShow={(image) => showPhoto(targetImageToPhoto(image))} />
140
+ <StreetLevelImageryViewer lookAt={{ lngLat: data.feature.lngLat, outline: shownImage?.outline, value: data.feature.value }} … />
141
+ // On the map, after StreetLevelImagerySourcesAndLayers: the sign's halo and a line from the camera
142
+ <SelectedMapFeatureLayer data={data} shownImage={shownImage} providers={providers} bbox={bbox} zoom={zoom} />
143
+ ```
144
+
145
+ A click on a sign dot: `queryStreetImageryFeatures(event).find((hit) => hit.kind === 'mapFeature')?.featureId`.
146
+
147
+ ## Date filter with a slider
148
+
149
+ `PhotoDateRangeFilter` is the date filter of iD's photo panel as a controlled component: one slider with two handles on a scale that gives recent years more room. It works on plain `{ from?, to? }` days (`YYYY-MM-DD`), the same `DateRange` the layers take as `filter.date`.
150
+
151
+ ```tsx
152
+ const { photos } = useAllProviderPhotos(providers, bbox, zoom) // no date: all photos in view
153
+
154
+ <PhotoDateRangeFilter
155
+ value={date}
156
+ onChange={setDate} // on release of a handle, not while dragging
157
+ capturedAt={photos.map((photo) => photo.capturedAt)} // marks: where photos are
158
+ yearLines={[1, 2, 3, 4]} // lines with a label; a click shows the last N years
159
+ recommendedMaxAgeYears={2} // this line is red
160
+ markers={[{ date: '2024-04-01', label: 'Survey start' }]} // dashed lines for fixed dates
161
+ dateInputs="collapsed" // exact days: 'collapsed' (default), 'open' or 'none'
162
+ />
163
+ ```
164
+
165
+ A handle at its end means no limit, so the left end also shows photos older than the slider spans (`maxYears`, default 10). The scale itself is in the core package (`dateSliderPosition`, `dateSliderBins`, …) for a host that draws its own chart. The texts are in `messages.dateFilter`.
123
166
 
124
167
  ## Pick a place on the map to open elsewhere
125
168
 
@@ -0,0 +1,2 @@
1
+ export { MapillaryPanel } from './chunk-SGIB7AVC.js';
2
+ import './chunk-25UGGXIE.js';
@@ -0,0 +1,2 @@
1
+ export { PanoramaxPanel } from './chunk-KZTMKQT5.js';
2
+ import './chunk-25UGGXIE.js';
@@ -15,12 +15,14 @@ export type StreetLevelImagerySelectionOverlayProps = {
15
15
  cameraLngLat?: [number, number] | null;
16
16
  /** Layer to draw the sequence line below, so photo dots stay on top of it. */
17
17
  sequenceBeforeId?: string;
18
+ /** Id of a layer of the host's style to draw the whole overlay below. */
19
+ beforeId?: string;
18
20
  };
19
21
  /**
20
22
  * The shown photo on the map, bottom to top: its sequence line (only passed when the provider's
21
23
  * own layers are off; else that layer marks it), a filled marker on its photo dot, and — when the camera position differs from the
22
24
  * dot — a dashed connector and a pin at the camera position.
23
25
  */
24
- export declare const StreetLevelImagerySelectionOverlay: ({ selectedPhoto, selectedSequence, cameraLngLat, sequenceBeforeId, }: StreetLevelImagerySelectionOverlayProps) => import("react").JSX.Element | null;
26
+ export declare const StreetLevelImagerySelectionOverlay: ({ selectedPhoto, selectedSequence, cameraLngLat, sequenceBeforeId, beforeId, }: StreetLevelImagerySelectionOverlayProps) => import("react").JSX.Element | null;
25
27
  /** Resolve sequence geometry for highlight when only photo + fetched sequences are available. */
26
28
  export declare const resolveSelectedSequence: (selectedPhoto: NormalizedPhoto | null | undefined, sequences: NormalizedSequence[], sequenceId: string | null | undefined) => NormalizedSequence | null;
@@ -41,6 +41,16 @@ export type StreetLevelImagerySourcesAndLayersProps = {
41
41
  /** Length of the shown photo's view cone, as a multiple of the per-photo shapes. Default 2.5. */
42
42
  viewConeScale?: number;
43
43
  mapFeatureCircleColor: DataDrivenPropertyValueSpecification<string>;
44
+ /**
45
+ * Id of a layer of the host's style: all layers are drawn below it (e.g. below road names).
46
+ * The layer must exist when this component mounts. Default: on top of the style.
47
+ */
48
+ beforeId?: string;
49
+ /**
50
+ * Below this map zoom nothing is requested or drawn. Each provider also has its own minimum
51
+ * zooms (photos, lines); the higher one counts. Default: only the providers' own.
52
+ */
53
+ minZoom?: number;
44
54
  };
45
55
  };
46
56
  export declare const StreetLevelImagerySourcesAndLayers: ({ providers, filter, bbox, zoom, options, }: StreetLevelImagerySourcesAndLayersProps) => import("react").JSX.Element;
@@ -17,5 +17,7 @@ export type StreetLevelImageryViewConeProps = {
17
17
  color?: DataDrivenPropertyValueSpecification<string>;
18
18
  /** Cone length as a multiple of the per-photo view-direction shapes. Default 2.5. */
19
19
  scale?: number;
20
+ /** Id of a layer of the host's style to draw the cone below. */
21
+ beforeId?: string;
20
22
  };
21
- export declare const StreetLevelImageryViewCone: ({ selectedPhoto, zoom, viewerPov, color, scale, }: StreetLevelImageryViewConeProps) => import("react").JSX.Element | null;
23
+ export declare const StreetLevelImageryViewCone: ({ selectedPhoto, zoom, viewerPov, color, scale, beforeId, }: StreetLevelImageryViewConeProps) => import("react").JSX.Element | null;
@@ -18,6 +18,21 @@ var en = {
18
18
  nextPhotoOfView: (label) => `${label} \u2014 click for the next photo`
19
19
  },
20
20
  unknownDate: "Unknown date",
21
+ dateFilter: {
22
+ allDates: "All dates",
23
+ since: (date) => `Since ${date}`,
24
+ until: (date) => `Until ${date}`,
25
+ between: (from, to) => `${from} \u2013 ${to}`,
26
+ yearsShort: (years) => `${years} y`,
27
+ lastYears: (years) => years === 1 ? "Show the last year" : `Show the last ${years} years`,
28
+ recommendedMaxAge: (years) => `Recommended: photos of the last ${years === 1 ? "year" : `${years} years`}`,
29
+ fromHandle: "Oldest photos to show",
30
+ toHandle: "Newest photos to show",
31
+ exactDates: "Exact dates",
32
+ from: "From",
33
+ to: "To",
34
+ photoCount: (count) => count === 1 ? "1 photo" : `${count.toLocaleString("en")} photos`
35
+ },
21
36
  viewpointRole: {
22
37
  here: "Here",
23
38
  "line-start": "Start",
@@ -59,6 +74,21 @@ var de = {
59
74
  nextPhotoOfView: (label) => `${label} \u2014 klicken f\xFCr das n\xE4chste Foto`
60
75
  },
61
76
  unknownDate: "Datum unbekannt",
77
+ dateFilter: {
78
+ allDates: "Alle Daten",
79
+ since: (date) => `Seit ${date}`,
80
+ until: (date) => `Bis ${date}`,
81
+ between: (from, to) => `${from} \u2013 ${to}`,
82
+ yearsShort: (years) => `${years} J.`,
83
+ lastYears: (years) => years === 1 ? "Das letzte Jahr zeigen" : `Die letzten ${years} Jahre zeigen`,
84
+ recommendedMaxAge: (years) => `Empfohlen: Fotos ${years === 1 ? "des letzten Jahres" : `der letzten ${years} Jahre`}`,
85
+ fromHandle: "\xC4lteste Fotos, die gezeigt werden",
86
+ toHandle: "Neueste Fotos, die gezeigt werden",
87
+ exactDates: "Genaue Daten",
88
+ from: "Von",
89
+ to: "Bis",
90
+ photoCount: (count) => count === 1 ? "1 Foto" : `${count.toLocaleString("de")} Fotos`
91
+ },
62
92
  viewpointRole: {
63
93
  here: "Hier",
64
94
  "line-start": "Anfang",
@@ -95,6 +125,7 @@ var DEFAULT_I18N = {
95
125
  var I18nContext = createContext(DEFAULT_I18N);
96
126
  var mergeMessages = (base, overrides) => overrides ? {
97
127
  unknownDate: overrides.unknownDate ?? base.unknownDate,
128
+ dateFilter: { ...base.dateFilter, ...overrides.dateFilter },
98
129
  viewer: { ...base.viewer, ...overrides.viewer },
99
130
  viewpointRole: { ...base.viewpointRole, ...overrides.viewpointRole },
100
131
  compass: overrides.compass?.length === 8 ? overrides.compass : base.compass,
@@ -299,6 +299,7 @@ var PanoramaxPanel = ({
299
299
  className: "block h-full w-full",
300
300
  endpoint: panoramaxApiEndpoint(),
301
301
  "url-parameters": "false",
302
+ widgets: hideLegend ? "false" : void 0,
302
303
  picture: photo.photoId,
303
304
  sequence: photo.sequenceId ?? void 0
304
305
  }
@@ -211,7 +211,12 @@ var MapillaryPanel = ({
211
211
  lngLat: [position.lng, position.lat],
212
212
  ...image.creatorUsername ? { creatorName: image.creatorUsername } : {}
213
213
  });
214
- actions.setPov({ lngLat: [position.lng, position.lat] });
214
+ const isFlat = image.cameraType !== "spherical" && image.cameraType !== "equirectangular";
215
+ const compass = image.computedCompassAngle ?? image.compassAngle;
216
+ actions.setPov({
217
+ lngLat: [position.lng, position.lat],
218
+ ...isFlat && compass != null ? { bearing: compass } : {}
219
+ });
215
220
  emitEaseMapToPoint(position.lng, position.lat);
216
221
  };
217
222
  const flushBearing = () => {
@@ -0,0 +1,46 @@
1
+ import { type DateRange } from '@osm-editor-kit/street-imagery';
2
+ /** A fixed date to show in the chart, e.g. the start of a project or of a survey. */
3
+ export type PhotoDateMarker = {
4
+ /** A day as `YYYY-MM-DD`, or a time (ms). */
5
+ date: string | number;
6
+ label: string;
7
+ /** CSS color of line and label. Default red. */
8
+ color?: string;
9
+ };
10
+ export type PhotoDateRangeFilterProps = {
11
+ /** The chosen range, days as `YYYY-MM-DD`. No `from` and no `to`: all dates. */
12
+ value: DateRange;
13
+ /** Called when a handle is released, a year label is clicked or a date input changes. */
14
+ onChange: (range: DateRange) => void;
15
+ /**
16
+ * Capture times (ms) of the photos to show as marks above the slider, usually all photos in
17
+ * view before the date filter. Without it there are no marks.
18
+ */
19
+ capturedAt?: Iterable<number | null | undefined>;
20
+ /** Lines with a label at these ages in years; a click on a label shows that many years. */
21
+ yearLines?: readonly number[];
22
+ /** Its line is red; added to `yearLines` when it is not one of them. */
23
+ recommendedMaxAgeYears?: number;
24
+ markers?: readonly PhotoDateMarker[];
25
+ /** Years the slider spans. Default 10. */
26
+ maxYears?: number;
27
+ /** Reference time (ms). Default: the time of the first render. */
28
+ now?: number;
29
+ /**
30
+ * The two date inputs for exact days, in sync with the slider: folded away (default), open,
31
+ * or left out.
32
+ */
33
+ dateInputs?: 'collapsed' | 'open' | 'none';
34
+ /**
35
+ * The "All dates" action next to the chosen range, which removes both limits. Default true;
36
+ * without it the user moves both handles to the ends.
37
+ */
38
+ allDatesAction?: boolean;
39
+ className?: string;
40
+ };
41
+ /**
42
+ * Date filter for photos after iD's photo age slider: one slider with two handles on a scale that
43
+ * gives recent years more room, marks that show how many photos there are at each time, lines at
44
+ * whole years, and optional fixed markers. Controlled: `value` in, `onChange` out.
45
+ */
46
+ export declare const PhotoDateRangeFilter: ({ value, onChange, capturedAt, yearLines, recommendedMaxAgeYears, markers, maxYears, now: nowProp, dateInputs, className, allDatesAction, }: PhotoDateRangeFilterProps) => import("react").JSX.Element;
@@ -1,5 +1,10 @@
1
1
  import type { Bbox } from '@osm-editor-kit/street-imagery';
2
2
  import { type ProviderId } from '@osm-editor-kit/street-imagery';
3
- export declare const useProviderPhotos: (providerId: ProviderId, bbox: Bbox | null, zoom: number) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@osm-editor-kit/street-imagery").NormalizedPhoto[]>, Error>;
4
- export declare const useProviderSequences: (providerId: ProviderId, bbox: Bbox | null, zoom: number) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@osm-editor-kit/street-imagery").NormalizedSequence[]>, Error>;
5
- export declare const useProviderMapFeatures: (providerId: ProviderId, bbox: Bbox | null, zoom: number) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@osm-editor-kit/street-imagery").NormalizedMapFeature[]>, Error>;
3
+ /** Host limit on top of the provider's own minimum zooms: below it nothing is requested. */
4
+ type ProviderDataOptions = {
5
+ minZoom?: number;
6
+ };
7
+ export declare const useProviderPhotos: (providerId: ProviderId, bbox: Bbox | null, zoom: number, { minZoom }?: ProviderDataOptions) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@osm-editor-kit/street-imagery").NormalizedPhoto[]>, Error>;
8
+ export declare const useProviderSequences: (providerId: ProviderId, bbox: Bbox | null, zoom: number, { minZoom }?: ProviderDataOptions) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@osm-editor-kit/street-imagery").NormalizedSequence[]>, Error>;
9
+ export declare const useProviderMapFeatures: (providerId: ProviderId, bbox: Bbox | null, zoom: number, { minZoom }?: ProviderDataOptions) => import("@tanstack/react-query").UseQueryResult<NoInfer<import("@osm-editor-kit/street-imagery").NormalizedMapFeature[]>, Error>;
10
+ export {};
@@ -21,6 +21,24 @@ export type StreetImageryMessages = {
21
21
  /** Eight compass points, clockwise from north: N, NE, E, SE, S, SW, W, NW. */
22
22
  compass: readonly string[];
23
23
  unknownDate: string;
24
+ dateFilter: {
25
+ allDates: string;
26
+ /** e.g. "Since 5 Oct 2024". */
27
+ since: (date: string) => string;
28
+ until: (date: string) => string;
29
+ between: (from: string, to: string) => string;
30
+ /** Short label below a year line, e.g. "2 y". */
31
+ yearsShort: (years: number) => string;
32
+ /** Tooltip of a year label; a click shows the photos of the last `years` years. */
33
+ lastYears: (years: number) => string;
34
+ recommendedMaxAge: (years: number) => string;
35
+ fromHandle: string;
36
+ toHandle: string;
37
+ exactDates: string;
38
+ from: string;
39
+ to: string;
40
+ photoCount: (count: number) => string;
41
+ };
24
42
  /** e.g. "Start, looking along the street"; `role` is the viewpoint's label. */
25
43
  viewDirection: (role: string, kind: ViewDirection['kind']) => string;
26
44
  };
package/dist/index.d.ts CHANGED
@@ -9,21 +9,21 @@ export * from './hooks/useAllProviderPhotos';
9
9
  export * from './hooks/useMapViewportBbox';
10
10
  export * from './hooks/useProviderData';
11
11
  export * from './hooks/usePhotoThumbnails';
12
- export { MapillaryPanel } from './panels/MapillaryPanel';
13
- export { PanoramaxPanel } from './panels/PanoramaxPanel';
14
12
  export * from './useViewerStore';
15
13
  export { FloatingPhotoViewer, FloatingViewerInfoButton, type FloatingPhotoViewerProps, } from './viewpoints/FloatingPhotoViewer';
16
14
  export { VIEWPOINT_DEFAULT_COLOR, VIEWPOINT_DIRECTION_LAYER_ID, ViewpointLayer, viewDirectionKeyFromFeatures, type ViewpointLayerProps, } from './viewpoints/ViewpointLayer';
17
15
  export { viewSuggestionLabel } from './viewpoints/viewDirectionLabels';
18
16
  export * from './viewpoints/useViewpointSessionStore';
19
17
  export * from './viewpoints/useViewSuggestions';
20
- export { mapillaryViewFor, setMapillaryViewerOutlines, turnMapillaryViewerTo, type MapillaryLookAt, type MapillaryViewerOutline, } from './panels/mapillaryLookAt';
18
+ export type { MapillaryLookAt, MapillaryViewerOutline } from './panels/mapillaryLookAt';
21
19
  export * from './mapillary/useMapillaryFeatures';
22
20
  export { LocationPickOnMap, type LocationPickOnMapProps } from './openers/LocationPickOnMap';
23
21
  export * from './openers/useLocationPickStore';
22
+ export { SelectedMapFeatureLayer, type SelectedMapFeatureLayerProps, } from './mapillary/SelectedMapFeatureLayer';
24
23
  export { MapillaryFeatureBar, type MapillaryFeatureBarProps } from './mapillary/MapillaryFeatureBar';
25
24
  export { MAPILLARY_FEATURE_BAR_LABELS, type MapillaryFeatureBarLabels, } from './mapillary/featureBarLabels';
26
25
  export { STREET_IMAGERY_MESSAGES, type StreetImageryMessages } from './i18n/messages';
27
26
  export { StreetImageryLocaleProvider, useStreetImageryI18n, type StreetImageryMessageOverrides, } from './i18n/StreetImageryLocaleProvider';
27
+ export { PhotoDateRangeFilter, type PhotoDateMarker, type PhotoDateRangeFilterProps, } from './filters/PhotoDateRangeFilter';
28
28
  export { PhotoDate } from './i18n/PhotoDate';
29
29
  export { PhotoCount } from './i18n/PhotoCount';