@carto/kepler.gl 0.0.88 → 0.0.89

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.
Files changed (53) hide show
  1. package/dist/components/common/animation-control/playback-controls.js +11 -21
  2. package/dist/components/common/info-helper.js +3 -5
  3. package/dist/components/common/styled-components.d.ts +3 -2
  4. package/dist/components/common/styled-components.js +13 -14
  5. package/dist/components/editor/editor.js +2 -2
  6. package/dist/components/editor/feature-styles.js +2 -2
  7. package/dist/components/editor/handle-style.js +2 -2
  8. package/dist/components/map/layer-selector-panel.js +2 -3
  9. package/dist/components/map/map-control-tooltip.js +2 -3
  10. package/dist/components/side-panel/common/dataset-tag.js +4 -7
  11. package/dist/components/side-panel/common/dataset-title.js +5 -9
  12. package/dist/components/side-panel/layer-panel/color-range-selector.js +3 -7
  13. package/dist/components/side-panel/layer-panel/layer-config-group.js +2 -4
  14. package/dist/components/side-panel/layer-panel/panel-view-list-toggle.js +2 -4
  15. package/dist/components/side-panel/panel-header-action.d.ts +2 -2
  16. package/dist/components/side-panel/panel-header-action.js +3 -5
  17. package/dist/components/side-panel/panel-header.js +3 -5
  18. package/dist/components/side-panel/panel-tab.js +2 -4
  19. package/dist/constants/default-settings.js +3 -3
  20. package/dist/vendor/react-map-gl-draw/constants.js +55 -0
  21. package/dist/vendor/react-map-gl-draw/editor.js +1122 -0
  22. package/dist/vendor/react-map-gl-draw/feature.js +191 -0
  23. package/dist/vendor/react-map-gl-draw/index.js +33 -0
  24. package/dist/vendor/react-map-gl-draw/style.js +93 -0
  25. package/dist/vendor/react-map-gl-draw/utils.js +51 -0
  26. package/package.json +5 -4
  27. package/src/components/common/animation-control/playback-controls.tsx +11 -16
  28. package/src/components/common/info-helper.tsx +2 -3
  29. package/src/components/common/styled-components.tsx +17 -31
  30. package/src/components/editor/editor.tsx +1 -1
  31. package/src/components/editor/feature-styles.ts +1 -1
  32. package/src/components/editor/handle-style.ts +1 -1
  33. package/src/components/map/layer-selector-panel.tsx +1 -2
  34. package/src/components/map/map-control-tooltip.tsx +1 -1
  35. package/src/components/side-panel/common/dataset-tag.tsx +3 -5
  36. package/src/components/side-panel/common/dataset-title.tsx +4 -8
  37. package/src/components/side-panel/layer-panel/color-range-selector.tsx +4 -6
  38. package/src/components/side-panel/layer-panel/layer-config-group.tsx +2 -2
  39. package/src/components/side-panel/layer-panel/panel-view-list-toggle.tsx +2 -3
  40. package/src/components/side-panel/panel-header-action.tsx +4 -5
  41. package/src/components/side-panel/panel-header.tsx +2 -2
  42. package/src/components/side-panel/panel-tab.tsx +2 -2
  43. package/src/constants/default-settings.ts +1 -1
  44. package/src/vendor/react-map-gl-draw/LICENSE +9 -0
  45. package/src/vendor/react-map-gl-draw/constants.js +70 -0
  46. package/src/vendor/react-map-gl-draw/editor.js +1207 -0
  47. package/src/vendor/react-map-gl-draw/feature.js +171 -0
  48. package/src/vendor/react-map-gl-draw/index.js +7 -0
  49. package/src/vendor/react-map-gl-draw/style.js +153 -0
  50. package/src/vendor/react-map-gl-draw/utils.js +55 -0
  51. package/CHANGELOG.md +0 -1015
  52. package/src/.DS_Store +0 -0
  53. package/src/reducers/trips-to-geojson.mjs +0 -8
@@ -0,0 +1,1207 @@
1
+ // Vendored from react-map-gl-draw@0.14.8 (MIT, Copyright (c) 2018 Uber Technologies, Inc.), see ./LICENSE
2
+ /* eslint-disable complexity, max-statements */
3
+ import React, {PureComponent} from 'react';
4
+ import {_MapContext as MapContext} from 'react-map-gl';
5
+ import {v4 as uuid} from 'uuid';
6
+
7
+ import Feature from './feature';
8
+ import {
9
+ getEditHandleStyle as defaultGetEditHandleStyle,
10
+ getFeatureStyle as defaultGetFeatureStyle,
11
+ HIDDEN_CLICKABLE_STYLE
12
+ } from './style';
13
+
14
+ import {
15
+ OPERATIONS,
16
+ MODES,
17
+ DRAWING_MODES,
18
+ MODE_TO_GEOJSON_TYPE,
19
+ MODE_TO_RENDER_TYPE,
20
+ RENDER_STATE,
21
+ RENDER_TYPE,
22
+ STATIC_STYLE,
23
+ ELEMENT_TYPE
24
+ } from './constants';
25
+ import {parseElemDataAttributes, findClosestPointOnLineSegment} from './utils';
26
+
27
+ const defaultProps = {
28
+ mode: MODES.READ_ONLY,
29
+ clickRadius: 0,
30
+ getEditHandleStyle: defaultGetEditHandleStyle,
31
+ getFeatureStyle: defaultGetFeatureStyle,
32
+ getFeatureShape: 'circle',
33
+ getEditHandleShape: 'circle',
34
+ onSelect: () => {}
35
+ };
36
+
37
+ const UNCOMMITTED_ID = 'uncommitted';
38
+
39
+ export default class Editor extends PureComponent {
40
+ static defaultProps = defaultProps;
41
+
42
+ constructor(props) {
43
+ super(props);
44
+ this.state = {
45
+ features: props.features
46
+ ? props.features.map(f => Feature.fromFeature(f)).filter(Boolean)
47
+ : null,
48
+
49
+ selectedFeatureId: -1,
50
+
51
+ hoveredFeatureId: null,
52
+ hoveredLngLat: null,
53
+ hoveredVertexIndex: -1,
54
+
55
+ // intermediate mouse position when drawing
56
+ uncommittedLngLat: null,
57
+
58
+ draggingVertexIndex: -1,
59
+ startDragPos: null,
60
+ isDragging: false,
61
+ didDrag: false
62
+ };
63
+
64
+ this._containerRef = null;
65
+ this._events = {};
66
+ this._context = null;
67
+ }
68
+
69
+ componentDidMount() {
70
+ if (this.props.mode && this.props.mode !== MODES.READ_ONLY) {
71
+ this._setupEvents();
72
+ }
73
+ }
74
+
75
+ componentWillReceiveProps(nextProps) {
76
+ if (this.props.mode !== nextProps.mode) {
77
+ if (!nextProps.mode || nextProps.mode === MODES.READ_ONLY) {
78
+ this._removeEvents();
79
+ }
80
+ if (!this.props.mode || this.props.mode === MODES.READ_ONLY) {
81
+ this._setupEvents();
82
+ }
83
+ }
84
+
85
+ if (this.props.mode !== nextProps.mode || this.props.features !== nextProps.features) {
86
+ this.setState({
87
+ features:
88
+ nextProps.features && nextProps.features.map(f => Feature.fromFeature(f)).filter(Boolean)
89
+ });
90
+ }
91
+
92
+ if (
93
+ this.props.mode !== nextProps.mode ||
94
+ this.props.selectedFeatureId !== nextProps.selectedFeatureId
95
+ ) {
96
+ this._clearCache();
97
+ this.setState({
98
+ selectedFeatureId: nextProps.selectedFeatureId
99
+ });
100
+ }
101
+ }
102
+
103
+ componentWillUnmount() {
104
+ this._removeEvents();
105
+ }
106
+
107
+ _containerRef;
108
+ _events;
109
+ _context;
110
+
111
+ /* FEATURE OPERATIONS */
112
+ _update = features => {
113
+ if (features) {
114
+ this.props.onUpdate(features.map(f => f.toFeature()));
115
+ }
116
+ };
117
+
118
+ _updateRectangle = (feature, options) => {
119
+ const {vertexIndex, lngLat} = options;
120
+ /*
121
+ * p0.x, p0.y ---------- diagonal.x, p0.y
122
+ * | |
123
+ * | |
124
+ * p0.x, diagonal.y ----- diagonal.x, diagonal.y
125
+ */
126
+ const diagonal = vertexIndex;
127
+ const p0 = feature.points[(diagonal + 2) % 4];
128
+
129
+ feature.replacePoint(diagonal, [lngLat[0], lngLat[1]]);
130
+ feature.replacePoint((diagonal + 1) % 4, [lngLat[0], p0[1]]);
131
+ feature.replacePoint((diagonal + 3) % 4, [p0[0], lngLat[1]]);
132
+
133
+ this._update(this.state.features);
134
+ };
135
+
136
+ _updateFeature = (feature, mode, options) => {
137
+ switch (mode) {
138
+ case 'vertex':
139
+ if (feature.renderType === RENDER_TYPE.RECTANGLE) {
140
+ this._updateRectangle(feature, options);
141
+ } else {
142
+ feature.replacePoint(options.vertexIndex, [options.lngLat[0], options.lngLat[1]]);
143
+ this._update(this.state.features);
144
+ }
145
+ break;
146
+
147
+ case 'feature':
148
+ const {dx, dy} = options;
149
+ feature.points = feature.points
150
+ .map(lngLat => {
151
+ const pixels = this._project(lngLat);
152
+ if (pixels) {
153
+ pixels[0] += dx;
154
+ pixels[1] += dy;
155
+ return this._unproject(pixels);
156
+ }
157
+ return null;
158
+ })
159
+ .filter(Boolean);
160
+
161
+ this._update(this.state.features);
162
+ break;
163
+
164
+ case 'Rectangle':
165
+ this._updateRectangle(feature, options);
166
+ break;
167
+
168
+ default:
169
+ }
170
+ };
171
+
172
+ _addPoint = (x, y, feature, isNew = false) => {
173
+ feature = feature || this._getSelectedFeature();
174
+
175
+ if (!feature) {
176
+ return;
177
+ }
178
+
179
+ const lngLat = this._unproject([x, y]);
180
+ if (!lngLat) {
181
+ return;
182
+ }
183
+
184
+ feature.addPoint([lngLat[0], lngLat[1]]);
185
+
186
+ const features = this.state.features || [];
187
+ if (isNew) {
188
+ features.push(feature);
189
+ }
190
+
191
+ const validPath = feature.points && feature.points.length >= 2;
192
+ const {mode, onSelect} = this.props;
193
+
194
+ if (mode === MODES.DRAW_POINT || (mode === MODES.DRAW_PATH && validPath)) {
195
+ this._update(features);
196
+ onSelect({selectedFeatureId: feature && feature.id});
197
+ } else {
198
+ this.setState({
199
+ features: [...features],
200
+ selectedFeatureId: feature && feature.id
201
+ });
202
+ }
203
+ };
204
+
205
+ _clearCache = () => {
206
+ this.setState({
207
+ selectedFeatureId: null,
208
+ uncommittedLngLat: null,
209
+
210
+ hoveredFeatureId: null,
211
+ hoveredLngLat: null,
212
+ hoveredVertexIndex: -1,
213
+
214
+ draggingVertexIndex: -1,
215
+ startDragPos: null,
216
+ isDragging: false,
217
+ didDrag: false
218
+ });
219
+ };
220
+
221
+ _closePath = () => {
222
+ const selectedFeature = this._getSelectedFeature();
223
+ if (selectedFeature) {
224
+ selectedFeature.closePath();
225
+ this._update(this.state.features);
226
+ }
227
+ };
228
+
229
+ _addFeature = (mode, point) => {
230
+ const type = MODE_TO_GEOJSON_TYPE[mode];
231
+ const renderType = MODE_TO_RENDER_TYPE[mode];
232
+
233
+ const feature = new Feature({
234
+ id: uuid(),
235
+ type,
236
+ renderType
237
+ });
238
+
239
+ this._addPoint(point.x, point.y, feature, true);
240
+
241
+ if (mode === MODES.DRAW_RECTANGLE) {
242
+ for (let i = 0; i < 3; i++) {
243
+ this._addPoint(point.x, point.y, feature, false);
244
+ }
245
+ }
246
+ };
247
+
248
+ /* EVENTS */
249
+ _setupEvents() {
250
+ const ref = this._containerRef;
251
+
252
+ if (!ref || !this._context || !this._context.eventManager) {
253
+ return;
254
+ }
255
+
256
+ this._events = {
257
+ anyclick: evt => this._onEvent(this._onClick, evt, true),
258
+ click: evt => evt.stopImmediatePropagation(),
259
+ pointermove: evt => this._onEvent(this._onMouseMove, evt, true),
260
+ pointerdown: evt => this._onEvent(this._onMouseDown, evt, true),
261
+ pointerup: evt => this._onEvent(this._onMouseUp, evt, true),
262
+ panmove: evt => this._onEvent(this._onPan, evt, false),
263
+ panstart: evt => this._onEvent(this._onPan, evt, false),
264
+ panend: evt => this._onEvent(this._onPan, evt, false)
265
+ };
266
+
267
+ this._context.eventManager.on(this._events, ref);
268
+ }
269
+
270
+ _removeEvents() {
271
+ if (!this._context || !this._context.eventManager || !this._events) {
272
+ return;
273
+ }
274
+ this._context.eventManager.off(this._events);
275
+ this._events = null;
276
+ }
277
+
278
+ _onEvent = (handler, evt, stopPropagation, ...args) => {
279
+ const {mode} = this.props;
280
+ if (mode === MODES.READ_ONLY) {
281
+ return;
282
+ }
283
+
284
+ handler(evt, ...args);
285
+
286
+ if (stopPropagation) {
287
+ evt.stopImmediatePropagation();
288
+ }
289
+ };
290
+
291
+ _onMouseUp = evt => {
292
+ this.setState({
293
+ isDragging: false,
294
+ didDrag: false
295
+ });
296
+ const {draggingVertexIndex} = this.state;
297
+
298
+ if (Number(draggingVertexIndex) >= 0) {
299
+ this.setState({
300
+ draggingVertexIndex: -1
301
+ });
302
+ }
303
+ };
304
+
305
+ _onMouseDown = evt => {
306
+ const {x, y} = this._getEventPosition(evt);
307
+ const attributes = parseElemDataAttributes(evt.target);
308
+
309
+ // click segment
310
+ if (attributes && attributes.type === ELEMENT_TYPE.VERTEX) {
311
+ const {vertexIndex} = attributes;
312
+ this.setState({
313
+ draggingVertexIndex: vertexIndex,
314
+ startDragPos: {x, y},
315
+ isDragging: true
316
+ });
317
+
318
+ // click selected feature
319
+ } else if (this._matchesFeature(attributes, this._getSelectedFeature())) {
320
+ this.setState({
321
+ startDragPos: {x, y},
322
+ isDragging: true
323
+ });
324
+ }
325
+ };
326
+
327
+ /* eslint-disable max-depth */
328
+ _onMouseMove = evt => {
329
+ const attributes = parseElemDataAttributes(evt.target) || {};
330
+ const {vertexIndex, featureIndex, type} = attributes;
331
+
332
+ const {startDragPos, isDragging, didDrag} = this.state;
333
+ const {mode} = this.props;
334
+ const {x, y} = this._getEventPosition(evt);
335
+ const lngLat = this._unproject([x, y]);
336
+
337
+ if (isDragging && !didDrag && startDragPos) {
338
+ const dx = x - startDragPos.x;
339
+ const dy = y - startDragPos.y;
340
+ if (dx * dx + dy * dy > 5) {
341
+ this.setState({didDrag: true});
342
+ }
343
+ }
344
+
345
+ const selectedFeature = this._getSelectedFeature();
346
+ const isDrawing = DRAWING_MODES.indexOf(mode) !== -1;
347
+ const isEditing = mode === MODES.EDIT_VERTEX;
348
+
349
+ if (selectedFeature) {
350
+ // dragging
351
+ if (didDrag && startDragPos) {
352
+ const draggingVertexIndex = Number(this.state.draggingVertexIndex);
353
+
354
+ if (draggingVertexIndex >= 0) {
355
+ // dragging vertex
356
+ this._updateFeature(selectedFeature, 'vertex', {
357
+ vertexIndex: draggingVertexIndex,
358
+ lngLat
359
+ });
360
+ } else {
361
+ // dragging feature
362
+ const dx = x - startDragPos.x;
363
+ const dy = y - startDragPos.y;
364
+ this.setState({startDragPos: {x, y}});
365
+
366
+ this._updateFeature(selectedFeature, 'feature', {dx, dy});
367
+ }
368
+ } else if (mode === MODES.DRAW_RECTANGLE) {
369
+ // drawing rectangle
370
+ this._updateFeature(selectedFeature, 'Rectangle', {vertexIndex: 2, lngLat});
371
+ } else if (isDrawing) {
372
+ // drawing other shapes
373
+ this.setState({uncommittedLngLat: lngLat});
374
+ } else if (isEditing) {
375
+ if (
376
+ (selectedFeature.renderType === RENDER_TYPE.LINE_STRING ||
377
+ selectedFeature.renderType === RENDER_TYPE.POLYGON) &&
378
+ type === ELEMENT_TYPE.SEGMENT
379
+ ) {
380
+ // segmentId is start vertexIndex
381
+ let uncommittedLngLat = null;
382
+ if (lngLat && typeof vertexIndex === 'number') {
383
+ uncommittedLngLat = this._getClosestPositionOnSegment(
384
+ vertexIndex,
385
+ lngLat,
386
+ selectedFeature
387
+ );
388
+ }
389
+
390
+ this.setState({
391
+ uncommittedLngLat
392
+ });
393
+ } else {
394
+ this.setState({
395
+ uncommittedLngLat: null
396
+ });
397
+ }
398
+ }
399
+ }
400
+
401
+ const {features, selectedFeatureId} = this.state;
402
+ if (selectedFeatureId && type === ELEMENT_TYPE.VERTEX && typeof featureIndex === 'number') {
403
+ const feature = features && features[featureIndex];
404
+ if (selectedFeatureId === (feature && feature.id)) {
405
+ this.setState({
406
+ hoveredVertexIndex: vertexIndex
407
+ });
408
+ }
409
+ } else if (type !== ELEMENT_TYPE.VERTEX) {
410
+ this.setState({
411
+ hoveredVertexIndex: null
412
+ });
413
+ }
414
+
415
+ if (type === ELEMENT_TYPE.FEATURE && typeof featureIndex === 'number') {
416
+ const feature = features && features[featureIndex];
417
+ this.setState({
418
+ hoveredFeatureId: feature && feature.id
419
+ });
420
+ } else {
421
+ this.setState({
422
+ hoveredFeatureId: null
423
+ });
424
+ }
425
+ };
426
+ /* eslint-enable max-depth */
427
+
428
+ _onClickFeature = (evt, attributes) => {
429
+ const {featureIndex} = attributes;
430
+ const {features} = this.state;
431
+ const selectedFeature = features && typeof featureIndex === 'number' && features[featureIndex];
432
+
433
+ if (selectedFeature) {
434
+ this.props.onSelect({
435
+ selectedFeatureId: selectedFeature.id,
436
+ sourceEvent: evt
437
+ });
438
+ }
439
+ };
440
+
441
+ _onClickVertex = (evt, attributes) => {
442
+ const {mode} = this.props;
443
+ const operation = attributes.operation;
444
+ if (
445
+ operation === OPERATIONS.INTERSECT ||
446
+ (operation === OPERATIONS.SET && mode === MODES.DRAW_RECTANGLE)
447
+ ) {
448
+ this._closePath();
449
+ this._clearCache();
450
+ }
451
+ };
452
+
453
+ _onClickSegment = (evt, attributes) => {
454
+ const feature = this._getSelectedFeature();
455
+
456
+ if (
457
+ feature &&
458
+ (feature.renderType === RENDER_TYPE.POLYGON ||
459
+ feature.renderType === RENDER_TYPE.LINE_STRING) &&
460
+ attributes
461
+ ) {
462
+ const {vertexIndex} = attributes;
463
+
464
+ const {uncommittedLngLat} = this.state;
465
+
466
+ let lngLat = uncommittedLngLat;
467
+ if (!lngLat && typeof vertexIndex === 'number') {
468
+ const {x, y} = this._getEventPosition(evt);
469
+ lngLat = this._unproject([x, y]);
470
+ lngLat = this._getClosestPositionOnSegment(vertexIndex, lngLat, feature);
471
+ }
472
+
473
+ if (lngLat) {
474
+ const insertPosition = (vertexIndex + 1) % feature.points.length;
475
+ feature.insertPoint(lngLat, insertPosition);
476
+ this._update(this.state.features);
477
+ }
478
+
479
+ this.setState({
480
+ uncommittedLngLat: null,
481
+ hoveredLngLat: null
482
+ });
483
+ }
484
+ };
485
+
486
+ _onClick = evt => {
487
+ const {mode} = this.props;
488
+ const attributes = parseElemDataAttributes(evt.target);
489
+
490
+ if (attributes && attributes.type === ELEMENT_TYPE.VERTEX) {
491
+ this._onClickVertex(evt, attributes);
492
+ return;
493
+ }
494
+
495
+ if (mode === MODES.EDIT_VERTEX && attributes && attributes.type === ELEMENT_TYPE.SEGMENT) {
496
+ this._onClickSegment(evt, attributes);
497
+ return;
498
+ }
499
+
500
+ if (
501
+ (mode === MODES.SELECT_FEATURE || mode === MODES.EDIT_VERTEX) &&
502
+ attributes &&
503
+ attributes.type === ELEMENT_TYPE.FEATURE
504
+ ) {
505
+ this._onClickFeature(evt, attributes);
506
+ return;
507
+ }
508
+
509
+ const selectedFeature = this._getSelectedFeature();
510
+ const {x, y} = this._getEventPosition(evt);
511
+
512
+ switch (mode) {
513
+ case MODES.EDIT_VERTEX:
514
+ if (selectedFeature) {
515
+ this.props.onSelect({
516
+ selectedFeatureId: null,
517
+ sourceEvent: evt
518
+ });
519
+ }
520
+ break;
521
+
522
+ case MODES.DRAW_POINT:
523
+ this._addFeature(mode, {x, y});
524
+ break;
525
+
526
+ case MODES.DRAW_PATH:
527
+ case MODES.DRAW_POLYGON:
528
+ if (selectedFeature && selectedFeature.isClosed) {
529
+ // clicked outside
530
+ this._clearCache();
531
+ } else if (selectedFeature) {
532
+ this._addPoint(x, y, selectedFeature);
533
+ } else {
534
+ this._addFeature(mode, {x, y});
535
+ }
536
+ break;
537
+
538
+ case MODES.DRAW_RECTANGLE:
539
+ if (selectedFeature && selectedFeature.isClosed) {
540
+ // clicked outside
541
+ this._clearCache();
542
+ this.props.onSelect({
543
+ selectedFeatureId: null,
544
+ sourceEvent: evt
545
+ });
546
+ } else {
547
+ this._addFeature(mode, {x, y});
548
+ }
549
+
550
+ break;
551
+
552
+ default:
553
+ }
554
+ };
555
+
556
+ // don't forward pan events to the underlying map when:
557
+ // - the pan target is a vertex/line/the currently selected feature
558
+ // - the user is dragging something around
559
+ // - there is currently an uncommitted position
560
+ // (i.e. the user is creating a feature/vertex/line)
561
+ _onPan = evt => {
562
+ const attributes = parseElemDataAttributes(evt.target);
563
+ const type = attributes && attributes.type;
564
+ if (
565
+ type === ELEMENT_TYPE.VERTEX ||
566
+ type === ELEMENT_TYPE.SEGMENT ||
567
+ this.state.isDragging ||
568
+ this.state.uncommittedLngLat !== null
569
+ ) {
570
+ evt.stopImmediatePropagation();
571
+ }
572
+ };
573
+
574
+ /* HELPERS */
575
+ // lngLat to pixels
576
+ _project = pt => {
577
+ return this._context && this._context.viewport && this._context.viewport.project(pt);
578
+ };
579
+
580
+ // pixels to lngLat
581
+ _unproject = pt => {
582
+ return this._context && this._context.viewport && this._context.viewport.unproject(pt);
583
+ };
584
+
585
+ _matchesFeature = (attributes, feature) => {
586
+ if (!attributes || attributes.type !== ELEMENT_TYPE.FEATURE || !feature) {
587
+ return false;
588
+ }
589
+
590
+ const {featureIndex} = attributes;
591
+ const {features} = this.props;
592
+ const elemFeature = features && features[featureIndex];
593
+ return elemFeature && feature.id === elemFeature.id;
594
+ };
595
+
596
+ _getClosestPositionOnSegment = (vertexIndex, pointLngLat, feature) => {
597
+ const points = feature && feature.points;
598
+ if (!points || !points.length) {
599
+ return null;
600
+ }
601
+
602
+ // segmentId is start vertexIndex
603
+ const startPos = points[vertexIndex];
604
+ const endPos = points[(vertexIndex + 1) % points.length];
605
+ return findClosestPointOnLineSegment(startPos, endPos, pointLngLat);
606
+ };
607
+
608
+ _getEventPosition = evt => {
609
+ const {
610
+ offsetCenter: {x, y}
611
+ } = evt;
612
+ return {x: Number(x), y: Number(y)};
613
+ };
614
+
615
+ _getProjectedData(points, renderType, isClosed = false) {
616
+ if (points.length === 0) {
617
+ return '';
618
+ }
619
+
620
+ const projected = points.map(p => this._project(p));
621
+ switch (renderType) {
622
+ case RENDER_TYPE.POINT:
623
+ return projected;
624
+ case RENDER_TYPE.LINE_STRING:
625
+ case RENDER_TYPE.POLYGON:
626
+ case RENDER_TYPE.RECTANGLE:
627
+ const pathString = projected
628
+ .map(p => {
629
+ return `${p[0]},${p[1]}`;
630
+ })
631
+ .join('L');
632
+ return `M ${pathString} ${isClosed ? 'z' : ''}`;
633
+ default:
634
+ return null;
635
+ }
636
+ }
637
+
638
+ _getSelectedFeature = () => {
639
+ const {features, selectedFeatureId} = this.state;
640
+ return features && features.find(f => f.id === selectedFeatureId);
641
+ };
642
+
643
+ _getEditHandleState = (index, renderState) => {
644
+ const {mode} = this.props;
645
+ const {draggingVertexIndex, hoveredVertexIndex} = this.state;
646
+ const selectedFeature = this._getSelectedFeature();
647
+ const isSelected =
648
+ index === draggingVertexIndex ||
649
+ (selectedFeature && selectedFeature.renderType === RENDER_TYPE.POINT);
650
+ const isClosing = mode === MODES.DRAW_POLYGON && hoveredVertexIndex === 0 && index === -1;
651
+
652
+ if (renderState) {
653
+ return renderState;
654
+ }
655
+
656
+ if (isClosing) {
657
+ return RENDER_STATE.CLOSING;
658
+ }
659
+
660
+ if (isSelected) {
661
+ return RENDER_STATE.SELECTED;
662
+ }
663
+
664
+ switch (index) {
665
+ case hoveredVertexIndex:
666
+ return RENDER_STATE.HOVERED;
667
+ case UNCOMMITTED_ID:
668
+ return RENDER_STATE.UNCOMMITTED;
669
+ default:
670
+ return RENDER_STATE.INACTIVE;
671
+ }
672
+ };
673
+
674
+ _getFeatureRenderState = (id, renderState) => {
675
+ const {selectedFeatureId, hoveredFeatureId} = this.state;
676
+ if (renderState) {
677
+ return renderState;
678
+ }
679
+ switch (id) {
680
+ case selectedFeatureId:
681
+ return RENDER_STATE.SELECTED;
682
+ case hoveredFeatureId:
683
+ return RENDER_STATE.HOVERED;
684
+ case UNCOMMITTED_ID:
685
+ return RENDER_STATE.UNCOMMITTED;
686
+ default:
687
+ return RENDER_STATE.INACTIVE;
688
+ }
689
+ };
690
+
691
+ /* RENDER */
692
+ /* eslint-disable max-params */
693
+ _renderVertex = (position, featureIndex, vertexIndex, operation, style, shape) => {
694
+ /* eslint-enable max-params */
695
+ const p = this._project(position);
696
+ if (!p) {
697
+ return null;
698
+ }
699
+
700
+ const {clickRadius} = this.props;
701
+
702
+ const elemKey = `${ELEMENT_TYPE.VERTEX}.${featureIndex}.${vertexIndex}.${operation}`;
703
+ // first <circle|rect> is to make path easily interacted with
704
+ switch (shape) {
705
+ case 'circle':
706
+ return (
707
+ <g key={elemKey} transform={`translate(${p[0]}, ${p[1]})`}>
708
+ <circle
709
+ data-type={ELEMENT_TYPE.VERTEX}
710
+ data-feature-index={featureIndex}
711
+ data-vertex-index={vertexIndex}
712
+ data-operation={operation}
713
+ key={`${elemKey}.hidden`}
714
+ style={{...style, ...HIDDEN_CLICKABLE_STYLE, stroke: 'none'}}
715
+ cx={0}
716
+ cy={0}
717
+ r={clickRadius}
718
+ />
719
+ <circle
720
+ data-type={ELEMENT_TYPE.VERTEX}
721
+ data-feature-index={featureIndex}
722
+ data-vertex-index={vertexIndex}
723
+ data-operation={operation}
724
+ key={elemKey}
725
+ style={style}
726
+ cx={0}
727
+ cy={0}
728
+ />
729
+ </g>
730
+ );
731
+ case 'rect':
732
+ return (
733
+ <g key={`vertex.${vertexIndex}`} transform={`translate(${p[0]}, ${p[1]})`}>
734
+ <rect
735
+ data-type={ELEMENT_TYPE.VERTEX}
736
+ data-feature-index={featureIndex}
737
+ data-vertex-index={vertexIndex}
738
+ data-operation={operation}
739
+ key={`${elemKey}.hidden`}
740
+ style={{
741
+ ...style,
742
+ ...HIDDEN_CLICKABLE_STYLE,
743
+ width: clickRadius,
744
+ height: clickRadius
745
+ }}
746
+ />
747
+ <rect
748
+ data-type={ELEMENT_TYPE.VERTEX}
749
+ data-feature-index={featureIndex}
750
+ data-vertex-index={vertexIndex}
751
+ data-operation={operation}
752
+ key={elemKey}
753
+ style={style}
754
+ />
755
+ </g>
756
+ );
757
+
758
+ default:
759
+ return null;
760
+ }
761
+ };
762
+
763
+ _renderSegment = (featureIndex, startVertexId, startPos, endPos, style = {}) => {
764
+ const {clickRadius} = this.props;
765
+ const projected = this._getProjectedData([startPos, endPos], RENDER_TYPE.LINE_STRING);
766
+ const {radius, ...others} = style;
767
+
768
+ const elemKey = `${ELEMENT_TYPE.SEGMENT}.${featureIndex}.${startVertexId}`;
769
+ return (
770
+ <g key={elemKey}>
771
+ <path
772
+ data-type={ELEMENT_TYPE.SEGMENT}
773
+ data-feature-index={featureIndex}
774
+ data-vertex-index={startVertexId}
775
+ key={`${elemKey}.hidden`}
776
+ style={{...others, strokeWidth: clickRadius || radius, opacity: 0}}
777
+ d={projected}
778
+ />
779
+ <path
780
+ data-type={ELEMENT_TYPE.SEGMENT}
781
+ data-feature-index={featureIndex}
782
+ data-vertex-index={startVertexId}
783
+ key={elemKey}
784
+ style={others}
785
+ d={projected}
786
+ />
787
+ </g>
788
+ );
789
+ };
790
+
791
+ _renderCommittedStroke = (featureIndex, feature, style) => {
792
+ const {points, isClosed, renderType} = feature;
793
+ if (!points || points.length < 2 || (renderType === RENDER_TYPE.RECTANGLE && !isClosed)) {
794
+ return null;
795
+ }
796
+
797
+ const segments = [];
798
+ for (let i = 0; i < points.length - 1; i++) {
799
+ segments.push(this._renderSegment(featureIndex, i, points[i], points[i + 1], style));
800
+ }
801
+
802
+ if (isClosed) {
803
+ const lastIndex = points.length - 1;
804
+ segments.push(
805
+ this._renderSegment(featureIndex, lastIndex, points[lastIndex], points[0], style)
806
+ );
807
+ }
808
+
809
+ return <g key="committed group">{segments}</g>;
810
+ };
811
+
812
+ _renderUncommittedStrokes = (featureIndex, feature, style) => {
813
+ const {points, isClosed, renderType} = feature;
814
+ const {mode} = this.props;
815
+ const {uncommittedLngLat} = this.state;
816
+ const isDrawing = DRAWING_MODES.find(m => m === mode);
817
+
818
+ if (!points || isClosed || !isDrawing) {
819
+ return null;
820
+ }
821
+
822
+ const uncommittedSegments = [];
823
+
824
+ if (renderType === RENDER_TYPE.RECTANGLE) {
825
+ for (let i = 0; i < points.length - 1; i++) {
826
+ uncommittedSegments.push(
827
+ this._renderSegment(featureIndex, i, points[i], points[i + 1], style)
828
+ );
829
+ }
830
+ if (points.length === 4) {
831
+ uncommittedSegments.push(this._renderSegment(featureIndex, 3, points[3], points[0], style));
832
+ }
833
+ }
834
+
835
+ if (!uncommittedLngLat) {
836
+ return uncommittedSegments.length ? uncommittedSegments : null;
837
+ }
838
+
839
+ /* eslint-disable no-inline-comments */
840
+ uncommittedSegments.push(
841
+ this._renderSegment(
842
+ featureIndex,
843
+ points.length - 1, // id
844
+ points.slice(-1)[0], // startPos
845
+ uncommittedLngLat, // endPos
846
+ style
847
+ )
848
+ );
849
+ /* eslint-enable no-inline-comments */
850
+
851
+ return uncommittedSegments.filter(Boolean);
852
+ };
853
+
854
+ _renderClosingStroke = (featureIndex, feature, style) => {
855
+ const {points, isClosed} = feature;
856
+ const {mode} = this.props;
857
+ const {uncommittedLngLat} = this.state;
858
+ if (uncommittedLngLat && mode === MODES.DRAW_POLYGON && points.length > 2 && !isClosed) {
859
+ // from uncommitted position to the first point of the polygon
860
+ return this._renderSegment(
861
+ featureIndex,
862
+ 'uncommitted-close',
863
+ uncommittedLngLat,
864
+ points[0],
865
+ style
866
+ );
867
+ }
868
+ return null;
869
+ };
870
+
871
+ _renderFill = (index, feature, style) => {
872
+ const {mode} = this.props;
873
+ const isDrawing = DRAWING_MODES.find(m => m === mode);
874
+ const {points, renderType, isClosed} = feature;
875
+ if (renderType !== RENDER_TYPE.POLYGON && renderType !== RENDER_TYPE.RECTANGLE) {
876
+ return null;
877
+ }
878
+
879
+ const {uncommittedLngLat} = this.state;
880
+
881
+ let fillPoints = points;
882
+ if (uncommittedLngLat && isDrawing) {
883
+ fillPoints = [...points, uncommittedLngLat];
884
+ }
885
+
886
+ const fillPath = this._getProjectedData(fillPoints, renderType, isClosed);
887
+ return (
888
+ <path
889
+ data-type={ELEMENT_TYPE.FEATURE}
890
+ data-feature-index={index}
891
+ key={`${ELEMENT_TYPE.FEATURE}.${index}.fill`}
892
+ style={{...style, stroke: 'none'}}
893
+ d={fillPath}
894
+ />
895
+ );
896
+ };
897
+
898
+ _renderCurrentPath = (feature, index) => {
899
+ const {points, renderType} = feature;
900
+ if (!points || !points.length || renderType === RENDER_TYPE.POINT) {
901
+ return null;
902
+ }
903
+
904
+ const {getFeatureStyle} = this.props;
905
+ const geoJson = feature.toFeature();
906
+ const committedStyle = getFeatureStyle({feature: geoJson, state: RENDER_STATE.SELECTED});
907
+ const uncommittedStyle = getFeatureStyle({feature: geoJson, state: RENDER_STATE.UNCOMMITTED});
908
+ const closingStyle = getFeatureStyle({feature: geoJson, state: RENDER_STATE.CLOSING});
909
+
910
+ const committedStroke = this._renderCommittedStroke(index, feature, committedStyle);
911
+ const uncommittedStrokes =
912
+ this._renderUncommittedStrokes(index, feature, uncommittedStyle) || [];
913
+ const closingStroke = this._renderClosingStroke(index, feature, closingStyle);
914
+ const fill = this._renderFill(index, feature, uncommittedStyle);
915
+
916
+ return [fill, committedStroke, ...uncommittedStrokes, closingStroke].filter(Boolean);
917
+ };
918
+
919
+ _renderCommittedVertices = (featureIndex, feature, geoJson) => {
920
+ const {mode, getEditHandleStyle, getEditHandleShape} = this.props;
921
+ const {isClosed, points} = feature;
922
+
923
+ const committedVertices = [];
924
+ for (let i = 0; i < points.length; i++) {
925
+ const p = points[i];
926
+ let operation = OPERATIONS.SET;
927
+
928
+ const style = getEditHandleStyle({
929
+ feature: geoJson,
930
+ index: i,
931
+ state: this._getEditHandleState(i)
932
+ });
933
+ const shape =
934
+ typeof getEditHandleShape === 'function'
935
+ ? getEditHandleShape({
936
+ feature: geoJson,
937
+ index: i,
938
+ state: this._getEditHandleState(i)
939
+ })
940
+ : getEditHandleShape;
941
+
942
+ if (isClosed) {
943
+ committedVertices.push(this._renderVertex(p, featureIndex, i, operation, style, shape));
944
+ } else {
945
+ if (mode === MODES.DRAW_POLYGON && i === 0 && points.length > 2) {
946
+ operation = OPERATIONS.INTERSECT;
947
+ }
948
+
949
+ committedVertices.push(this._renderVertex(p, featureIndex, i, operation, style, shape));
950
+ }
951
+ }
952
+
953
+ return committedVertices;
954
+ };
955
+
956
+ _renderUncommittedVertex = (featureIndex, feature, geoJson) => {
957
+ const {getEditHandleStyle, getEditHandleShape} = this.props;
958
+ const {selectedFeatureId, uncommittedLngLat} = this.state;
959
+ const {id} = feature;
960
+
961
+ let uncommittedVertex = null;
962
+ if (selectedFeatureId === id && uncommittedLngLat) {
963
+ const style = getEditHandleStyle({
964
+ feature: geoJson,
965
+ index: 'uncommitted',
966
+ state: this._getEditHandleState(-1, RENDER_STATE.UNCOMMITTED)
967
+ });
968
+
969
+ const shape =
970
+ typeof getEditHandleShape === 'function'
971
+ ? getEditHandleShape({
972
+ feature: geoJson,
973
+ index: null,
974
+ state: this._getEditHandleState(-1)
975
+ })
976
+ : getEditHandleShape;
977
+
978
+ uncommittedVertex = this._renderVertex(
979
+ uncommittedLngLat,
980
+ featureIndex,
981
+ 'uncommitted',
982
+ OPERATIONS.INSERT,
983
+ {...style, pointerEvents: 'none'},
984
+ shape
985
+ );
986
+ }
987
+
988
+ return uncommittedVertex;
989
+ };
990
+
991
+ _renderCurrentVertices = (feature, featureIndex) => {
992
+ const {points} = feature;
993
+
994
+ if (!points || !points.length) {
995
+ return null;
996
+ }
997
+
998
+ const geoJson = feature.toFeature();
999
+ const committedVertices = this._renderCommittedVertices(featureIndex, feature, geoJson);
1000
+ const uncommittedVertex = this._renderUncommittedVertex(featureIndex, feature, geoJson);
1001
+
1002
+ return (
1003
+ <g key="edit-handles">
1004
+ {committedVertices}
1005
+ {uncommittedVertex}
1006
+ </g>
1007
+ );
1008
+ };
1009
+
1010
+ _renderCurrent = () => {
1011
+ const {features} = this.state;
1012
+ const feature = this._getSelectedFeature();
1013
+
1014
+ if (!features || !feature || !feature.points) {
1015
+ return null;
1016
+ }
1017
+
1018
+ const {mode} = this.props;
1019
+ const index = features.findIndex(f => f.id === feature.id);
1020
+
1021
+ return (
1022
+ <g
1023
+ key="feature current"
1024
+ style={mode === MODES.READ_ONLY || mode === MODES.SELECT_FEATURE ? STATIC_STYLE : null}
1025
+ >
1026
+ {this._renderCurrentPath(feature, index)}
1027
+ {this._renderCurrentVertices(feature, index)}
1028
+ </g>
1029
+ );
1030
+ };
1031
+
1032
+ _renderFeature = (feature, index) => {
1033
+ if (feature === this._getSelectedFeature()) {
1034
+ return null;
1035
+ }
1036
+
1037
+ const {clickRadius} = this.props;
1038
+ const {id, points, renderType, isClosed} = feature;
1039
+ if (!points || !points.length) {
1040
+ return null;
1041
+ }
1042
+
1043
+ const projected = this._getProjectedData(points, renderType, isClosed);
1044
+
1045
+ if (!projected) {
1046
+ return null;
1047
+ }
1048
+
1049
+ const geoJson = feature.toFeature();
1050
+ const renderState = this._getFeatureRenderState(id);
1051
+ const {getFeatureStyle, getFeatureShape} = this.props;
1052
+ const style = getFeatureStyle({feature: geoJson, state: renderState});
1053
+ const shape =
1054
+ typeof getFeatureShape === 'function'
1055
+ ? getFeatureShape({feature: geoJson, state: renderState})
1056
+ : getFeatureShape;
1057
+
1058
+ const elemKey = `${ELEMENT_TYPE.FEATURE}.${index}`;
1059
+
1060
+ switch (renderType) {
1061
+ case RENDER_TYPE.POINT:
1062
+ if (shape === 'rect') {
1063
+ return (
1064
+ <g key={elemKey} transform={`translate(${projected[0][0]}, ${projected[0][1]})`}>
1065
+ <rect
1066
+ data-type={ELEMENT_TYPE.FEATURE}
1067
+ data-feature-index={index}
1068
+ key={`${elemKey}.hidden`}
1069
+ style={{
1070
+ ...style,
1071
+ ...HIDDEN_CLICKABLE_STYLE,
1072
+ width: clickRadius,
1073
+ height: clickRadius
1074
+ }}
1075
+ />
1076
+ <rect
1077
+ data-type={ELEMENT_TYPE.FEATURE}
1078
+ data-feature-index={index}
1079
+ key={elemKey}
1080
+ style={style}
1081
+ />
1082
+ </g>
1083
+ );
1084
+ }
1085
+
1086
+ return (
1087
+ <g key={elemKey} transform={`translate(${projected[0][0]}, ${projected[0][1]})`}>
1088
+ <circle
1089
+ data-type={ELEMENT_TYPE.FEATURE}
1090
+ data-feature-index={index}
1091
+ key={`${elemKey}.hidden`}
1092
+ style={{
1093
+ ...style,
1094
+ opacity: 0
1095
+ }}
1096
+ cx={0}
1097
+ cy={0}
1098
+ r={clickRadius}
1099
+ />
1100
+ <circle
1101
+ data-type={ELEMENT_TYPE.FEATURE}
1102
+ data-feature-index={index}
1103
+ key={`feature.${index}`}
1104
+ style={style}
1105
+ cx={0}
1106
+ cy={0}
1107
+ />
1108
+ </g>
1109
+ );
1110
+
1111
+ // first <path> is to make path easily interacted with
1112
+ case RENDER_TYPE.LINE_STRING:
1113
+ return (
1114
+ <g key={elemKey}>
1115
+ <path
1116
+ data-type={ELEMENT_TYPE.FEATURE}
1117
+ data-feature-index={index}
1118
+ key={`${elemKey}.hidden`}
1119
+ style={{
1120
+ ...style,
1121
+ strokeWidth: clickRadius,
1122
+ opacity: 0
1123
+ }}
1124
+ d={projected}
1125
+ />
1126
+ <path
1127
+ data-type={ELEMENT_TYPE.FEATURE}
1128
+ data-feature-index={index}
1129
+ key={elemKey}
1130
+ style={style}
1131
+ d={projected}
1132
+ />
1133
+ </g>
1134
+ );
1135
+
1136
+ case 'Polygon':
1137
+ case 'Rectangle':
1138
+ return (
1139
+ <path
1140
+ data-type={ELEMENT_TYPE.FEATURE}
1141
+ data-feature-index={index}
1142
+ key={elemKey}
1143
+ style={style}
1144
+ d={projected}
1145
+ />
1146
+ );
1147
+
1148
+ default:
1149
+ return null;
1150
+ }
1151
+ };
1152
+
1153
+ _renderFeatures = () => {
1154
+ const {features} = this.state;
1155
+ return features && features.map(this._renderFeature);
1156
+ };
1157
+
1158
+ _renderCanvas = () => {
1159
+ const {selectedFeatureId, features} = this.state;
1160
+
1161
+ return (
1162
+ <svg key="draw-canvas" width="100%" height="100%">
1163
+ {features && features.length > 0 && <g key="feature-group">{this._renderFeatures()}</g>}
1164
+ {selectedFeatureId && this._renderCurrent()}
1165
+ </svg>
1166
+ );
1167
+ };
1168
+
1169
+ _renderEditor = () => {
1170
+ const viewport = (this._context && this._context.viewport) || {};
1171
+ const {style} = this.props;
1172
+ const {width, height} = viewport;
1173
+
1174
+ return (
1175
+ <div
1176
+ id="editor"
1177
+ style={{
1178
+ width,
1179
+ height,
1180
+ ...style
1181
+ }}
1182
+ ref={_ => {
1183
+ this._containerRef = _;
1184
+ }}
1185
+ >
1186
+ {this._renderCanvas()}
1187
+ </div>
1188
+ );
1189
+ };
1190
+
1191
+ render() {
1192
+ return (
1193
+ <MapContext.Consumer>
1194
+ {context => {
1195
+ this._context = context;
1196
+ const viewport = context && context.viewport;
1197
+
1198
+ if (!viewport || viewport.height <= 0 || viewport.width <= 0) {
1199
+ return null;
1200
+ }
1201
+
1202
+ return this._renderEditor();
1203
+ }}
1204
+ </MapContext.Consumer>
1205
+ );
1206
+ }
1207
+ }