Source StreetView
Source StreetView
Cette page est générée automatiquement à partir du dépôt local au moment de la génération de la documentation.
Fichiers inclus
packages/common/src/lib/widgets/streetview/streetview.declaration.tspackages/common/src/lib/widgets/streetview/streetview.config.tspackages/common/src/lib/widgets/streetview/streetview.models.tspackages/common/src/lib/widgets/streetview/StreetView.svelte
packages/common/src/lib/widgets/streetview/streetview.declaration.ts
import { widgetFactorySvelte, type WidgetProps } from '$lib/api/managers/widget';import { type StreetviewConfig, streetViewConfigSchema } from '$lib/widgets/streetview/streetview.config';import type { WidgetDeclaration } from '$lib/api/managers/widget/widget-declaration';
export const declaration = { factory: () => import('./StreetView.svelte').then((StreetView) => widgetFactorySvelte(StreetView)), schema: () => streetViewConfigSchema,} satisfies WidgetDeclaration;
export type StreetViewProps = WidgetProps<StreetviewConfig>;packages/common/src/lib/widgets/streetview/streetview.config.ts
import { defineWidgetConfig } from '$lib/api/managers/configuration/models/widget/widget-configuration.schema';import { inToolbarSchemaFrom } from '$lib/api/managers/configuration/models/widget/widget-in-toolbar.schema';import { z } from 'zod';import { type I18nRegistry, i18nSchemaFrom } from '$lib/api/managers/i18n';
const streetViewerTranslations = { 'confirm-message': { fr: 'Conformément aux conditions d\'utilisation de Google, cet outil activera le fond de plan Google. Le fond de plan actuel sera réactivé lorsque vous quitterez "StreetView".', nl: 'NL - Conformément aux conditions d\'utilisation de Google, cet outil activera le fond de plan Google. Le fond de plan actuel sera réactivé lorsque vous quitterez "StreetView".', },} satisfies I18nRegistry;
export const streetViewConfigSchema = defineWidgetConfig({ title: { fr: 'Street View', nl: 'NL - Street View', }, icon: { geoviewer: 'street-view', }, inToolbar: inToolbarSchemaFrom({ type: 'button', }), onActivate: { deactivate: { classes: ['BasemapChooser'], }, }, i18n: i18nSchemaFrom(streetViewerTranslations), config: z .object({ apiKey: z.string().optional().default(''), googleBaseMapId: z.string().optional().default('GOOGLE'), closestLocationRadius: z.number().optional().default(500), initRadius: z.number().optional().default(10000), enablePinIconRotation: z.boolean().optional().default(false), pinIconUrl: z .string() .optional() .default('https://geoservices.wallonie.be/geoviewer-api/4.10/spw/widgets/images/gsv_graphic.png'), }) .prefault({}),});
export type StreetviewConfig = z.infer<typeof streetViewConfigSchema>;packages/common/src/lib/widgets/streetview/streetview.models.ts
export interface IconRendering { heading: number; x?: number; y?: number;}packages/common/src/lib/widgets/streetview/StreetView.svelte
<script lang="ts"> import { Loader as GoogleLoader } from '@googlemaps/js-api-loader'; import { getMapManager } from '$lib/api/map'; import { onDestroy, onMount } from 'svelte'; import { Projections } from '$lib/api/managers/projection'; import type { ApiPoint } from '$lib/api/geometry'; import type { Unsubscriber } from 'svelte/store'; import { initGraphicMapServiceConfiguration, MapServiceTypes } from '$lib/api/managers/configuration'; import StringUtils from '$lib/api/utils/string.utils'; import { createPicturePointSymbol } from '$lib/api/symbol'; import Loader from '$lib/components/common/Loader.svelte'; import ConfirmDialog from '$lib/components/confirm-dialog/ConfirmDialog.svelte'; import { getI18n } from '$lib/api/managers/i18n'; import { GeoviewerError } from '$lib/api/managers/error/geoviewer-error.model'; import type { ApiBasemap } from '$lib/api/mapservices'; import { Checkbox } from '$lib/components/shadcn/ui/checkbox'; import { Label } from '$lib/components/shadcn/ui/label'; import type { IconRendering } from './streetview.models'; import type { StreetViewProps } from './streetview.declaration';
let { fullConfig, reference }: StreetViewProps = $props();
const { config } = fullConfig; const i18n = getI18n(fullConfig.i18n); const mapManager = getMapManager(); const featureFactory = mapManager.tools.featureFactory; const zoom = mapManager.tools.zoom; const transform = mapManager.tools.transform; const scale = mapManager.tools.scale; const dragAndDrop = mapManager.tools.dragAndDrop; const basemapList = mapManager.basemapList; const iconRenderingState = $state<IconRendering>({ heading: 0, x: undefined, y: undefined, }); const googleLoader = new GoogleLoader({ apiKey: config.apiKey, version: 'weekly', }); const graphicMapService = mapManager.addGraphicMapService( initGraphicMapServiceConfiguration({ id: StringUtils.uuid(), label: 'StreetviewPinMapService', toc: { visible: false, }, }), ); const dragAndDropUnsubscriber = dragAndDrop.on('dragEnd', graphicMapService, (newPosition) => { updatePanoramaLocation(newPosition); }); const rememberMyChoiceLocalStorageKey = 'G5-StreetView-RememberMyChoice'; const rememberMyChoiceValue = 'true';
let panoramaInstance = $state<google.maps.StreetViewPanorama | undefined>(); let streetViewService: google.maps.StreetViewService | undefined; let panoramaAnchorElement: HTMLElement; let onClickUnsubscriber: Unsubscriber | undefined; let confirmDialogOpen = $state<boolean>(false); let previousBaseMap: ApiBasemap | undefined; let rememberMyChoice = $state<boolean>(false); let scaleAtOpening = scale.getCurrentScaleInfo();
let currentScale = 0; const scaleListenerUnsub = mapManager.tools.events.watch('SCALE', (v) => { currentScale = v; });
$effect(() => { if (iconRenderingState) { renderPin(); } });
async function onConfirm() { await switchToGoogleBaseMap(); await startStreetview(); }
async function switchToGoogleBaseMap() { if (rememberMyChoice) { localStorage.setItem(rememberMyChoiceLocalStorageKey, rememberMyChoiceValue); } previousBaseMap = basemapList.selected; const googleBaseMap = findGoogleBaseMap(); if (!googleBaseMap) { throw new GeoviewerError( `Unable to find Google basemap with ID ${config.googleBaseMapId} or any other basemap containing a Google map service`, ); } basemapList.select(googleBaseMap); }
async function startStreetview() { await initializePanorama(); onClickUnsubscriber = mapManager.tools.events.on('click', (point) => updatePanoramaLocation(point)); }
async function updatePanoramaLocation(point: ApiPoint, radius?: number) { if (!panoramaInstance || !streetViewService) return; const { x, y } = transform.transformPoint(point, Projections.WGS_1984.wkid); await streetViewService.getPanorama( { location: { lat: y, lng: x }, radius: radius ?? config.closestLocationRadius, }, (data, status) => { if (data && status === google.maps.StreetViewStatus.OK && data.location) { const closestLocation = data.location.latLng; if (closestLocation) { panoramaInstance!.setPosition(closestLocation); iconRenderingState.x = closestLocation.lng(); iconRenderingState.y = closestLocation.lat(); } } else { console.log('No Street View panorama found within the specified radius.'); } }, ); }
function renderPin() { graphicMapService.removeAll(); const { heading, x, y } = iconRenderingState; if (x === undefined || y === undefined) { return; } const symbol = createPicturePointSymbol({ source: config.pinIconUrl, rotation: config.enablePinIconRotation ? 180 + heading : 0, }); graphicMapService.addFeature( featureFactory.createPoint({ x, y, wkid: Projections.WGS_1984.wkid, symbol: symbol }), ); }
async function initializePanorama() { if (panoramaInstance) return; const { StreetViewPanorama, StreetViewService } = await googleLoader.importLibrary('streetView'); streetViewService = new StreetViewService(); const apiPoint = transform.transformPoint(mapManager.getMapCenter(), Projections.WGS_1984.wkid);
panoramaInstance = new StreetViewPanorama(panoramaAnchorElement, { position: { lat: apiPoint.y, lng: apiPoint.x }, pov: { heading: 0, pitch: 0, }, zoom: 1, }); if (panoramaAnchorElement.parentElement) { panoramaAnchorElement.parentElement.style.height = '100%'; } panoramaInstance.addListener('position_changed', () => { const newPosition = panoramaInstance!.getPosition(); if (newPosition) { const { lat, lng } = newPosition.toJSON(); if (iconRenderingState.x === undefined && iconRenderingState.y === undefined) { // At first initialization, we zoom on the pin, as it could be out of current extent zoom.zoomToPoint({ x: lng, y: lat, wkid: Projections.WGS_1984.wkid }); } iconRenderingState.x = lng; iconRenderingState.y = lat; } }); panoramaInstance.addListener('pov_changed', () => { iconRenderingState.heading = panoramaInstance!.getPov().heading; }); await updatePanoramaLocation(apiPoint, config.initRadius); if (scaleAtOpening) { scale.setScale(scaleAtOpening.scaleForDevice); } }
function closeWidget() { reference.deactivate(); }
function findGoogleBaseMap() { return ( basemapList.list.find((x) => x.id === config.googleBaseMapId) ?? basemapList.list.find((x) => x.services.some((s) => s.type === MapServiceTypes.GOOGLE)) ); }
function isGoogleBasemap() { return ( basemapList.selected?.id === config.googleBaseMapId || basemapList.selected?.services.find((x) => x.type === MapServiceTypes.GOOGLE) != undefined ); }
onMount(async () => { if (isGoogleBasemap()) { await startStreetview(); return; } const rememberMyChoice = localStorage.getItem(rememberMyChoiceLocalStorageKey); if (rememberMyChoice === rememberMyChoiceValue) { await switchToGoogleBaseMap(); await startStreetview(); } else { //FIXME: if no time out, the dialog is shown at the same time as the widget container opens, // resulting in the dialog to move on the screen at first opening setTimeout(() => { confirmDialogOpen = true; }, 1); } });
onDestroy(() => { if (onClickUnsubscriber) { onClickUnsubscriber(); } mapManager.removeMapService(graphicMapService.id); if (previousBaseMap) { mapManager.basemapList.select(previousBaseMap); scale.setScale(currentScale); } if (dragAndDropUnsubscriber) { dragAndDropUnsubscriber(); } scaleListenerUnsub(); });</script>
{#if !panoramaInstance} <Loader />{/if}<div bind:this={panoramaAnchorElement} class="gv-w-full gv-h-full"></div><ConfirmDialog onCancel={closeWidget} onClose={closeWidget} {onConfirm} title={i18n('common.warning')} message={i18n('confirm-message')} bind:open={confirmDialogOpen} confirmDataTestId="StreetView-Confirm" cancelDataTestId="StreetView-Cancel"> <div class="gv-flex gv-align-middle"> <Checkbox id="rememberMyChoice" bind:checked={rememberMyChoice} data-test-id="StreetView-RememberMe" /> <Label for="rememberMyChoice" class="gv-ml-2 gv-mt-0.5"> {i18n('common.remember')} </Label> </div></ConfirmDialog>