Skip to content

Commit 6870017

Browse files
authored
Support vector maps in google module (visgl#5981)
1 parent 2601f28 commit 6870017

File tree

9 files changed

+329
-77
lines changed

9 files changed

+329
-77
lines changed

examples/get-started/pure-js/google-maps/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ with [webpack-dev-server](https://webpack.js.org/guides/development/#webpack-dev
99

1010
## Usage
1111

12-
To run this example, you need a [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key). You can either set an environment variable:
12+
To run this example, you need a [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key) and a [map id](https://developers.google.com/maps/documentation/javascript/webgl#vector-id). You can either set an environment variable:
1313

1414
```bash
1515
export GoogleMapsAPIKey=<google_maps_api_key>
16+
export GoogleMapsMapId=<google_maps_map_id>
1617
```
1718

18-
Or set the `GOOGLE_MAPS_API_KEY` variable in `app.js`.
19+
Or set the `GOOGLE_MAPS_API_KEY` and `GOOGLE_MAP_ID` variables in `app.js`.
1920

2021
To install dependencies:
2122

examples/get-started/pure-js/google-maps/app.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const AIR_PORTS =
88

99
// Set your Google Maps API key here or via environment variable
1010
const GOOGLE_MAPS_API_KEY = process.env.GoogleMapsAPIKey; // eslint-disable-line
11-
const GOOGLE_MAPS_API_URL = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=visualization&v=3.45`;
11+
const GOOGLE_MAP_ID = process.env.GoogleMapsMapId; // eslint-disable-line
12+
const GOOGLE_MAPS_API_URL = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&v=beta&map_ids=${GOOGLE_MAP_ID}`;
1213

1314
function loadScript(url) {
1415
const script = document.createElement('script');
@@ -24,7 +25,8 @@ function loadScript(url) {
2425
loadScript(GOOGLE_MAPS_API_URL).then(() => {
2526
const map = new google.maps.Map(document.getElementById('map'), {
2627
center: {lat: 51.47, lng: 0.45},
27-
zoom: 5
28+
zoom: 5,
29+
mapId: GOOGLE_MAP_ID
2830
});
2931

3032
const overlay = new DeckOverlay({

examples/get-started/pure-js/google-maps/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
"build": "webpack -p"
99
},
1010
"dependencies": {
11-
"@deck.gl/core": "^8.1.0",
12-
"@deck.gl/google-maps": "^8.1.0",
13-
"@deck.gl/layers": "^8.1.0"
11+
"@deck.gl/core": "^8.5.0",
12+
"@deck.gl/google-maps": "^8.5.0",
13+
"@deck.gl/layers": "^8.5.0"
1414
},
1515
"devDependencies": {
1616
"webpack": "^4.20.2",

examples/get-started/pure-js/google-maps/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const CONFIG = {
1212

1313
plugins: [
1414
// Read google maps token from environment variable
15-
new webpack.EnvironmentPlugin(['GoogleMapsAPIKey'])
15+
new webpack.EnvironmentPlugin(['GoogleMapsAPIKey', 'GoogleMapsMapId'])
1616
]
1717
};
1818

modules/core/src/views/view.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ export default class View {
1515
height = '100%',
1616

1717
// Viewport Options
18-
projectionMatrix = null, // Projection matrix
1918
fovy = 50, // Perspective projection parameters, used if projectionMatrix not supplied
2019
near = 0.1, // Distance of near clipping plane
2120
far = 1000, // Distance of far clipping plane
22-
modelMatrix = null, // A model matrix to be applied to position, to match the layer props API
2321

2422
// A View can be a wrapper for a viewport instance
2523
viewportInstance = null,
@@ -38,11 +36,9 @@ export default class View {
3836
this.props = {
3937
...props,
4038
id: this.id,
41-
projectionMatrix,
4239
fovy,
4340
near,
44-
far,
45-
modelMatrix
41+
far
4642
};
4743

4844
// Extents

modules/google-maps/src/google-maps-overlay.js

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
/* global google */
2-
import {createDeckInstance, destroyDeckInstance, getViewState} from './utils';
2+
import {setParameters, withParameters} from '@luma.gl/core';
3+
import GL from '@luma.gl/constants';
4+
import {
5+
createDeckInstance,
6+
destroyDeckInstance,
7+
getViewPropsFromOverlay,
8+
getViewPropsFromCoordinateTransformer
9+
} from './utils';
310

411
const HIDE_ALL_LAYERS = () => false;
12+
const GL_STATE = {
13+
depthMask: true,
14+
depthTest: true,
15+
blendFunc: [GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA],
16+
blendEquation: GL.FUNC_ADD
17+
};
518

619
export default class GoogleMapsOverlay {
720
constructor(props) {
821
this.props = {};
9-
1022
this._map = null;
11-
12-
const overlay = new google.maps.OverlayView();
13-
overlay.onAdd = this._onAdd.bind(this);
14-
overlay.onRemove = this._onRemove.bind(this);
15-
overlay.draw = this._draw.bind(this);
16-
this._overlay = overlay;
17-
1823
this.setProps(props);
1924
}
2025

@@ -30,7 +35,9 @@ export default class GoogleMapsOverlay {
3035
}
3136
if (map) {
3237
this._map = map;
33-
this._overlay.setMap(map);
38+
map.addListener('renderingtype_changed', () => {
39+
this._createOverlay(map);
40+
});
3441
}
3542
}
3643

@@ -66,18 +73,56 @@ export default class GoogleMapsOverlay {
6673
}
6774

6875
/* Private API */
76+
_createOverlay(map) {
77+
const {VECTOR, UNINITIALIZED} = google.maps.RenderingType;
78+
const renderingType = map.getRenderingType();
79+
if (renderingType === UNINITIALIZED) {
80+
return;
81+
}
82+
const isVectorMap = renderingType === VECTOR;
83+
const OverlayView = isVectorMap ? google.maps.WebglOverlayView : google.maps.OverlayView;
84+
const overlay = new OverlayView();
85+
86+
// Lifecycle methods are different depending on map type
87+
if (isVectorMap) {
88+
overlay.onAdd = () => {};
89+
overlay.onContextLost = this._onContextLost.bind(this);
90+
overlay.onContextRestored = this._onContextRestored.bind(this);
91+
overlay.onDraw = this._onDrawVector.bind(this);
92+
} else {
93+
overlay.onAdd = this._onAdd.bind(this);
94+
overlay.draw = this._onDrawRaster.bind(this);
95+
}
96+
overlay.onRemove = this._onRemove.bind(this);
97+
98+
this._overlay = overlay;
99+
this._overlay.setMap(map);
100+
}
101+
69102
_onAdd() {
70103
this._deck = createDeckInstance(this._map, this._overlay, this._deck, this.props);
71104
}
72105

106+
_onContextRestored(gl) {
107+
this._deck = createDeckInstance(this._map, this._overlay, this._deck, {gl, ...this.props});
108+
}
109+
110+
_onContextLost() {
111+
// TODO this isn't working
112+
if (this._deck) {
113+
destroyDeckInstance(this._deck);
114+
this._deck = null;
115+
}
116+
}
117+
73118
_onRemove() {
74119
// Clear deck canvas
75120
this._deck.setProps({layerFilter: HIDE_ALL_LAYERS});
76121
}
77122

78-
_draw() {
123+
_onDrawRaster() {
79124
const deck = this._deck;
80-
const {width, height, left, top, zoom, pitch, latitude, longitude} = getViewState(
125+
const {width, height, left, top, zoom, pitch, latitude, longitude} = getViewPropsFromOverlay(
81126
this._map,
82127
this._overlay
83128
);
@@ -98,4 +143,31 @@ export default class GoogleMapsOverlay {
98143
// Deck is initialized
99144
deck.redraw();
100145
}
146+
147+
// Vector code path
148+
_onDrawVector(gl, coordinateTransformer) {
149+
const deck = this._deck;
150+
151+
deck.setProps({
152+
...getViewPropsFromCoordinateTransformer(this._map, coordinateTransformer)
153+
});
154+
155+
if (deck.layerManager) {
156+
this._overlay.requestRedraw();
157+
withParameters(gl, GL_STATE, () => {
158+
deck._drawLayers('google-vector', {
159+
clearCanvas: false
160+
});
161+
});
162+
163+
// Reset state otherwise get rendering errors in
164+
// Google library. These occur because the picking
165+
// code is run outside of the _onDrawVector() method and
166+
// the GL state can be inconsistent
167+
setParameters(gl, {
168+
scissor: [0, 0, gl.canvas.width, gl.canvas.height],
169+
stencilFunc: [gl.ALWAYS, 0, 255, gl.ALWAYS, 0, 255]
170+
});
171+
}
172+
}
101173
}

modules/google-maps/src/utils.js

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* global google, document */
22
import {Deck} from '@deck.gl/core';
3+
import {Matrix4} from 'math.gl';
34

45
// https://en.wikipedia.org/wiki/Web_Mercator_projection#Formulas
56
const MAX_LATITUDE = 85.05113;
@@ -57,7 +58,17 @@ function getContainer(overlay, style) {
5758
const container = document.createElement('div');
5859
container.style.position = 'absolute';
5960
Object.assign(container.style, style);
60-
overlay.getPanes().overlayLayer.appendChild(container);
61+
62+
// The DOM structure has a different structure depending on whether
63+
// the Google map is rendered as vector or raster
64+
if (overlay.getPanes) {
65+
overlay.getPanes().overlayLayer.appendChild(container);
66+
} else {
67+
overlay
68+
.getMap()
69+
.getDiv()
70+
.appendChild(container);
71+
}
6172
return container;
6273
}
6374

@@ -82,12 +93,8 @@ export function destroyDeckInstance(deck) {
8293
* @param map (google.maps.Map) - The parent Map instance
8394
* @param overlay (google.maps.OverlayView) - A maps Overlay instance
8495
*/
85-
export function getViewState(map, overlay) {
86-
// The map fills the container div unless it's in fullscreen mode
87-
// at which point the first child of the container is promoted
88-
const container = map.getDiv().firstChild;
89-
const width = container.offsetWidth;
90-
const height = container.offsetHeight;
96+
export function getViewPropsFromOverlay(map, overlay) {
97+
const {width, height} = getMapSize(map);
9198

9299
// Canvas position relative to draggable map's container depends on
93100
// overlayView's projection, not the map's. Have to use the center of the
@@ -145,8 +152,67 @@ export function getViewState(map, overlay) {
145152
longitude
146153
};
147154
}
155+
148156
/* eslint-enable max-statements */
149157

158+
/**
159+
* Get the current view state
160+
* @param map (google.maps.Map) - The parent Map instance
161+
* @param coordinateTransformer (google.maps.CoordinateTransformer) - A CoordinateTransformer instance
162+
*/
163+
export function getViewPropsFromCoordinateTransformer(map, coordinateTransformer) {
164+
const {width, height} = getMapSize(map);
165+
const {
166+
lat: latitude,
167+
lng: longitude,
168+
heading: bearing,
169+
tilt: pitch,
170+
zoom
171+
} = coordinateTransformer.getCameraParams();
172+
173+
// Match Google projection matrix
174+
const fovy = 25;
175+
const aspect = width / height;
176+
177+
// Match depth range (crucial for correct z-sorting)
178+
const near = 0.75;
179+
const far = 300000000000000;
180+
// const far = Infinity;
181+
182+
const projectionMatrix = new Matrix4().perspective({
183+
fovy: (fovy * Math.PI) / 180,
184+
aspect,
185+
near,
186+
far
187+
});
188+
const focalDistance = 0.5 * projectionMatrix[5];
189+
190+
return {
191+
width,
192+
height,
193+
viewState: {
194+
altitude: focalDistance,
195+
bearing,
196+
latitude,
197+
longitude,
198+
pitch,
199+
projectionMatrix,
200+
repeat: true,
201+
zoom: zoom - 1
202+
}
203+
};
204+
}
205+
206+
function getMapSize(map) {
207+
// The map fills the container div unless it's in fullscreen mode
208+
// at which point the first child of the container is promoted
209+
const container = map.getDiv().firstChild;
210+
return {
211+
width: container.offsetWidth,
212+
height: container.offsetHeight
213+
};
214+
}
215+
150216
function getEventPixel(event, deck) {
151217
if (event.pixel) {
152218
return event.pixel;

0 commit comments

Comments
 (0)