UNPKG

@goongmaps/goong-map-react

Version:

A fork of react-map-gl. React components for Goong JS

256 lines (222 loc) 7.8 kB
// Copyright (c) 2015 Uber Technologies, Inc. // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import * as React from 'react'; import {useState, useRef, useCallback, useContext, useImperativeHandle, forwardRef} from 'react'; import * as PropTypes from 'prop-types'; import WebMercatorViewport from 'viewport-mercator-project'; import ResizeObserver from 'resize-observer-polyfill'; import Mapbox from '../goong/goong'; import mapboxgl from '../utils/goongmap'; import {checkVisibilityConstraints} from '../utils/map-constraints'; import {MAPBOX_LIMITS} from '../utils/map-state'; import MapContext, {MapContextProvider} from './map-context'; import useIsomorphicLayoutEffect from '../utils/use-isomorphic-layout-effect'; import {getTerrainElevation} from '../utils/terrain'; /* eslint-disable max-len */ const TOKEN_DOC_URL = 'https://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens'; const NO_TOKEN_WARNING = 'A valid API access token is required to use Mapbox data'; /* eslint-disable max-len */ function noop() {} export function getViewport({map, props, width, height}) { const viewportProps = { ...props, ...props.viewState, width, height }; viewportProps.position = [0, 0, getTerrainElevation(map, viewportProps)]; return new WebMercatorViewport(viewportProps); } const UNAUTHORIZED_ERROR_CODE = 401; const CONTAINER_STYLE = { position: 'absolute', width: '100%', height: '100%', overflow: 'hidden' }; const propTypes = Object.assign({}, Mapbox.propTypes, { /** The dimensions of the map **/ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), /** Callback when map size changes **/ onResize: PropTypes.func, /** Hide invalid token warning even if request fails */ disableTokenWarning: PropTypes.bool, /** Whether the map is visible */ visible: PropTypes.bool, /** Custom class name for the map */ className: PropTypes.string, /** Custom CSS for the container */ style: PropTypes.object, /** Advanced features */ // Contraints for displaying the map. If not met, then the map is hidden. // Experimental! May be changed in minor version updates. visibilityConstraints: PropTypes.object }); const defaultProps = Object.assign({}, Mapbox.defaultProps, { disableTokenWarning: false, visible: true, onResize: noop, className: '', style: null, visibilityConstraints: MAPBOX_LIMITS }); function NoTokenWarning() { const style = { position: 'absolute', left: 0, top: 0 }; return ( <div key="warning" id="no-token-warning" // @ts-ignore style={style} > <h3 key="header">{NO_TOKEN_WARNING}</h3> <div key="text">For information on setting up your basemap, read</div> <a key="link" href={TOKEN_DOC_URL}> Note on Map Tokens </a> </div> ); } function getRefHandles(mapboxRef) { return { getMap: () => mapboxRef.current && mapboxRef.current.getMap(), queryRenderedFeatures: (geometry, options = {}) => { const map = mapboxRef.current && mapboxRef.current.getMap(); return map && map.queryRenderedFeatures(geometry, options); } }; } const StaticMap = forwardRef((props, ref) => { const [accessTokenValid, setTokenState] = useState(true); const [size, setSize] = useState({width: 0, height: 0}); const mapboxRef = useRef(null); const mapDivRef = useRef(null); const containerRef = useRef(null); const overlayRef = useRef(null); const context = useContext(MapContext); useIsomorphicLayoutEffect(() => { if (!StaticMap.supported()) { return undefined; } // Initialize const mapbox = new Mapbox({ ...props, ...size, mapboxgl, // Handle to mapbox-gl library container: mapDivRef.current, onError: evt => { const statusCode = (evt.error && evt.error.status) || evt.status; if (statusCode === UNAUTHORIZED_ERROR_CODE && accessTokenValid) { // Mapbox throws unauthorized error - invalid token console.error(NO_TOKEN_WARNING); // eslint-disable-line setTokenState(false); } props.onError(evt); } }); mapboxRef.current = mapbox; if (context && context.setMap) { context.setMap(mapbox.getMap()); } const resizeObserver = new ResizeObserver(entries => { if (entries[0].contentRect) { const {width, height} = entries[0].contentRect; setSize({width, height}); props.onResize({width, height}); } }); resizeObserver.observe(containerRef.current); // Clean up return () => { mapbox.finalize(); mapboxRef.current = null; resizeObserver.disconnect(); }; }, []); useIsomorphicLayoutEffect(() => { if (mapboxRef.current) { mapboxRef.current.setProps({...props, ...size}); } }); const map = mapboxRef.current && mapboxRef.current.getMap(); // External apps can call methods via ref // Note: this is not a recommended pattern in React FC - Keeping for backward compatibility useImperativeHandle(ref, () => getRefHandles(mapboxRef), []); const preventScroll = useCallback(({target}) => { if (target === overlayRef.current) { target.scrollTo(0, 0); } }, []); const overlays = map && ( <MapContextProvider value={{ ...context, viewport: context.viewport || getViewport({map, props, ...size}), map, container: context.container || containerRef.current }} > <div key="map-overlays" className="overlays" ref={overlayRef} // @ts-ignore style={CONTAINER_STYLE} onScroll={preventScroll} > {props.children} </div> </MapContextProvider> ); const {className, width, height, style, visibilityConstraints} = props; const mapContainerStyle = Object.assign({position: 'relative'}, style, { width, height }); const visible = props.visible && checkVisibilityConstraints(props.viewState || props, visibilityConstraints); const mapStyle = Object.assign({}, CONTAINER_STYLE, { visibility: visible ? 'inherit' : 'hidden' }); return ( <div key="map-container" ref={containerRef} // @ts-ignore style={mapContainerStyle} > <div key="map-mapbox" ref={mapDivRef} // @ts-ignore style={mapStyle} className={className} /> {overlays} {!accessTokenValid && !props.disableTokenWarning && <NoTokenWarning />} </div> ); }); StaticMap.supported = () => mapboxgl && mapboxgl.supported(); StaticMap.propTypes = propTypes; StaticMap.defaultProps = defaultProps; export default StaticMap;