modules/google-maps/src/utils.js (130 lines of code) (raw):

/* global google */ import {Deck} from '@deck.gl/core'; // https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas const MAX_LATITUDE = 85.05113; /** * Get a new deck instance * @param map (google.maps.Map) - The parent Map instance * @param overlay (google.maps.OverlayView) - A maps Overlay instance * @param [deck] (Deck) - a previously created instances */ export function createDeckInstance(map, overlay, deck, props) { if (deck) { if (deck.props.userData._googleMap === map) { return deck; } // deck instance was created for a different map destroyDeckInstance(deck); } const eventListeners = { click: null, dblclick: null, mousemove: null, mouseout: null }; deck = new Deck({ ...props, parent: getContainer(overlay), initialViewState: { longitude: 0, latitude: 0, zoom: 1 }, controller: false, userData: { _googleMap: map, _eventListeners: eventListeners } }); // Register event listeners for (const eventType in eventListeners) { eventListeners[eventType] = map.addListener(eventType, evt => handleMouseEvent(deck, eventType, evt) ); } return deck; } function getContainer(overlay) { return overlay.getPanes().overlayLayer; } /** * Safely remove a deck instance * @param deck (Deck) - a previously created instances */ export function destroyDeckInstance(deck) { const {_eventListeners: eventListeners} = deck.props.userData; // Unregister event listeners for (const eventType in eventListeners) { eventListeners[eventType].remove(); } deck.finalize(); } /* eslint-disable max-statements */ /** * Get the current view state * @param map (google.maps.Map) - The parent Map instance * @param overlay (google.maps.OverlayView) - A maps Overlay instance */ export function getViewState(map, overlay) { // The map fills the container div unless it's in fullscreen mode // at which point the first child of the container is promoted const container = map.getDiv().firstChild; const width = container.offsetWidth; const height = container.offsetHeight; // Canvas position relative to draggable map's container depends on // overlayView's projection, not the map's. Have to use the center of the // map for this, not the top left, for the same reason as above. const projection = overlay.getProjection(); const bounds = map.getBounds(); const ne = bounds.getNorthEast(); const sw = bounds.getSouthWest(); const topRight = projection.fromLatLngToDivPixel(ne); const bottomLeft = projection.fromLatLngToDivPixel(sw); // google maps places overlays in a container anchored at the map center. // the container CSS is manipulated during dragging. // We need to update left/top of the deck canvas to match the base map. const nwContainerPx = new google.maps.Point(0, 0); const nw = projection.fromContainerPixelToLatLng(nwContainerPx); const nwDivPx = projection.fromLatLngToDivPixel(nw); let leftOffset = nwDivPx.x; let topOffset = nwDivPx.y; // Adjust horizontal offset - position the viewport at the map in the center const mapWidth = projection.getWorldWidth(); const mapCount = Math.ceil(width / mapWidth); leftOffset -= Math.floor(mapCount / 2) * mapWidth; // Compute fractional zoom. const scale = height ? (bottomLeft.y - topRight.y) / height : 1; // When resizing aggressively, occasionally ne and sw are the same points // See https://github.com/visgl/deck.gl/issues/4218 const zoom = Math.log2(scale || 1) + map.getZoom() - 1; // Compute fractional center. let centerPx = new google.maps.Point(width / 2, height / 2); const centerContainer = projection.fromContainerPixelToLatLng(centerPx); let latitude = centerContainer.lat(); const longitude = centerContainer.lng(); // Adjust vertical offset - limit latitude if (Math.abs(latitude) > MAX_LATITUDE) { latitude = latitude > 0 ? MAX_LATITUDE : -MAX_LATITUDE; const center = new google.maps.LatLng(latitude, longitude); centerPx = projection.fromLatLngToContainerPixel(center); topOffset += centerPx.y - height / 2; } return { width, height, left: leftOffset, top: topOffset, zoom, pitch: map.getTilt(), latitude, longitude }; } /* eslint-enable max-statements */ function getEventPixel(event, deck) { if (event.pixel) { return event.pixel; } // event.pixel may not exist when clicking on a POI // https://developers.google.com/maps/documentation/javascript/reference/map#MouseEvent const point = deck.getViewports()[0].project([event.latLng.lng(), event.latLng.lat()]); return { x: point[0], y: point[1] }; } // Triggers picking on a mouse event function handleMouseEvent(deck, type, event) { const mockEvent = { type, offsetCenter: getEventPixel(event, deck), srcEvent: event }; switch (type) { case 'click': // Hack: because we do not listen to pointer down, perform picking now deck._lastPointerDownInfo = deck.pickObject(mockEvent.offsetCenter); mockEvent.tapCount = 1; deck._onEvent(mockEvent); break; case 'dblclick': mockEvent.type = 'click'; mockEvent.tapCount = 2; deck._onEvent(mockEvent); break; case 'mousemove': mockEvent.type = 'pointermove'; deck._onPointerMove(mockEvent); break; case 'mouseout': mockEvent.type = 'pointerleave'; deck._onPointerMove(mockEvent); break; default: return; } }