mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-12-03 10:22:13 +00:00
progress
This commit is contained in:
33
website/src/lib/components/map/street-view-control/Google.ts
Normal file
33
website/src/lib/components/map/street-view-control/Google.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { resetCursor, setCrosshairCursor } from '$lib/utils';
|
||||
import type mapboxgl from 'mapbox-gl';
|
||||
|
||||
export class GoogleRedirect {
|
||||
map: mapboxgl.Map;
|
||||
enabled = false;
|
||||
|
||||
constructor(map: mapboxgl.Map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
add() {
|
||||
if (this.enabled) return;
|
||||
|
||||
this.enabled = true;
|
||||
setCrosshairCursor();
|
||||
this.map.on('click', this.openStreetView);
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.enabled = false;
|
||||
resetCursor();
|
||||
this.map.off('click', this.openStreetView);
|
||||
}
|
||||
|
||||
openStreetView(e) {
|
||||
window.open(
|
||||
`https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${e.lngLat.lat},${e.lngLat.lng}`
|
||||
);
|
||||
}
|
||||
}
|
||||
149
website/src/lib/components/map/street-view-control/Mapillary.ts
Normal file
149
website/src/lib/components/map/street-view-control/Mapillary.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import mapboxgl, { type LayerSpecification, type VectorSourceSpecification } from 'mapbox-gl';
|
||||
import { Viewer, type ViewerBearingEvent } from 'mapillary-js/dist/mapillary.module';
|
||||
import 'mapillary-js/dist/mapillary.css';
|
||||
import { resetCursor, setPointerCursor } from '$lib/utils';
|
||||
|
||||
const mapillarySource: VectorSourceSpecification = {
|
||||
type: 'vector',
|
||||
tiles: [
|
||||
'https://tiles.mapillary.com/maps/vtp/mly1_computed_public/2/{z}/{x}/{y}?access_token=MLY|4381405525255083|3204871ec181638c3c31320490f03011',
|
||||
],
|
||||
minzoom: 6,
|
||||
maxzoom: 14,
|
||||
};
|
||||
|
||||
const mapillarySequenceLayer: LayerSpecification = {
|
||||
id: 'mapillary-sequence',
|
||||
type: 'line',
|
||||
source: 'mapillary',
|
||||
'source-layer': 'sequence',
|
||||
paint: {
|
||||
'line-color': 'rgb(0, 150, 70)',
|
||||
'line-opacity': 0.7,
|
||||
'line-width': 5,
|
||||
},
|
||||
layout: {
|
||||
'line-cap': 'round',
|
||||
'line-join': 'round',
|
||||
},
|
||||
};
|
||||
|
||||
const mapillaryImageLayer: LayerSpecification = {
|
||||
id: 'mapillary-image',
|
||||
type: 'circle',
|
||||
source: 'mapillary',
|
||||
'source-layer': 'image',
|
||||
paint: {
|
||||
'circle-color': 'rgb(0, 150, 70)',
|
||||
'circle-radius': 5,
|
||||
'circle-opacity': 0.7,
|
||||
},
|
||||
};
|
||||
|
||||
export class MapillaryLayer {
|
||||
map: mapboxgl.Map;
|
||||
marker: mapboxgl.Marker;
|
||||
viewer: Viewer;
|
||||
|
||||
active = false;
|
||||
popupOpen: { value: boolean };
|
||||
|
||||
addBinded = this.add.bind(this);
|
||||
onMouseEnterBinded = this.onMouseEnter.bind(this);
|
||||
onMouseLeaveBinded = this.onMouseLeave.bind(this);
|
||||
|
||||
constructor(map: mapboxgl.Map, container: HTMLElement, popupOpen: { value: boolean }) {
|
||||
this.map = map;
|
||||
|
||||
this.viewer = new Viewer({
|
||||
accessToken: 'MLY|4381405525255083|3204871ec181638c3c31320490f03011',
|
||||
container,
|
||||
});
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.className = 'mapboxgl-user-location mapboxgl-user-location-show-heading';
|
||||
const dot = document.createElement('div');
|
||||
dot.className = 'mapboxgl-user-location-dot';
|
||||
const heading = document.createElement('div');
|
||||
heading.className = 'mapboxgl-user-location-heading';
|
||||
element.appendChild(dot);
|
||||
element.appendChild(heading);
|
||||
|
||||
this.marker = new mapboxgl.Marker({
|
||||
rotationAlignment: 'map',
|
||||
element,
|
||||
});
|
||||
|
||||
this.viewer.on('position', async () => {
|
||||
if (this.active) {
|
||||
popupOpen.value = true;
|
||||
let latLng = await this.viewer.getPosition();
|
||||
this.marker.setLngLat(latLng).addTo(this.map);
|
||||
if (!this.map.getBounds()?.contains(latLng)) {
|
||||
this.map.panTo(latLng);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.viewer.on('bearing', (e: ViewerBearingEvent) => {
|
||||
if (this.active) {
|
||||
this.marker.setRotation(e.bearing);
|
||||
}
|
||||
});
|
||||
|
||||
this.popupOpen = popupOpen;
|
||||
}
|
||||
|
||||
add() {
|
||||
if (!this.map.getSource('mapillary')) {
|
||||
this.map.addSource('mapillary', mapillarySource);
|
||||
}
|
||||
if (!this.map.getLayer('mapillary-sequence')) {
|
||||
this.map.addLayer(mapillarySequenceLayer);
|
||||
}
|
||||
if (!this.map.getLayer('mapillary-image')) {
|
||||
this.map.addLayer(mapillaryImageLayer);
|
||||
}
|
||||
this.map.on('style.load', this.addBinded);
|
||||
this.map.on('mouseenter', 'mapillary-image', this.onMouseEnterBinded);
|
||||
this.map.on('mouseleave', 'mapillary-image', this.onMouseLeaveBinded);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.map.off('style.load', this.addBinded);
|
||||
this.map.off('mouseenter', 'mapillary-image', this.onMouseEnterBinded);
|
||||
this.map.off('mouseleave', 'mapillary-image', this.onMouseLeaveBinded);
|
||||
|
||||
if (this.map.getLayer('mapillary-image')) {
|
||||
this.map.removeLayer('mapillary-image');
|
||||
}
|
||||
if (this.map.getLayer('mapillary-sequence')) {
|
||||
this.map.removeLayer('mapillary-sequence');
|
||||
}
|
||||
if (this.map.getSource('mapillary')) {
|
||||
this.map.removeSource('mapillary');
|
||||
}
|
||||
|
||||
this.marker.remove();
|
||||
this.popupOpen.value = false;
|
||||
}
|
||||
|
||||
closePopup() {
|
||||
this.active = false;
|
||||
this.marker.remove();
|
||||
this.popupOpen.value = false;
|
||||
}
|
||||
|
||||
onMouseEnter(e: mapboxgl.MapMouseEvent) {
|
||||
this.active = true;
|
||||
|
||||
this.viewer.resize();
|
||||
this.viewer.moveTo(e.features[0].properties.id);
|
||||
|
||||
setPointerCursor();
|
||||
}
|
||||
|
||||
onMouseLeave() {
|
||||
resetCursor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { streetViewEnabled } from '$lib/components/map/street-view-control/utils.svelte';
|
||||
import { map } from '$lib/components/map/utils.svelte';
|
||||
import CustomControl from '$lib/components/map/custom-control/CustomControl.svelte';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import { Toggle } from '$lib/components/ui/toggle';
|
||||
import { PersonStanding, X } from '@lucide/svelte';
|
||||
import { MapillaryLayer } from './Mapillary';
|
||||
import { GoogleRedirect } from './Google';
|
||||
import { settings } from '$lib/logic/settings.svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const { streetViewSource } = settings;
|
||||
|
||||
let googleRedirect: GoogleRedirect | null = $state(null);
|
||||
let mapillaryLayer: MapillaryLayer | null = $state(null);
|
||||
let mapillaryOpen = $state({
|
||||
value: false,
|
||||
});
|
||||
let container: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
map.onLoad((map: mapboxgl.Map) => {
|
||||
googleRedirect = new GoogleRedirect(map);
|
||||
mapillaryLayer = new MapillaryLayer(map, container, mapillaryOpen);
|
||||
});
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (streetViewSource.value === 'mapillary') {
|
||||
googleRedirect?.remove();
|
||||
if (streetViewEnabled.current) {
|
||||
mapillaryLayer?.add();
|
||||
} else {
|
||||
mapillaryLayer?.remove();
|
||||
}
|
||||
} else {
|
||||
mapillaryLayer?.remove();
|
||||
if (streetViewEnabled.current) {
|
||||
googleRedirect?.add();
|
||||
} else {
|
||||
googleRedirect?.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<CustomControl class="w-[29px] h-[29px] shrink-0">
|
||||
<Tooltip class="w-full h-full" side="left" label={i18n._('menu.toggle_street_view')}>
|
||||
<Toggle
|
||||
bind:pressed={streetViewEnabled.current}
|
||||
class="w-full h-full rounded p-0"
|
||||
aria-label={i18n._('menu.toggle_street_view')}
|
||||
>
|
||||
<PersonStanding size="22" />
|
||||
</Toggle>
|
||||
</Tooltip>
|
||||
</CustomControl>
|
||||
|
||||
<div
|
||||
bind:this={container}
|
||||
class="{mapillaryOpen.value
|
||||
? ''
|
||||
: 'hidden'} !absolute bottom-[44px] right-2.5 z-10 w-[40%] h-[40%] bg-background rounded-md overflow-hidden border-background border-2"
|
||||
>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="absolute top-0 right-0 z-10 bg-background p-1 rounded-bl-md cursor-pointer"
|
||||
onclick={() => {
|
||||
if (mapillaryLayer) {
|
||||
mapillaryLayer.closePopup();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<X size="16" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
export const streetViewEnabled = $state({
|
||||
current: false,
|
||||
});
|
||||
Reference in New Issue
Block a user