@octanejs/react-map-gl 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +62 -0
- package/README.md +112 -0
- package/UPSTREAM.md +216 -0
- package/package.json +61 -0
- package/src/components/attribution-control.ts +27 -0
- package/src/components/fullscreen-control.ts +35 -0
- package/src/components/geolocate-control.ts +88 -0
- package/src/components/layer.ts +135 -0
- package/src/components/map.tsrx +173 -0
- package/src/components/marker.ts +217 -0
- package/src/components/navigation-control.ts +27 -0
- package/src/components/popup.ts +100 -0
- package/src/components/scale-control.ts +44 -0
- package/src/components/source.tsrx +146 -0
- package/src/components/use-control.ts +73 -0
- package/src/components/use-map.tsrx +64 -0
- package/src/index.ts +33 -0
- package/src/mapbox/create-ref.ts +114 -0
- package/src/mapbox/mapbox.ts +714 -0
- package/src/mapbox/proxy-transform.ts +155 -0
- package/src/types/common.ts +41 -0
- package/src/types/events.ts +130 -0
- package/src/types/internal.ts +20 -0
- package/src/types/lib.ts +70 -0
- package/src/types/octane.ts +27 -0
- package/src/types/style-spec.ts +51 -0
- package/src/utils/apply-react-style.ts +26 -0
- package/src/utils/assert.ts +10 -0
- package/src/utils/children.ts +26 -0
- package/src/utils/compare-class-names.ts +34 -0
- package/src/utils/deep-equal.ts +66 -0
- package/src/utils/set-globals.ts +62 -0
- package/src/utils/style-utils.ts +65 -0
- package/src/utils/transform.ts +115 -0
- package/src/utils/use-isomorphic-layout-effect.ts +7 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
// @ts-nocheck — byte-exact reuse of @vis.gl/react-mapbox 8.1.2. Upstream compiles
|
|
2
|
+
// without strictNullChecks or noImplicitAny; this repository is strict. The copy
|
|
3
|
+
// under upstream/ is typechecked with upstream's own configuration instead, so
|
|
4
|
+
// nothing here is unchecked, only checked at the settings its authors chose.
|
|
5
|
+
// Every line below this four-line banner is byte-identical to upstream.
|
|
6
|
+
import {transformToViewState, compareViewStateWithTransform} from '../utils/transform';
|
|
7
|
+
import {ProxyTransform, createProxyTransform} from './proxy-transform';
|
|
8
|
+
import {normalizeStyle} from '../utils/style-utils';
|
|
9
|
+
import {deepEqual} from '../utils/deep-equal';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ViewState,
|
|
13
|
+
Point,
|
|
14
|
+
PointLike,
|
|
15
|
+
PaddingOptions,
|
|
16
|
+
ImmutableLike,
|
|
17
|
+
LngLatBoundsLike,
|
|
18
|
+
MapGeoJSONFeature
|
|
19
|
+
} from '../types/common';
|
|
20
|
+
import type {
|
|
21
|
+
StyleSpecification,
|
|
22
|
+
LightSpecification,
|
|
23
|
+
TerrainSpecification,
|
|
24
|
+
FogSpecification,
|
|
25
|
+
ProjectionSpecification
|
|
26
|
+
} from '../types/style-spec';
|
|
27
|
+
import type {MapInstance} from '../types/lib';
|
|
28
|
+
import type {Transform} from '../types/internal';
|
|
29
|
+
import type {
|
|
30
|
+
MapCallbacks,
|
|
31
|
+
ViewStateChangeEvent,
|
|
32
|
+
MapEvent,
|
|
33
|
+
ErrorEvent,
|
|
34
|
+
MapMouseEvent
|
|
35
|
+
} from '../types/events';
|
|
36
|
+
|
|
37
|
+
export type MapboxProps = Partial<ViewState> &
|
|
38
|
+
MapCallbacks & {
|
|
39
|
+
// Init options
|
|
40
|
+
mapboxAccessToken?: string;
|
|
41
|
+
|
|
42
|
+
/** Camera options used when constructing the Map instance */
|
|
43
|
+
initialViewState?: Partial<ViewState> & {
|
|
44
|
+
/** The initial bounds of the map. If bounds is specified, it overrides longitude, latitude and zoom options. */
|
|
45
|
+
bounds?: LngLatBoundsLike;
|
|
46
|
+
/** A fitBounds options object to use only when setting the bounds option. */
|
|
47
|
+
fitBoundsOptions?: {
|
|
48
|
+
offset?: PointLike;
|
|
49
|
+
minZoom?: number;
|
|
50
|
+
maxZoom?: number;
|
|
51
|
+
padding?: number | PaddingOptions;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** If provided, render into an external WebGL context */
|
|
56
|
+
gl?: WebGLRenderingContext;
|
|
57
|
+
|
|
58
|
+
/** For external controller to override the camera state */
|
|
59
|
+
viewState?: ViewState & {
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Styling
|
|
65
|
+
|
|
66
|
+
/** Mapbox style */
|
|
67
|
+
mapStyle?: string | StyleSpecification | ImmutableLike<StyleSpecification>;
|
|
68
|
+
/** Enable diffing when the map style changes
|
|
69
|
+
* @default true
|
|
70
|
+
*/
|
|
71
|
+
styleDiffing?: boolean;
|
|
72
|
+
/** The projection property of the style. Must conform to the Projection Style Specification.
|
|
73
|
+
* @default 'mercator'
|
|
74
|
+
*/
|
|
75
|
+
projection?: ProjectionSpecification | ProjectionSpecification['name'];
|
|
76
|
+
/** The fog property of the style. Must conform to the Fog Style Specification .
|
|
77
|
+
* If `undefined` is provided, removes the fog from the map. */
|
|
78
|
+
fog?: FogSpecification;
|
|
79
|
+
/** Light properties of the map. */
|
|
80
|
+
light?: LightSpecification;
|
|
81
|
+
/** Terrain property of the style. Must conform to the Terrain Style Specification .
|
|
82
|
+
* If `undefined` is provided, removes terrain from the map. */
|
|
83
|
+
terrain?: TerrainSpecification;
|
|
84
|
+
|
|
85
|
+
/** Default layers to query on pointer events */
|
|
86
|
+
interactiveLayerIds?: string[];
|
|
87
|
+
/** CSS cursor */
|
|
88
|
+
cursor?: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const DEFAULT_STYLE = {version: 8, sources: {}, layers: []} as StyleSpecification;
|
|
92
|
+
|
|
93
|
+
const DEFAULT_SETTINGS = {
|
|
94
|
+
minZoom: 0,
|
|
95
|
+
maxZoom: 22,
|
|
96
|
+
minPitch: 0,
|
|
97
|
+
maxPitch: 85,
|
|
98
|
+
maxBounds: [-180, -85.051129, 180, 85.051129],
|
|
99
|
+
projection: 'mercator',
|
|
100
|
+
renderWorldCopies: true
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const pointerEvents = {
|
|
104
|
+
mousedown: 'onMouseDown',
|
|
105
|
+
mouseup: 'onMouseUp',
|
|
106
|
+
mouseover: 'onMouseOver',
|
|
107
|
+
mousemove: 'onMouseMove',
|
|
108
|
+
click: 'onClick',
|
|
109
|
+
dblclick: 'onDblClick',
|
|
110
|
+
mouseenter: 'onMouseEnter',
|
|
111
|
+
mouseleave: 'onMouseLeave',
|
|
112
|
+
mouseout: 'onMouseOut',
|
|
113
|
+
contextmenu: 'onContextMenu',
|
|
114
|
+
touchstart: 'onTouchStart',
|
|
115
|
+
touchend: 'onTouchEnd',
|
|
116
|
+
touchmove: 'onTouchMove',
|
|
117
|
+
touchcancel: 'onTouchCancel'
|
|
118
|
+
};
|
|
119
|
+
const cameraEvents = {
|
|
120
|
+
movestart: 'onMoveStart',
|
|
121
|
+
move: 'onMove',
|
|
122
|
+
moveend: 'onMoveEnd',
|
|
123
|
+
dragstart: 'onDragStart',
|
|
124
|
+
drag: 'onDrag',
|
|
125
|
+
dragend: 'onDragEnd',
|
|
126
|
+
zoomstart: 'onZoomStart',
|
|
127
|
+
zoom: 'onZoom',
|
|
128
|
+
zoomend: 'onZoomEnd',
|
|
129
|
+
rotatestart: 'onRotateStart',
|
|
130
|
+
rotate: 'onRotate',
|
|
131
|
+
rotateend: 'onRotateEnd',
|
|
132
|
+
pitchstart: 'onPitchStart',
|
|
133
|
+
pitch: 'onPitch',
|
|
134
|
+
pitchend: 'onPitchEnd'
|
|
135
|
+
};
|
|
136
|
+
const otherEvents = {
|
|
137
|
+
wheel: 'onWheel',
|
|
138
|
+
boxzoomstart: 'onBoxZoomStart',
|
|
139
|
+
boxzoomend: 'onBoxZoomEnd',
|
|
140
|
+
boxzoomcancel: 'onBoxZoomCancel',
|
|
141
|
+
resize: 'onResize',
|
|
142
|
+
load: 'onLoad',
|
|
143
|
+
render: 'onRender',
|
|
144
|
+
idle: 'onIdle',
|
|
145
|
+
remove: 'onRemove',
|
|
146
|
+
data: 'onData',
|
|
147
|
+
styledata: 'onStyleData',
|
|
148
|
+
sourcedata: 'onSourceData',
|
|
149
|
+
error: 'onError'
|
|
150
|
+
};
|
|
151
|
+
const settingNames = [
|
|
152
|
+
'minZoom',
|
|
153
|
+
'maxZoom',
|
|
154
|
+
'minPitch',
|
|
155
|
+
'maxPitch',
|
|
156
|
+
'maxBounds',
|
|
157
|
+
'projection',
|
|
158
|
+
'renderWorldCopies'
|
|
159
|
+
];
|
|
160
|
+
const handlerNames = [
|
|
161
|
+
'scrollZoom',
|
|
162
|
+
'boxZoom',
|
|
163
|
+
'dragRotate',
|
|
164
|
+
'dragPan',
|
|
165
|
+
'keyboard',
|
|
166
|
+
'doubleClickZoom',
|
|
167
|
+
'touchZoomRotate',
|
|
168
|
+
'touchPitch'
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* A wrapper for mapbox-gl's Map class
|
|
173
|
+
*/
|
|
174
|
+
export default class Mapbox {
|
|
175
|
+
private _MapClass: {new (options: any): MapInstance};
|
|
176
|
+
/** mapboxgl.Map instance */
|
|
177
|
+
private _map: MapInstance = null;
|
|
178
|
+
/** User-supplied props */
|
|
179
|
+
props: MapboxProps;
|
|
180
|
+
|
|
181
|
+
/** The transform that replaces native map.transform to resolve changes vs. React props
|
|
182
|
+
* See proxy-transform.ts
|
|
183
|
+
*/
|
|
184
|
+
private _proxyTransform: ProxyTransform;
|
|
185
|
+
|
|
186
|
+
// Internal states
|
|
187
|
+
/** Making updates driven by React props. Do not trigger React callbacks to avoid infinite loop */
|
|
188
|
+
private _internalUpdate: boolean = false;
|
|
189
|
+
/** Map is currently rendering */
|
|
190
|
+
private _inRender: boolean = false;
|
|
191
|
+
/** Map features under the pointer */
|
|
192
|
+
private _hoveredFeatures: MapGeoJSONFeature[] = null;
|
|
193
|
+
/** View state changes driven by React props
|
|
194
|
+
* They still need to fire move/etc. events because controls such as marker/popup
|
|
195
|
+
* subscribe to the move event internally to update their position
|
|
196
|
+
* React callbacks like onMove are not called for these */
|
|
197
|
+
private _deferredEvents: {
|
|
198
|
+
move: boolean;
|
|
199
|
+
zoom: boolean;
|
|
200
|
+
pitch: boolean;
|
|
201
|
+
rotate: boolean;
|
|
202
|
+
} = {
|
|
203
|
+
move: false,
|
|
204
|
+
zoom: false,
|
|
205
|
+
pitch: false,
|
|
206
|
+
rotate: false
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
static savedMaps: Mapbox[] = [];
|
|
210
|
+
|
|
211
|
+
constructor(
|
|
212
|
+
MapClass: {new (options: any): MapInstance},
|
|
213
|
+
props: MapboxProps,
|
|
214
|
+
container: HTMLDivElement
|
|
215
|
+
) {
|
|
216
|
+
this._MapClass = MapClass;
|
|
217
|
+
this.props = props;
|
|
218
|
+
this._initialize(container);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
get map(): MapInstance {
|
|
222
|
+
return this._map;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
get transform(): Transform {
|
|
226
|
+
return this._map.transform;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
setProps(props: MapboxProps) {
|
|
230
|
+
const oldProps = this.props;
|
|
231
|
+
this.props = props;
|
|
232
|
+
|
|
233
|
+
const settingsChanged = this._updateSettings(props, oldProps);
|
|
234
|
+
const sizeChanged = this._updateSize(props);
|
|
235
|
+
const viewStateChanged = this._updateViewState(props, true);
|
|
236
|
+
this._updateStyle(props, oldProps);
|
|
237
|
+
this._updateStyleComponents(props, oldProps);
|
|
238
|
+
this._updateHandlers(props, oldProps);
|
|
239
|
+
|
|
240
|
+
// If 1) view state has changed to match props and
|
|
241
|
+
// 2) the props change is not triggered by map events,
|
|
242
|
+
// it's driven by an external state change. Redraw immediately
|
|
243
|
+
if (settingsChanged || sizeChanged || (viewStateChanged && !this._map.isMoving())) {
|
|
244
|
+
this.redraw();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static reuse(props: MapboxProps, container: HTMLDivElement): Mapbox {
|
|
249
|
+
const that = Mapbox.savedMaps.pop();
|
|
250
|
+
if (!that) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const map = that.map;
|
|
255
|
+
// When reusing the saved map, we need to reparent the map(canvas) and other child nodes
|
|
256
|
+
// intoto the new container from the props.
|
|
257
|
+
// Step 1: reparenting child nodes from old container to new container
|
|
258
|
+
const oldContainer = map.getContainer();
|
|
259
|
+
container.className = oldContainer.className;
|
|
260
|
+
while (oldContainer.childNodes.length > 0) {
|
|
261
|
+
container.appendChild(oldContainer.childNodes[0]);
|
|
262
|
+
}
|
|
263
|
+
// Step 2: replace the internal container with new container from the react component
|
|
264
|
+
// @ts-ignore
|
|
265
|
+
map._container = container;
|
|
266
|
+
|
|
267
|
+
// Step 4: apply new props
|
|
268
|
+
that.setProps({...props, styleDiffing: false});
|
|
269
|
+
map.resize();
|
|
270
|
+
const {initialViewState} = props;
|
|
271
|
+
if (initialViewState) {
|
|
272
|
+
if (initialViewState.bounds) {
|
|
273
|
+
map.fitBounds(initialViewState.bounds, {...initialViewState.fitBoundsOptions, duration: 0});
|
|
274
|
+
} else {
|
|
275
|
+
that._updateViewState(initialViewState, false);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Simulate load event
|
|
280
|
+
if (map.isStyleLoaded()) {
|
|
281
|
+
map.fire('load');
|
|
282
|
+
} else {
|
|
283
|
+
map.once('styledata', () => map.fire('load'));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Force reload
|
|
287
|
+
// @ts-ignore
|
|
288
|
+
map._update();
|
|
289
|
+
return that;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* eslint-disable complexity,max-statements */
|
|
293
|
+
_initialize(container: HTMLDivElement) {
|
|
294
|
+
const {props} = this;
|
|
295
|
+
const {mapStyle = DEFAULT_STYLE} = props;
|
|
296
|
+
const mapOptions = {
|
|
297
|
+
...props,
|
|
298
|
+
...props.initialViewState,
|
|
299
|
+
accessToken: props.mapboxAccessToken || getAccessTokenFromEnv() || null,
|
|
300
|
+
container,
|
|
301
|
+
style: normalizeStyle(mapStyle)
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const viewState = mapOptions.initialViewState || mapOptions.viewState || mapOptions;
|
|
305
|
+
Object.assign(mapOptions, {
|
|
306
|
+
center: [viewState.longitude || 0, viewState.latitude || 0],
|
|
307
|
+
zoom: viewState.zoom || 0,
|
|
308
|
+
pitch: viewState.pitch || 0,
|
|
309
|
+
bearing: viewState.bearing || 0
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
if (props.gl) {
|
|
313
|
+
// eslint-disable-next-line
|
|
314
|
+
const getContext = HTMLCanvasElement.prototype.getContext;
|
|
315
|
+
// Hijack canvas.getContext to return our own WebGLContext
|
|
316
|
+
// This will be called inside the mapboxgl.Map constructor
|
|
317
|
+
// @ts-expect-error
|
|
318
|
+
HTMLCanvasElement.prototype.getContext = () => {
|
|
319
|
+
// Unhijack immediately
|
|
320
|
+
HTMLCanvasElement.prototype.getContext = getContext;
|
|
321
|
+
return props.gl;
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const map = new this._MapClass(mapOptions);
|
|
326
|
+
// Props that are not part of constructor options
|
|
327
|
+
if (viewState.padding) {
|
|
328
|
+
map.setPadding(viewState.padding);
|
|
329
|
+
}
|
|
330
|
+
if (props.cursor) {
|
|
331
|
+
map.getCanvas().style.cursor = props.cursor;
|
|
332
|
+
}
|
|
333
|
+
this._createProxyTransform(map);
|
|
334
|
+
|
|
335
|
+
// Hack
|
|
336
|
+
// Insert code into map's render cycle
|
|
337
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
338
|
+
const renderMap = map._render;
|
|
339
|
+
map._render = (arg: number) => {
|
|
340
|
+
// Hijacked to set this state flag
|
|
341
|
+
this._inRender = true;
|
|
342
|
+
renderMap.call(map, arg);
|
|
343
|
+
this._inRender = false;
|
|
344
|
+
};
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
346
|
+
const runRenderTaskQueue = map._renderTaskQueue.run;
|
|
347
|
+
map._renderTaskQueue.run = (arg: number) => {
|
|
348
|
+
// This is where camera updates from input handler/animation happens
|
|
349
|
+
// And where all view state change events are fired
|
|
350
|
+
this._proxyTransform.$internalUpdate = true;
|
|
351
|
+
runRenderTaskQueue.call(map._renderTaskQueue, arg);
|
|
352
|
+
this._proxyTransform.$internalUpdate = false;
|
|
353
|
+
this._fireDefferedEvents();
|
|
354
|
+
};
|
|
355
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
356
|
+
const jumpTo = map.jumpTo;
|
|
357
|
+
map.jumpTo = (...args: Parameters<MapInstance['jumpTo']>) => {
|
|
358
|
+
// This method will fire view state change events immediately
|
|
359
|
+
this._proxyTransform.$internalUpdate = true;
|
|
360
|
+
jumpTo.apply(map, args);
|
|
361
|
+
this._proxyTransform.$internalUpdate = false;
|
|
362
|
+
return map;
|
|
363
|
+
};
|
|
364
|
+
// Insert code into map's event pipeline
|
|
365
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
366
|
+
const fireEvent = map.fire;
|
|
367
|
+
map.fire = this._fireEvent.bind(this, fireEvent);
|
|
368
|
+
|
|
369
|
+
// add listeners
|
|
370
|
+
map.on('styledata', () => {
|
|
371
|
+
this._updateStyleComponents(this.props, {});
|
|
372
|
+
});
|
|
373
|
+
map.on('sourcedata', () => {
|
|
374
|
+
this._updateStyleComponents(this.props, {});
|
|
375
|
+
});
|
|
376
|
+
for (const eventName in pointerEvents) {
|
|
377
|
+
map.on(eventName, this._onPointerEvent);
|
|
378
|
+
}
|
|
379
|
+
for (const eventName in cameraEvents) {
|
|
380
|
+
map.on(eventName, this._onCameraEvent);
|
|
381
|
+
}
|
|
382
|
+
for (const eventName in otherEvents) {
|
|
383
|
+
map.on(eventName, this._onEvent);
|
|
384
|
+
}
|
|
385
|
+
this._map = map;
|
|
386
|
+
}
|
|
387
|
+
/* eslint-enable complexity,max-statements */
|
|
388
|
+
|
|
389
|
+
recycle() {
|
|
390
|
+
// Clean up unnecessary elements before storing for reuse.
|
|
391
|
+
const container = this.map.getContainer();
|
|
392
|
+
const children = container.querySelector('[mapboxgl-children]');
|
|
393
|
+
children?.remove();
|
|
394
|
+
|
|
395
|
+
Mapbox.savedMaps.push(this);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
destroy() {
|
|
399
|
+
this._map.remove();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Force redraw the map now. Typically resize() and jumpTo() is reflected in the next
|
|
403
|
+
// render cycle, which is managed by Mapbox's animation loop.
|
|
404
|
+
// This removes the synchronization issue caused by requestAnimationFrame.
|
|
405
|
+
redraw() {
|
|
406
|
+
const map = this._map as any;
|
|
407
|
+
// map._render will throw error if style does not exist
|
|
408
|
+
// https://github.com/mapbox/mapbox-gl-js/blob/fb9fc316da14e99ff4368f3e4faa3888fb43c513
|
|
409
|
+
// /src/ui/map.js#L1834
|
|
410
|
+
if (!this._inRender && map.style) {
|
|
411
|
+
// cancel the scheduled update
|
|
412
|
+
if (map._frame) {
|
|
413
|
+
map._frame.cancel();
|
|
414
|
+
map._frame = null;
|
|
415
|
+
}
|
|
416
|
+
// the order is important - render() may schedule another update
|
|
417
|
+
map._render();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
_createProxyTransform(map: any) {
|
|
422
|
+
const proxyTransform = createProxyTransform(map.transform);
|
|
423
|
+
map.transform = proxyTransform;
|
|
424
|
+
map.painter.transform = proxyTransform;
|
|
425
|
+
this._proxyTransform = proxyTransform;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* Trigger map resize if size is controlled
|
|
429
|
+
@param {object} nextProps
|
|
430
|
+
@returns {bool} true if size has changed
|
|
431
|
+
*/
|
|
432
|
+
_updateSize(nextProps: MapboxProps): boolean {
|
|
433
|
+
// Check if size is controlled
|
|
434
|
+
const {viewState} = nextProps;
|
|
435
|
+
if (viewState) {
|
|
436
|
+
const map = this._map;
|
|
437
|
+
if (viewState.width !== map.transform.width || viewState.height !== map.transform.height) {
|
|
438
|
+
map.resize();
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Adapted from map.jumpTo
|
|
446
|
+
/* Update camera to match props
|
|
447
|
+
@param {object} nextProps
|
|
448
|
+
@param {bool} triggerEvents - should fire camera events
|
|
449
|
+
@returns {bool} true if anything is changed
|
|
450
|
+
*/
|
|
451
|
+
_updateViewState(nextProps: MapboxProps, triggerEvents: boolean): boolean {
|
|
452
|
+
const viewState: Partial<ViewState> = nextProps.viewState || nextProps;
|
|
453
|
+
const tr = this._proxyTransform;
|
|
454
|
+
const {zoom, pitch, bearing} = tr;
|
|
455
|
+
const changed = compareViewStateWithTransform(this._proxyTransform, viewState);
|
|
456
|
+
tr.$reactViewState = viewState;
|
|
457
|
+
|
|
458
|
+
if (changed && triggerEvents) {
|
|
459
|
+
const deferredEvents = this._deferredEvents;
|
|
460
|
+
// Delay DOM control updates to the next render cycle
|
|
461
|
+
deferredEvents.move = true;
|
|
462
|
+
deferredEvents.zoom ||= zoom !== tr.zoom;
|
|
463
|
+
deferredEvents.rotate ||= bearing !== tr.bearing;
|
|
464
|
+
deferredEvents.pitch ||= pitch !== tr.pitch;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return changed;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/* Update camera constraints and projection settings to match props
|
|
471
|
+
@param {object} nextProps
|
|
472
|
+
@param {object} currProps
|
|
473
|
+
@returns {bool} true if anything is changed
|
|
474
|
+
*/
|
|
475
|
+
_updateSettings(nextProps: MapboxProps, currProps: MapboxProps): boolean {
|
|
476
|
+
const map = this._map;
|
|
477
|
+
let changed = false;
|
|
478
|
+
for (const propName of settingNames) {
|
|
479
|
+
const propPresent = propName in nextProps || propName in currProps;
|
|
480
|
+
|
|
481
|
+
if (propPresent && !deepEqual(nextProps[propName], currProps[propName])) {
|
|
482
|
+
changed = true;
|
|
483
|
+
const nextValue = propName in nextProps ? nextProps[propName] : DEFAULT_SETTINGS[propName];
|
|
484
|
+
const setter = map[`set${propName[0].toUpperCase()}${propName.slice(1)}`];
|
|
485
|
+
setter?.call(map, nextValue);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return changed;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* Update map style to match props
|
|
492
|
+
@param {object} nextProps
|
|
493
|
+
@param {object} currProps
|
|
494
|
+
@returns {bool} true if style is changed
|
|
495
|
+
*/
|
|
496
|
+
_updateStyle(nextProps: MapboxProps, currProps: MapboxProps): boolean {
|
|
497
|
+
if (nextProps.cursor !== currProps.cursor) {
|
|
498
|
+
this._map.getCanvas().style.cursor = nextProps.cursor || '';
|
|
499
|
+
}
|
|
500
|
+
if (nextProps.mapStyle !== currProps.mapStyle) {
|
|
501
|
+
const {mapStyle = DEFAULT_STYLE, styleDiffing = true} = nextProps;
|
|
502
|
+
const options: any = {
|
|
503
|
+
diff: styleDiffing
|
|
504
|
+
};
|
|
505
|
+
if ('localIdeographFontFamily' in nextProps) {
|
|
506
|
+
// @ts-ignore Mapbox specific prop
|
|
507
|
+
options.localIdeographFontFamily = nextProps.localIdeographFontFamily;
|
|
508
|
+
}
|
|
509
|
+
this._map.setStyle(normalizeStyle(mapStyle), options);
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* Update fog, light and terrain to match props
|
|
516
|
+
@param {object} nextProps
|
|
517
|
+
@param {object} currProps
|
|
518
|
+
@returns {bool} true if anything is changed
|
|
519
|
+
*/
|
|
520
|
+
_updateStyleComponents(nextProps: MapboxProps, currProps: MapboxProps): boolean {
|
|
521
|
+
const map = this._map;
|
|
522
|
+
let changed = false;
|
|
523
|
+
if (map.isStyleLoaded()) {
|
|
524
|
+
if ('light' in nextProps && map.setLight && !deepEqual(nextProps.light, currProps.light)) {
|
|
525
|
+
changed = true;
|
|
526
|
+
map.setLight(nextProps.light);
|
|
527
|
+
}
|
|
528
|
+
if ('fog' in nextProps && map.setFog && !deepEqual(nextProps.fog, currProps.fog)) {
|
|
529
|
+
changed = true;
|
|
530
|
+
map.setFog(nextProps.fog);
|
|
531
|
+
}
|
|
532
|
+
if (
|
|
533
|
+
'terrain' in nextProps &&
|
|
534
|
+
map.setTerrain &&
|
|
535
|
+
!deepEqual(nextProps.terrain, currProps.terrain)
|
|
536
|
+
) {
|
|
537
|
+
if (!nextProps.terrain || map.getSource(nextProps.terrain.source)) {
|
|
538
|
+
changed = true;
|
|
539
|
+
map.setTerrain(nextProps.terrain);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return changed;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* Update interaction handlers to match props
|
|
547
|
+
@param {object} nextProps
|
|
548
|
+
@param {object} currProps
|
|
549
|
+
@returns {bool} true if anything is changed
|
|
550
|
+
*/
|
|
551
|
+
_updateHandlers(nextProps: MapboxProps, currProps: MapboxProps): boolean {
|
|
552
|
+
const map = this._map;
|
|
553
|
+
let changed = false;
|
|
554
|
+
for (const propName of handlerNames) {
|
|
555
|
+
const newValue = nextProps[propName] ?? true;
|
|
556
|
+
const oldValue = currProps[propName] ?? true;
|
|
557
|
+
if (!deepEqual(newValue, oldValue)) {
|
|
558
|
+
changed = true;
|
|
559
|
+
if (newValue) {
|
|
560
|
+
map[propName].enable(newValue);
|
|
561
|
+
} else {
|
|
562
|
+
map[propName].disable();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return changed;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
_onEvent = (e: MapEvent) => {
|
|
570
|
+
// @ts-ignore
|
|
571
|
+
const cb = this.props[otherEvents[e.type]];
|
|
572
|
+
if (cb) {
|
|
573
|
+
cb(e);
|
|
574
|
+
} else if (e.type === 'error') {
|
|
575
|
+
console.error((e as ErrorEvent).error); // eslint-disable-line
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
private _queryRenderedFeatures(point: Point) {
|
|
580
|
+
const map = this._map;
|
|
581
|
+
const {interactiveLayerIds = []} = this.props;
|
|
582
|
+
try {
|
|
583
|
+
return map.queryRenderedFeatures(point, {
|
|
584
|
+
layers: interactiveLayerIds.filter(map.getLayer.bind(map))
|
|
585
|
+
});
|
|
586
|
+
} catch {
|
|
587
|
+
// May fail if style is not loaded
|
|
588
|
+
return [];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
_updateHover(e: MapMouseEvent) {
|
|
593
|
+
const {props} = this;
|
|
594
|
+
const shouldTrackHoveredFeatures =
|
|
595
|
+
props.interactiveLayerIds && (props.onMouseMove || props.onMouseEnter || props.onMouseLeave);
|
|
596
|
+
|
|
597
|
+
if (shouldTrackHoveredFeatures) {
|
|
598
|
+
const eventType = e.type;
|
|
599
|
+
const wasHovering = this._hoveredFeatures?.length > 0;
|
|
600
|
+
const features = this._queryRenderedFeatures(e.point);
|
|
601
|
+
const isHovering = features.length > 0;
|
|
602
|
+
|
|
603
|
+
if (!isHovering && wasHovering) {
|
|
604
|
+
e.type = 'mouseleave';
|
|
605
|
+
this._onPointerEvent(e);
|
|
606
|
+
}
|
|
607
|
+
this._hoveredFeatures = features;
|
|
608
|
+
if (isHovering && !wasHovering) {
|
|
609
|
+
e.type = 'mouseenter';
|
|
610
|
+
this._onPointerEvent(e);
|
|
611
|
+
}
|
|
612
|
+
e.type = eventType;
|
|
613
|
+
} else {
|
|
614
|
+
this._hoveredFeatures = null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
_onPointerEvent = (e: MapMouseEvent) => {
|
|
619
|
+
if (e.type === 'mousemove' || e.type === 'mouseout') {
|
|
620
|
+
this._updateHover(e);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// @ts-ignore
|
|
624
|
+
const cb = this.props[pointerEvents[e.type]];
|
|
625
|
+
if (cb) {
|
|
626
|
+
if (this.props.interactiveLayerIds && e.type !== 'mouseover' && e.type !== 'mouseout') {
|
|
627
|
+
e.features = this._hoveredFeatures || this._queryRenderedFeatures(e.point);
|
|
628
|
+
}
|
|
629
|
+
cb(e);
|
|
630
|
+
delete e.features;
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
_onCameraEvent = (e: ViewStateChangeEvent) => {
|
|
635
|
+
if (!this._internalUpdate) {
|
|
636
|
+
// @ts-ignore
|
|
637
|
+
const cb = this.props[cameraEvents[e.type]];
|
|
638
|
+
const tr = this._proxyTransform;
|
|
639
|
+
if (cb) {
|
|
640
|
+
e.viewState = transformToViewState(tr.$proposedTransform ?? tr);
|
|
641
|
+
cb(e);
|
|
642
|
+
}
|
|
643
|
+
if (e.type === 'moveend') {
|
|
644
|
+
tr.$proposedTransform = null;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (e.type in this._deferredEvents) {
|
|
648
|
+
this._deferredEvents[e.type] = false;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
_fireEvent(baseFire: Function, event: string | MapEvent, properties?: object) {
|
|
653
|
+
const map = this._map;
|
|
654
|
+
const tr = this._proxyTransform;
|
|
655
|
+
|
|
656
|
+
// Always expose the controlled transform to controls/end user
|
|
657
|
+
const internal = tr.$internalUpdate;
|
|
658
|
+
try {
|
|
659
|
+
tr.$internalUpdate = false;
|
|
660
|
+
baseFire.call(map, event, properties);
|
|
661
|
+
} finally {
|
|
662
|
+
tr.$internalUpdate = internal;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return map;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// If there are camera changes driven by props, invoke camera events so that DOM controls are synced
|
|
669
|
+
_fireDefferedEvents() {
|
|
670
|
+
const map = this._map;
|
|
671
|
+
this._internalUpdate = true;
|
|
672
|
+
for (const eventType in this._deferredEvents) {
|
|
673
|
+
if (this._deferredEvents[eventType]) {
|
|
674
|
+
map.fire(eventType);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
this._internalUpdate = false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Access token can be provided via one of:
|
|
683
|
+
* mapboxAccessToken prop
|
|
684
|
+
* access_token query parameter
|
|
685
|
+
* MapboxAccessToken environment variable
|
|
686
|
+
* REACT_APP_MAPBOX_ACCESS_TOKEN environment variable
|
|
687
|
+
* @returns access token
|
|
688
|
+
*/
|
|
689
|
+
function getAccessTokenFromEnv(): string {
|
|
690
|
+
let accessToken = null;
|
|
691
|
+
|
|
692
|
+
/* global location, process */
|
|
693
|
+
if (typeof location !== 'undefined') {
|
|
694
|
+
const match = /access_token=([^&\/]*)/.exec(location.search);
|
|
695
|
+
accessToken = match && match[1];
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Note: This depends on bundler plugins (e.g. webpack) importing environment correctly
|
|
699
|
+
try {
|
|
700
|
+
// eslint-disable-next-line no-process-env
|
|
701
|
+
accessToken = accessToken || process.env.MapboxAccessToken;
|
|
702
|
+
} catch {
|
|
703
|
+
// ignore
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
try {
|
|
707
|
+
// eslint-disable-next-line no-process-env
|
|
708
|
+
accessToken = accessToken || process.env.REACT_APP_MAPBOX_ACCESS_TOKEN;
|
|
709
|
+
} catch {
|
|
710
|
+
// ignore
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return accessToken;
|
|
714
|
+
}
|