import 'maplibre-gl/dist/maplibre-gl.css' import React, { useEffect, useState, useRef, useMemo } from 'react' import Map, { Layer, NavigationControl, Source } from 'react-map-gl' import bearing from '@turf/bearing' import maplibre from 'maplibre-gl' import * as pmtiles from 'pmtiles' const ANIMATION_MOVE_MS = 350 export default function FlyingTrip() { const [tramRoute, setTramRoute] = useState() const [boundaries, setBoundaries] = useState() const [isRunning, setRunning] = useState(false) const mapRef = useRef(null) const lastIdx = useRef(0) const routeCoordinates = useMemo( () => tramRoute ? tramRoute.features .filter((f) => f.properties.route_variant_type === 'default') .map((f) => f.geometry.coordinates) .flat() : [], [tramRoute] ) useEffect(() => { const protocol = new pmtiles.Protocol() maplibre.addProtocol('pmtiles', protocol.tile) }, []) useEffect(() => { Promise.all([ fetch('/blog/assets/tram8.json'), fetch('/blog/assets/szczecin.json') ]) .then((responses) => Promise.all(responses.map((res) => res.json()))) .then(([tramRoute, boundaries]) => { setTramRoute(tramRoute) setBoundaries(boundaries) }) .catch(console.error) }, []) useEffect(() => { if (routeCoordinates.length === 0 || !isRunning) { return } mapRef?.current.jumpTo({ center: routeCoordinates[lastIdx.current], pitch: 70, zoom: 16 }) let timeout = null const nextMove = () => { const currentIdx = lastIdx.current if (currentIdx + 1 >= routeCoordinates.length) { lastIdx.current = 0 setRunning(false) return } const currentPoint = routeCoordinates[currentIdx] const nextPoint = routeCoordinates[currentIdx + 1] mapRef?.current.flyTo({ animate: true, bearing: bearing(currentPoint, nextPoint), center: nextPoint, curve: 1, duration: ANIMATION_MOVE_MS, easing: (x) => Math.sin((x * Math.PI) / 2), zoom: 16 }) lastIdx.current = currentIdx + 1 timeout = setTimeout(() => nextMove(), ANIMATION_MOVE_MS) } nextMove() return () => { if (timeout !== null) { clearTimeout(timeout) } } }, [routeCoordinates, isRunning]) return ( ) }