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"; import type { Writable } from "svelte/store"; 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: Writable; addBinded = this.add.bind(this); onMouseEnterBinded = this.onMouseEnter.bind(this); onMouseLeaveBinded = this.onMouseLeave.bind(this); constructor(map: mapboxgl.Map, container: HTMLElement, popupOpen: Writable) { 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.set(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.set(false); } closePopup() { this.active = false; this.marker.remove(); this.popupOpen.set(false); } onMouseEnter(e: mapboxgl.MapMouseEvent) { this.active = true; this.viewer.resize(); this.viewer.moveTo(e.features[0].properties.id); setPointerCursor(); } onMouseLeave() { resetCursor(); } }