diff --git a/src/web/components/Camera.tsx b/src/web/components/Camera.tsx index 2acef7072..46445ee5a 100644 --- a/src/web/components/Camera.tsx +++ b/src/web/components/Camera.tsx @@ -1,32 +1,142 @@ -import React from 'react'; +import { Component, ContextType } from 'react'; +import { CameraProps, CameraRef } from '../../components/Camera'; +import { Position } from '../../types/Position'; import MapContext from '../MapContext'; -class Camera extends React.Component<{ - centerCoordinate: [number, number] | null; -}> { - context!: React.ContextType; +function isArray(value: T | ArrayLike): value is ArrayLike { + return (value as ArrayLike).length !== undefined; +} + +function buildMapboxGlPadding( + padding?: number | number[], +): number | mapboxgl.PaddingOptions | undefined { + if (padding === undefined) { + // undefined + return undefined; + } else if (!isArray(padding)) { + // padding + return padding; + } else { + // Array + if (padding.length === 0) { + // [] + return undefined; + } else if (padding.length < 2) { + // [padding] + return padding[0]; + } else if (padding.length < 4) { + // [vertical, horizontal] + return { + left: padding[0], + right: padding[0], + top: padding[1], + bottom: padding[1], + }; + } else { + // [top, right, bottom, left] + return { + top: padding[0], + right: padding[1], + bottom: padding[2], + left: padding[3], + }; + } + } +} + +class Camera + extends Component< + Pick< + CameraProps, + 'centerCoordinate' | 'zoomLevel' | 'minZoomLevel' | 'maxZoomLevel' + > + > + implements Omit +{ + context!: ContextType; static contextType = MapContext; static UserTrackingModes = []; componentDidMount() { const { map } = this.context; - const { centerCoordinate } = this.props; - if (map && centerCoordinate) { - map.flyTo({ center: centerCoordinate }); + if (!map) { + return; + } + + // minZoomLevel + if (this.props.minZoomLevel !== undefined) { + map.setMinZoom(this.props.minZoomLevel); + } + + // maxZoomLevel + if (this.props.maxZoomLevel !== undefined) { + map.setMaxZoom(this.props.maxZoomLevel); + } + + // zoomLevel + if (this.props.zoomLevel !== undefined) { + map.setZoom(this.props.zoomLevel); + } + + // centerCoordinate + if (this.props.centerCoordinate !== undefined) { + map.flyTo({ + center: this.props.centerCoordinate.slice(0, 2) as [number, number], + duration: 0, + }); } } fitBounds( - northEastCoordinates: [number, number], - southWestCoordinates: [number, number], - padding = 0, - animationDuration = 0.0, + northEastCoordinates: Position, + southWestCoordinates: Position, + padding: number | number[] = 0, + animationDuration = 0, ) { const { map } = this.context; if (map) { - map.fitBounds([northEastCoordinates, southWestCoordinates]); + map.fitBounds( + [ + northEastCoordinates.slice(0, 2) as [number, number], + southWestCoordinates.slice(0, 2) as [number, number], + ], + { + padding: buildMapboxGlPadding(padding), + duration: animationDuration, + }, + ); + } + } + + flyTo(centerCoordinate: Position, animationDuration = 2000) { + const { map } = this.context; + if (map) { + map.flyTo({ + center: centerCoordinate.slice(0, 2) as [number, number], + duration: animationDuration, + }); + } + } + + moveTo(centerCoordinate: Position, animationDuration = 0) { + const { map } = this.context; + if (map) { + map.easeTo({ + center: centerCoordinate.slice(0, 2) as [number, number], + duration: animationDuration, + }); + } + } + + zoomTo(zoomLevel: number, animationDuration = 2000) { + const { map } = this.context; + if (map) { + map.flyTo({ + zoom: zoomLevel, + duration: animationDuration, + }); } }