@osm-editor-kit/street-imagery-react 0.1.0-alpha.11 → 0.1.0-alpha.12

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.
Files changed (2) hide show
  1. package/dist/index.js +144 -79
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { useStreetImageryI18n } from './chunk-6EMFFIL2.js';
2
2
  export { STREET_IMAGERY_MESSAGES, StreetImageryLocaleProvider, useStreetImageryI18n } from './chunk-6EMFFIL2.js';
3
3
  export { useViewerActions, useViewerBearing, useViewerHfov, useViewerLngLat } from './chunk-25UGGXIE.js';
4
- import { distanceMeters, sequencesToFeatureCollection, emptyLineCollection, adapterById, providerById, providerCoversBbox, fetchedSequencesMinZoom, viewConeGeoJson, viewShapeRadiusMeters, setStreetImageryConfig, viewfieldLayerId, photoLayerId, photoTargetLayerId, featureLayerId, PROVIDER_IDS, mapFeatureMatchesDateRange, isBrowserAvailableProvider, photoMatchesFilters, resolvePhotoPanoramaUrl, photoFullUrlQueryKey, resolvePhotoThumbnailUrl, photoThumbnailQueryKey, formatLocalDateTime, formatMonth, formatRelativeDate, formatDateTooltip, mapillaryPhotoSource, buildViewSuggestions, yearsToMs, SIGN_TARGET, fetchMapFeatureImages, fetchMapillaryMapFeaturesInBbox, bboxAround, fetchImageDetections, bestTargetImage, findLocationOpener, MAP_FEATURE_COLOR, mapillaryValueName, mapillaryIconUrl, dateRangeToSliderPositions, sliderPositionsToDateRange, dateSliderBins, dateSliderPosition, yearsAgoMs, parseIsoDateStartMs, openLocationInNewTab, formatDate, isIsoDate, alignLineToPoints, coneRadiusMeters, mapFeaturesToFeatureCollection, emptyPointCollection, buildMapFeatureLayerFilter, featureSourceId, photosToFeatureCollection, photosToViewfieldsFeatureCollection, emptyPolygonCollection, buildPhotoLayerFilter, photoSourceId, viewfieldSourceId, sequenceTilesSourceId, renameExpressionProperties, sequenceTilesLayerId, sequenceSourceId, sequenceLayerId, viewfieldLineLayerId, VIEWFIELD_MIN_ZOOM } from '@osm-editor-kit/street-imagery';
4
+ import { distanceMeters, sequencesToFeatureCollection, emptyLineCollection, adapterById, providerById, providerCoversBbox, fetchedSequencesMinZoom, viewConeGeoJson, viewShapeRadiusMeters, setStreetImageryConfig, viewfieldLayerId, photoLayerId, photoTargetLayerId, featureLayerId, PROVIDER_IDS, mapFeatureMatchesDateRange, isBrowserAvailableProvider, photoFilter, resolvePhotoPanoramaUrl, photoFullUrlQueryKey, resolvePhotoThumbnailUrl, photoThumbnailQueryKey, formatLocalDateTime, formatMonth, formatRelativeDate, formatDateTooltip, mapillaryPhotoSource, buildViewSuggestions, yearsToMs, SIGN_TARGET, fetchMapFeatureImages, fetchMapillaryMapFeaturesInBbox, bboxAround, fetchImageDetections, bestTargetImage, findLocationOpener, MAP_FEATURE_COLOR, mapillaryValueName, mapillaryIconUrl, dateRangeToSliderPositions, sliderPositionsToDateRange, dateSliderBins, dateSliderPosition, yearsAgoMs, parseIsoDateStartMs, openLocationInNewTab, formatDate, isIsoDate, alignLineToPoints, coneRadiusMeters, emptyPointCollection, mapFeaturesToFeatureCollection, buildMapFeatureLayerFilter, featureSourceId, buildPhotoLayerFilter, photoSourceId, viewfieldSourceId, sequenceTilesSourceId, renameExpressionProperties, sequenceTilesLayerId, sequenceSourceId, sequenceLayerId, viewfieldLineLayerId, VIEWFIELD_MIN_ZOOM, photosToFeatureCollection, photosToViewfieldsFeatureCollection, emptyPolygonCollection } from '@osm-editor-kit/street-imagery';
5
5
  import { Source, Layer, useMap } from 'react-map-gl/maplibre';
6
6
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
7
- import { lazy, useEffect, Fragment as Fragment$1, Suspense, useState, useRef, useEffectEvent } from 'react';
7
+ import { lazy, useEffect, Fragment as Fragment$1, Suspense, useState, useRef, useEffectEvent, useMemo } from 'react';
8
8
  import { keepPreviousData, useQuery, useQueries } from '@tanstack/react-query';
9
9
  import { create } from 'zustand';
10
10
 
@@ -280,8 +280,95 @@ var AGE_SHADE = [
280
280
  ]
281
281
  ];
282
282
  var PHOTO_TARGET_RADIUS_PX = 10;
283
- var PHOTO_SORT_KEY = ["coalesce", ["get", "capturedAt"], 0];
284
- var FEATURE_SORT_KEY = ["coalesce", ["get", "lastSeenAt"], 0];
283
+ var NO_PHOTOS = [];
284
+ var NO_SEQUENCES = [];
285
+ var NO_MAP_FEATURES = [];
286
+ var bboxesOverlap = (a, b) => a[0] <= b[2] && a[2] >= b[0] && a[1] <= b[3] && a[3] >= b[1];
287
+ var sequenceBbox = ({ geometry }) => {
288
+ const lines = geometry.type === "LineString" ? [geometry.coordinates] : geometry.coordinates;
289
+ const bbox = [Infinity, Infinity, -Infinity, -Infinity];
290
+ for (const line of lines) {
291
+ for (const [lng, lat] of line) {
292
+ if (lng === void 0 || lat === void 0) continue;
293
+ bbox[0] = Math.min(bbox[0], lng);
294
+ bbox[1] = Math.min(bbox[1], lat);
295
+ bbox[2] = Math.max(bbox[2], lng);
296
+ bbox[3] = Math.max(bbox[3], lat);
297
+ }
298
+ }
299
+ return bbox;
300
+ };
301
+ var buildPhotoLayerData = ({
302
+ photos,
303
+ sequences,
304
+ bbox,
305
+ photoTypes,
306
+ date,
307
+ showPhotos,
308
+ showSequences,
309
+ viewfieldZoom
310
+ }) => {
311
+ const matchesFilters = photoFilter(photoTypes, date);
312
+ const visiblePhotos = showPhotos ? photos.filter(matchesFilters) : [];
313
+ const drawBbox = bbox ? padBbox(bbox, 0.25) : null;
314
+ const drawnPhotos = drawBbox ? visiblePhotos.filter((photo) => lngLatInBbox(photo.lngLat, drawBbox)) : [...visiblePhotos];
315
+ drawnPhotos.sort((a, b) => (a.capturedAt ?? 0) - (b.capturedAt ?? 0));
316
+ const photoCollection = showPhotos ? photosToFeatureCollection(drawnPhotos) : emptyPointCollection();
317
+ const viewfieldCollection = viewfieldZoom != null ? photosToViewfieldsFeatureCollection(visiblePhotos, viewfieldZoom, { bbox }) : emptyPolygonCollection();
318
+ const sequenceCapturedAtById = /* @__PURE__ */ new Map();
319
+ for (const photo of visiblePhotos) {
320
+ if (photo.sequenceId == null || photo.capturedAt == null) continue;
321
+ const prev = sequenceCapturedAtById.get(photo.sequenceId);
322
+ if (prev == null || photo.capturedAt > prev) {
323
+ sequenceCapturedAtById.set(photo.sequenceId, photo.capturedAt);
324
+ }
325
+ }
326
+ const filteredSequences = showSequences ? sequences.map((sequence) => {
327
+ if (sequence.capturedAt != null) return sequence;
328
+ const fromPhotos = sequenceCapturedAtById.get(sequence.sequenceId);
329
+ return fromPhotos != null ? { ...sequence, capturedAt: fromPhotos } : sequence;
330
+ }).filter((sequence) => {
331
+ const asPhoto = {
332
+ providerId: sequence.providerId,
333
+ photoId: sequence.sequenceId,
334
+ sequenceId: sequence.sequenceId,
335
+ capturedAt: sequence.capturedAt,
336
+ isPano: sequence.isPano,
337
+ heading: null,
338
+ lngLat: [0, 0]
339
+ };
340
+ return matchesFilters(asPhoto) && (!drawBbox || bboxesOverlap(sequenceBbox(sequence), drawBbox));
341
+ }) : [];
342
+ const photoLngLatsBySequence = /* @__PURE__ */ new Map();
343
+ for (const photo of photos) {
344
+ if (photo.sequenceId == null) continue;
345
+ const list = photoLngLatsBySequence.get(photo.sequenceId);
346
+ if (list) {
347
+ list.push(photo.lngLat);
348
+ } else {
349
+ photoLngLatsBySequence.set(photo.sequenceId, [photo.lngLat]);
350
+ }
351
+ }
352
+ const alignedSequences = filteredSequences.map((sequence) => {
353
+ const points = photoLngLatsBySequence.get(sequence.sequenceId);
354
+ if (!points) return sequence;
355
+ const { geometry } = sequence;
356
+ return {
357
+ ...sequence,
358
+ geometry: geometry.type === "LineString" ? {
359
+ ...geometry,
360
+ coordinates: alignLineToPoints(geometry.coordinates, points)
361
+ } : {
362
+ ...geometry,
363
+ coordinates: geometry.coordinates.map(
364
+ (line) => alignLineToPoints(line, points)
365
+ )
366
+ }
367
+ };
368
+ });
369
+ const sequenceCollection = alignedSequences.length > 0 ? sequencesToFeatureCollection(alignedSequences) : emptyLineCollection();
370
+ return { photoCollection, viewfieldCollection, sequenceCollection };
371
+ };
285
372
  var PhotoProviderLayer = ({
286
373
  providerId,
287
374
  bbox,
@@ -301,15 +388,44 @@ var PhotoProviderLayer = ({
301
388
  const photosMinZoom = Math.max(meta.minZoom, minZoom);
302
389
  const fetchedLinesMinZoom = fetchedSequencesMinZoom(providerId);
303
390
  const sequencesMinZoom = Math.max(fetchedLinesMinZoom, minZoom);
304
- const { data: photos = [] } = useProviderPhotos(providerId, bbox, zoom, { minZoom });
305
- const { data: sequences = [] } = useProviderSequences(providerId, bbox, zoom, { minZoom });
306
- const visiblePhotos = zoom >= photosMinZoom ? photos.filter((photo) => photoMatchesFilters(photo, filter?.photoTypes, filter?.date)) : [];
307
- const drawBbox = bbox ? padBbox(bbox, 0.25) : null;
308
- const photoCollection = zoom >= photosMinZoom ? photosToFeatureCollection(
309
- drawBbox ? visiblePhotos.filter((photo) => lngLatInBbox(photo.lngLat, drawBbox)) : visiblePhotos
310
- ) : emptyPointCollection();
391
+ const { data: photos = NO_PHOTOS } = useProviderPhotos(providerId, bbox, zoom, { minZoom });
392
+ const { data: sequences = NO_SEQUENCES } = useProviderSequences(providerId, bbox, zoom, {
393
+ minZoom
394
+ });
311
395
  const viewfieldsActive = showViewfields && zoom >= VIEWFIELD_MIN_ZOOM;
312
- const viewfieldCollection = viewfieldsActive ? photosToViewfieldsFeatureCollection(visiblePhotos, zoom, { bbox }) : emptyPolygonCollection();
396
+ const viewfieldZoom = viewfieldsActive ? Math.floor(zoom * 2) / 2 : null;
397
+ const showPhotos = zoom >= photosMinZoom;
398
+ const showFetchedSequences = showSequences && zoom >= sequencesMinZoom;
399
+ const [west, south, east, north] = bbox ?? [];
400
+ const photoTypesKey = filter?.photoTypes?.join(",");
401
+ const dateFrom = filter?.date?.from;
402
+ const dateTo = filter?.date?.to;
403
+ const { photoCollection, viewfieldCollection, sequenceCollection } = useMemo(
404
+ () => buildPhotoLayerData({
405
+ photos,
406
+ sequences,
407
+ bbox: west !== void 0 && south !== void 0 && east !== void 0 && north !== void 0 ? [west, south, east, north] : null,
408
+ photoTypes: photoTypesKey ? photoTypesKey.split(",") : void 0,
409
+ date: { from: dateFrom, to: dateTo },
410
+ showPhotos,
411
+ showSequences: showFetchedSequences,
412
+ viewfieldZoom
413
+ }),
414
+ [
415
+ photos,
416
+ sequences,
417
+ west,
418
+ south,
419
+ east,
420
+ north,
421
+ photoTypesKey,
422
+ dateFrom,
423
+ dateTo,
424
+ showPhotos,
425
+ showFetchedSequences,
426
+ viewfieldZoom
427
+ ]
428
+ );
313
429
  const inActiveSequence = [
314
430
  "==",
315
431
  ["get", "sequenceId"],
@@ -345,63 +461,11 @@ var PhotoProviderLayer = ({
345
461
  hasActiveSequence ? full * 0.4 : full
346
462
  ];
347
463
  const notShownPhoto = ["!=", ["get", "photoId"], shownPhotoId ?? ""];
348
- const photoFilter = buildPhotoLayerFilter(filter?.photoTypes, filter?.date);
464
+ const photoFilter3 = buildPhotoLayerFilter(filter?.photoTypes, filter?.date);
349
465
  const photoSrcId = photoSourceId(providerId);
350
466
  const viewfieldSrcId = viewfieldSourceId(providerId);
351
- const sequenceCapturedAtById = /* @__PURE__ */ new Map();
352
- for (const photo of visiblePhotos) {
353
- if (photo.sequenceId == null || photo.capturedAt == null) continue;
354
- const prev = sequenceCapturedAtById.get(photo.sequenceId);
355
- if (prev == null || photo.capturedAt > prev) {
356
- sequenceCapturedAtById.set(photo.sequenceId, photo.capturedAt);
357
- }
358
- }
359
- const filteredSequences = showSequences && zoom >= sequencesMinZoom ? sequences.map((sequence) => {
360
- if (sequence.capturedAt != null) return sequence;
361
- const fromPhotos = sequenceCapturedAtById.get(sequence.sequenceId);
362
- return fromPhotos != null ? { ...sequence, capturedAt: fromPhotos } : sequence;
363
- }).filter((sequence) => {
364
- const asPhoto = {
365
- providerId: sequence.providerId,
366
- photoId: sequence.sequenceId,
367
- sequenceId: sequence.sequenceId,
368
- capturedAt: sequence.capturedAt,
369
- isPano: sequence.isPano,
370
- heading: null,
371
- lngLat: [0, 0]
372
- };
373
- return photoMatchesFilters(asPhoto, filter?.photoTypes, filter?.date);
374
- }) : [];
375
- const photoLngLatsBySequence = /* @__PURE__ */ new Map();
376
- for (const photo of photos) {
377
- if (photo.sequenceId == null) continue;
378
- const list = photoLngLatsBySequence.get(photo.sequenceId);
379
- if (list) {
380
- list.push(photo.lngLat);
381
- } else {
382
- photoLngLatsBySequence.set(photo.sequenceId, [photo.lngLat]);
383
- }
384
- }
385
- const alignedSequences = filteredSequences.map((sequence) => {
386
- const points = photoLngLatsBySequence.get(sequence.sequenceId);
387
- if (!points) return sequence;
388
- const { geometry } = sequence;
389
- return {
390
- ...sequence,
391
- geometry: geometry.type === "LineString" ? {
392
- ...geometry,
393
- coordinates: alignLineToPoints(geometry.coordinates, points)
394
- } : {
395
- ...geometry,
396
- coordinates: geometry.coordinates.map(
397
- (line) => alignLineToPoints(line, points)
398
- )
399
- }
400
- };
401
- });
402
467
  const sequenceTiles = showSequences ? adapter?.sequenceTiles?.() : null;
403
468
  const sequenceTilesMinZoom = sequenceTiles ? Math.max(sequenceTiles.minZoom, minZoom) : 0;
404
- const sequenceCollection = alignedSequences.length > 0 ? sequencesToFeatureCollection(alignedSequences) : emptyLineCollection();
405
469
  return /* @__PURE__ */ jsxs(Fragment, { children: [
406
470
  meta.coverageTiles ? /* @__PURE__ */ jsxs(Fragment, { children: [
407
471
  /* @__PURE__ */ jsx(
@@ -448,7 +512,7 @@ var PhotoProviderLayer = ({
448
512
  "source-layer": sequenceTiles.sourceLayer,
449
513
  minzoom: sequenceTilesMinZoom,
450
514
  maxzoom: fetchedLinesMinZoom,
451
- filter: renameExpressionProperties(photoFilter, sequenceTiles.properties),
515
+ filter: renameExpressionProperties(photoFilter3, sequenceTiles.properties),
452
516
  layout: {
453
517
  "line-cap": "round",
454
518
  "line-join": "round",
@@ -556,8 +620,7 @@ var PhotoProviderLayer = ({
556
620
  id: photoTargetLayerId(providerId),
557
621
  type: "circle",
558
622
  source: photoSrcId,
559
- filter: photoFilter,
560
- layout: { "circle-sort-key": PHOTO_SORT_KEY },
623
+ filter: photoFilter3,
561
624
  paint: { "circle-radius": PHOTO_TARGET_RADIUS_PX, "circle-opacity": 0 }
562
625
  }
563
626
  ),
@@ -568,8 +631,7 @@ var PhotoProviderLayer = ({
568
631
  id: photoLayerId(providerId),
569
632
  type: "circle",
570
633
  source: photoSrcId,
571
- filter: photoFilter,
572
- layout: { "circle-sort-key": PHOTO_SORT_KEY },
634
+ filter: photoFilter3,
573
635
  paint: {
574
636
  "circle-radius": dotRadius,
575
637
  "circle-color": lineAndDotColor
@@ -588,11 +650,17 @@ var MapFeatureProviderLayer = ({
588
650
  minZoom
589
651
  }) => {
590
652
  const meta = providerById[providerId];
591
- const { data: features = [] } = useProviderMapFeatures(providerId, bbox, zoom, { minZoom });
653
+ const { data: features = NO_MAP_FEATURES } = useProviderMapFeatures(providerId, bbox, zoom, {
654
+ minZoom
655
+ });
592
656
  const valueFilter = filter?.mapFeatureValue;
593
- const featureCollection = zoom >= Math.max(meta.minZoom, minZoom) ? mapFeaturesToFeatureCollection(
594
- valueFilter ? features.filter((feature) => valueFilter(feature.value)) : features
595
- ) : emptyPointCollection();
657
+ const showFeatures = zoom >= Math.max(meta.minZoom, minZoom);
658
+ const featureCollection = useMemo(() => {
659
+ if (!showFeatures) return emptyPointCollection();
660
+ const drawn = valueFilter ? features.filter((feature) => valueFilter(feature.value)) : [...features];
661
+ drawn.sort((a, b) => (a.lastSeenAt ?? 0) - (b.lastSeenAt ?? 0));
662
+ return mapFeaturesToFeatureCollection(drawn);
663
+ }, [features, valueFilter, showFeatures]);
596
664
  const featureFilter = buildMapFeatureLayerFilter(filter?.date);
597
665
  const featureSrcId = featureSourceId(providerId);
598
666
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -614,7 +682,6 @@ var MapFeatureProviderLayer = ({
614
682
  type: "circle",
615
683
  source: featureSrcId,
616
684
  filter: featureFilter,
617
- layout: { "circle-sort-key": FEATURE_SORT_KEY },
618
685
  paint: {
619
686
  "circle-radius": FEATURE_CIRCLE_RADIUS,
620
687
  "circle-color": mapFeatureCircleColor
@@ -866,10 +933,8 @@ var useAllProviderPhotos = (providerIds, bbox, zoom, photoTypes, date) => {
866
933
  zoom
867
934
  )
868
935
  }));
869
- const photos = queries.flatMap(({ query }) => {
870
- const data = query.data ?? [];
871
- return data.filter((photo) => photoMatchesFilters(photo, photoTypes, date));
872
- });
936
+ const matchesFilters = photoFilter(photoTypes, date);
937
+ const photos = queries.flatMap(({ query }) => (query.data ?? []).filter(matchesFilters));
873
938
  const activeQueries = queries.filter(({ providerId }) => enabled.has(providerId));
874
939
  const isLoading = activeQueries.some(({ query }) => query.isLoading);
875
940
  const isFetching = activeQueries.some(({ query }) => query.isFetching);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osm-editor-kit/street-imagery-react",
3
- "version": "0.1.0-alpha.11",
3
+ "version": "0.1.0-alpha.12",
4
4
  "description": "React MapLibre layers and photo viewers for street-level imagery providers.",
5
5
  "homepage": "https://github.com/osmberlin/street-space-editor/tree/main/packages/street-imagery-react",
6
6
  "license": "MIT",
@@ -64,7 +64,7 @@
64
64
  "zustand": "^5.0.7"
65
65
  },
66
66
  "peerDependencies": {
67
- "@osm-editor-kit/street-imagery": "^0.1.0-alpha.8",
67
+ "@osm-editor-kit/street-imagery": "^0.1.0-alpha.9",
68
68
  "@panoramax/web-viewer": "^5",
69
69
  "@tanstack/react-query": "^5",
70
70
  "mapillary-js": "^4",