@hpcc-js/map 2.79.3 → 2.79.5

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 (89) hide show
  1. package/LICENSE +43 -43
  2. package/README.md +88 -88
  3. package/TopoJSON/BR.json +122 -122
  4. package/TopoJSON/GB_idx.json +1 -1
  5. package/TopoJSON/IE_idx.json +1 -1
  6. package/TopoJSON/ND_idx.json +1 -1
  7. package/TopoJSON/countries.json +257 -257
  8. package/TopoJSON/us-counties.json +16550 -16550
  9. package/TopoJSON/us-states.json +458 -458
  10. package/dist/index.es6.js +49 -49
  11. package/dist/index.es6.js.map +1 -1
  12. package/dist/index.js +49 -49
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.min.js +1 -1
  15. package/dist/index.min.js.map +1 -1
  16. package/package.json +7 -7
  17. package/src/CanvasPinLayer.ts +99 -99
  18. package/src/CanvasPins.ts +396 -396
  19. package/src/Choropleth.css +27 -27
  20. package/src/Choropleth.ts +195 -195
  21. package/src/ChoroplethContinents.ts +13 -13
  22. package/src/ChoroplethCounties.ts +109 -109
  23. package/src/ChoroplethCountries.ts +100 -100
  24. package/src/ChoroplethStates.ts +104 -104
  25. package/src/ChoroplethStatesHeat.ts +8 -8
  26. package/src/GMap.css +16 -16
  27. package/src/GMap.ts +879 -879
  28. package/src/GMapCounties.ts +93 -93
  29. package/src/GMapGraph.ts +61 -61
  30. package/src/GMapHeat.ts +27 -27
  31. package/src/GMapLayered.ts +94 -94
  32. package/src/GMapPin.ts +115 -115
  33. package/src/GMapPinLine.ts +125 -125
  34. package/src/GeoHash.css +15 -15
  35. package/src/GeoHash.ts +125 -125
  36. package/src/Graph.css +10 -10
  37. package/src/Graph.ts +98 -98
  38. package/src/Graticule.css +13 -13
  39. package/src/Graticule.ts +90 -90
  40. package/src/Heat.css +2 -2
  41. package/src/Heat.ts +82 -82
  42. package/src/IChoropleth.ts +8 -8
  43. package/src/Layer.ts +90 -90
  44. package/src/Layered.css +19 -19
  45. package/src/Layered.ts +206 -206
  46. package/src/Lines.css +9 -9
  47. package/src/Lines.ts +75 -75
  48. package/src/OpenStreet.css +15 -15
  49. package/src/OpenStreet.ts +123 -123
  50. package/src/Pins.css +18 -18
  51. package/src/Pins.ts +319 -319
  52. package/src/Projection.ts +42 -42
  53. package/src/TestHeatMap.ts +8 -8
  54. package/src/TopoJSONChoropleth.ts +114 -114
  55. package/src/Utility.ts +482 -482
  56. package/src/__package__.ts +3 -3
  57. package/src/index.ts +34 -34
  58. package/src/leaflet/AlbersPR.ts +48 -48
  59. package/src/leaflet/Blank.ts +9 -9
  60. package/src/leaflet/Circles.ts +139 -139
  61. package/src/leaflet/ClusterCircles.css +26 -26
  62. package/src/leaflet/ClusterCircles.ts +88 -88
  63. package/src/leaflet/Countries.ts +43 -43
  64. package/src/leaflet/DrawLayer.ts +167 -167
  65. package/src/leaflet/FeatureLayer.ts +138 -138
  66. package/src/leaflet/GMap.ts +44 -44
  67. package/src/leaflet/HeatLayer.ts +77 -77
  68. package/src/leaflet/Icons.ts +60 -60
  69. package/src/leaflet/Leaflet.css +3 -3
  70. package/src/leaflet/Leaflet.ts +238 -238
  71. package/src/leaflet/MapBox.ts +35 -35
  72. package/src/leaflet/Markers.ts +109 -109
  73. package/src/leaflet/OpenStreet.ts +27 -27
  74. package/src/leaflet/Path.ts +138 -138
  75. package/src/leaflet/Pins.ts +73 -73
  76. package/src/leaflet/Polygons.ts +113 -113
  77. package/src/leaflet/Region.ts +138 -138
  78. package/src/leaflet/Text.ts +99 -99
  79. package/src/leaflet/TileLayer.ts +81 -81
  80. package/src/leaflet/TopoJSON.ts +146 -146
  81. package/src/leaflet/US.ts +15 -15
  82. package/src/leaflet/USCounties.ts +43 -43
  83. package/src/leaflet/USStates.ts +41 -41
  84. package/src/leaflet/World.css +3 -3
  85. package/src/leaflet/World.ts +171 -171
  86. package/src/leaflet/index.ts +18 -18
  87. package/src/test.ts +114 -114
  88. package/types/__package__.d.ts +2 -2
  89. package/types-3.4/__package__.d.ts +2 -2
package/src/GMap.ts CHANGED
@@ -1,879 +1,879 @@
1
- import { HTMLWidget } from "@hpcc-js/common";
2
- import { AbsoluteSurface } from "@hpcc-js/layout";
3
- import { promiseTimeout } from "@hpcc-js/util";
4
- import { map as d3Map } from "d3-collection";
5
- import * as _GoogleMapsLoader from "google-maps";
6
-
7
- const GoogleMapsLoader = _GoogleMapsLoader.default || _GoogleMapsLoader;
8
-
9
- import "../src/GMap.css";
10
-
11
- declare const window: any;
12
-
13
- export let google: any = null;
14
-
15
- let timout = 15000;
16
- export function requireGoogleMapTimeout(ms: number) {
17
- timout = ms;
18
- }
19
-
20
- let _googleMapPromise;
21
- export function requireGoogleMap(customGoogle?: any) {
22
- if (!_googleMapPromise) {
23
- _googleMapPromise = promiseTimeout(timout, new Promise<void>(function (resolve, reject) {
24
- if (google) {
25
- resolve();
26
- } else if (customGoogle) {
27
- google = customGoogle;
28
- resolve();
29
- } else {
30
- if (!window.__hpcc_gmap_apikey) {
31
- console.warn("__hpcc_gmap_apikey does not contain a valid API key, reverting to developers key (expect limited performance)");
32
- }
33
- try {
34
- GoogleMapsLoader.KEY = window.__hpcc_gmap_apikey || "AIzaSyDwGn2i1i_pMZvnqYJN1BksD_tjYaCOWKg";
35
- GoogleMapsLoader.LIBRARIES = ["geometry", "drawing"];
36
- GoogleMapsLoader.load(function (_google) {
37
- google = _google;
38
- resolve();
39
- });
40
- } catch (e) {
41
- console.warn(`Failed to initialize Google Map API: ${e.message}`);
42
- resolve();
43
- }
44
- }
45
- })).catch(e => {
46
- console.warn(`Timed out initializing Google Map API after ${timout}ms.`);
47
- });
48
- }
49
- return _googleMapPromise;
50
- }
51
-
52
- function createOverlay(map, worldSurface, viewportSurface) {
53
- function Overlay(map2, worldSurface2, viewportSurface2) {
54
- google.maps.OverlayView.call(this);
55
- this._div = null;
56
-
57
- this._worldSurface = worldSurface2;
58
- this._viewportSurface = viewportSurface2;
59
-
60
- this._map = map2;
61
- this.setMap(map2);
62
-
63
- const context = this;
64
- google.maps.event.addListener(map2, "bounds_changed", function () {
65
- context.draw();
66
- });
67
- google.maps.event.addListener(map2, "projection_changed", function () {
68
- context.draw();
69
- });
70
-
71
- this._prevWorldMin = { x: 0, y: 0 };
72
- this._prevWorldMax = { x: 0, y: 0 };
73
- this._prevMin = { x: 0, y: 0 };
74
- this._prevMax = { x: 0, y: 0 };
75
- }
76
- Overlay.prototype = google.maps.OverlayView.prototype;
77
-
78
- Overlay.prototype.onAdd = function () {
79
- this.div = document.createElement("div");
80
-
81
- this._viewportSurface.target(null)
82
- .target(this.div)
83
- .units("pixels");
84
-
85
- const panes = this.getPanes();
86
- panes.overlayMouseTarget.appendChild(this.div);
87
- };
88
-
89
- Overlay.prototype.draw = function () {
90
- const projection = this.getProjection();
91
- if (!projection)
92
- return;
93
-
94
- const bounds = this._map.getBounds();
95
- const center = projection.fromLatLngToDivPixel(bounds.getCenter());
96
- const sw = projection.fromLatLngToDivPixel(bounds.getSouthWest());
97
- const ne = projection.fromLatLngToDivPixel(bounds.getNorthEast());
98
-
99
- const min = {
100
- x: sw.x,
101
- y: ne.y
102
- };
103
- const max = {
104
- x: ne.x,
105
- y: sw.y
106
- };
107
-
108
- const worldWidth = projection.getWorldWidth();
109
- while (max.x < min.x + 100) { // Ignoe dateline from being the rect.
110
- max.x += worldWidth;
111
- }
112
- while (min.x > center.x) {
113
- min.x -= worldWidth;
114
- max.x -= worldWidth;
115
- }
116
-
117
- if (min.x !== this._prevMin.x || min.y !== this._prevMin.y || max.x !== this._prevMax.x || max.y !== this._prevMax.y) {
118
- this._viewportSurface
119
- .resize({ width: 0, height: 0 })
120
- .widgetX(min.x)
121
- .widgetY(min.y)
122
- .widgetWidth(max.x - min.x)
123
- .widgetHeight(max.y - min.y)
124
- ;
125
- // FF Issue on initial render (GH-1855) ---
126
- if (this._viewportSurface._renderCount) {
127
- this._viewportSurface.render();
128
- this._prevMin = min;
129
- this._prevMax = max;
130
- } else {
131
- this._viewportSurface.lazyRender();
132
- }
133
- }
134
-
135
- const worldMin = projection.fromLatLngToDivPixel(new google.maps.LatLng(85, -179.9));
136
- const worldMax = projection.fromLatLngToDivPixel(new google.maps.LatLng(-85, 179.9));
137
- while (worldMax.x < worldMin.x + 100) { // Ignoe dateline from being the rect.
138
- worldMax.x += worldWidth;
139
- }
140
- while (worldMin.x > center.x) {
141
- worldMin.x -= worldWidth;
142
- worldMax.x -= worldWidth;
143
- }
144
- if (worldMin.x !== this._prevWorldMin.x || worldMin.y !== this._prevWorldMin.y || worldMax.x !== this._prevWorldMax.x || worldMax.y !== this._prevWorldMax.y) {
145
- this._worldSurface
146
- .widgetX(worldMin.x)
147
- .widgetY(worldMin.y)
148
- .widgetWidth(worldMax.x - worldMin.x)
149
- .widgetHeight(worldMax.y - worldMin.y)
150
- .render()
151
- ;
152
- this._prevWorldMin = worldMax;
153
- this._prevWorldMax = worldMax;
154
- }
155
- };
156
-
157
- Overlay.prototype.onRemove = function () {
158
- this._viewportSurface.target(null);
159
- this._div.parentNode.removeChild(this._div);
160
- this._div = null;
161
- };
162
-
163
- return new Overlay(map, worldSurface, viewportSurface);
164
- }
165
-
166
- class UserShapeSelectionBag {
167
- _userShapes: any[];
168
- mapContext;
169
-
170
- constructor(mapObj) {
171
- this._userShapes = [];
172
- this.mapContext = mapObj;
173
- }
174
-
175
- add(_) {
176
- const idx = this._userShapes.indexOf(_);
177
- if (idx >= 0) {
178
- return;
179
- }
180
- this._userShapes.push(_);
181
- }
182
-
183
- remove(_) {
184
- const idx = this._userShapes.indexOf(_);
185
- if (idx >= 0) {
186
- this._userShapes.splice(idx, 1);
187
- }
188
- _.setMap(null);
189
- }
190
-
191
- save() {
192
- return this._userShapes.map(shape => this._saveShape(shape));
193
- }
194
-
195
- load(_) {
196
- this._deserializeShapes(_);
197
- }
198
-
199
- _saveShape(shape) {
200
- const retVal: any = {};
201
-
202
- const createShapes = {
203
- circle: (_) => {
204
- retVal.type = "circle";
205
- retVal.pos = {
206
- lat: _.center.lat(),
207
- lng: _.center.lng()
208
- };
209
- retVal.radius = _.radius;
210
- },
211
- rectangle: (_) => {
212
- retVal.type = "rectangle";
213
- retVal.bounds = {
214
- ne: _.bounds.getNorthEast(),
215
- sw: _.bounds.getSouthWest()
216
- };
217
- },
218
- polygon: (_) => {
219
- retVal.type = _.__hpcc_type;
220
-
221
- const vertices = _.getPath();
222
-
223
- retVal.vertices = [];
224
-
225
- for (let i = 0; i < vertices.length; i++) {
226
- retVal.vertices.push(vertices.getAt(i));
227
- }
228
- },
229
- polyline: (_) => {
230
- createShapes.polygon(_);
231
- }
232
- };
233
-
234
- createShapes[shape.__hpcc_type](shape);
235
- retVal.strokeWeight = shape.strokeWeight;
236
- retVal.fillColor = shape.fillColor;
237
- retVal.fillOpacity = shape.fillOpacity;
238
- retVal.editable = shape.editable;
239
- retVal.clickable = shape.clickable || true;
240
-
241
- return retVal;
242
- }
243
-
244
- _deserializeShapes(_shapes) {
245
-
246
- const shapes = JSON.parse(_shapes);
247
-
248
- const defOptions = {
249
- strokeWeight: 0,
250
- fillOpacity: 0.45,
251
- fillColor: "#1f77b4",
252
- editable: true,
253
- clickable: true
254
- };
255
-
256
- const createShapes = {
257
- circle: (_, map) => {
258
- const shape = new google.maps.Circle({
259
- strokeWeight: _.strokeWeight || defOptions.strokeWeight,
260
- fillColor: _.fillColor || defOptions.fillColor,
261
- fillOpacity: _.fillOpacity || defOptions.fillOpacity,
262
- editable: _.editable || defOptions.editable,
263
- clickable: _.clickable || defOptions.clickable,
264
- map,
265
- center: _.pos,
266
- radius: _.radius
267
- });
268
- return shape;
269
- },
270
- rectangle: (_, map) => {
271
- const shape = new google.maps.Rectangle({
272
- strokeWeight: _.strokeWeight || defOptions.strokeWeight,
273
- fillColor: _.fillColor || defOptions.fillColor,
274
- fillOpacity: _.fillOpacity || defOptions.fillOpacity,
275
- editable: _.editable || defOptions.editable,
276
- clickable: _.clickable || defOptions.clickable,
277
- map,
278
- bounds: {
279
- north: _.bounds.ne.lat,
280
- west: _.bounds.sw.lng,
281
- south: _.bounds.sw.lat,
282
- east: _.bounds.ne.lng
283
- }
284
- });
285
- return shape;
286
- },
287
- polygon: (_, map) => {
288
- const shape = new google.maps.Polygon({
289
- strokeWeight: _.strokeWeight || defOptions.strokeWeight,
290
- fillColor: _.fillColor || defOptions.fillColor,
291
- fillOpacity: _.fillOpacity || defOptions.fillOpacity,
292
- editable: _.editable || defOptions.editable,
293
- clickable: _.clickable || defOptions.clickable,
294
- map,
295
- paths: _.vertices
296
- });
297
- return shape;
298
- },
299
- polyline: (_, map) => {
300
- const shape = new google.maps.Polyline({
301
- strokeWeight: _.strokeWeight || defOptions.strokeWeight,
302
- fillColor: _.fillColor || defOptions.fillColor,
303
- fillOpacity: _.fillOpacity || defOptions.fillOpacity,
304
- editable: _.editable || defOptions.editable,
305
- clickable: _.clickable || defOptions.clickable,
306
- map,
307
- path: _.vertices
308
- });
309
- return shape;
310
- }
311
- };
312
-
313
- for (let i = 0; i < shapes.length; i++) {
314
- const shape = createShapes[shapes[i].type](shapes[i], this.mapContext._googleMap);
315
- this.mapContext.onDrawingComplete({ type: shapes[i].type, overlay: shape });
316
- }
317
- }
318
- }
319
-
320
- export class GMap extends HTMLWidget {
321
- _overlay;
322
- _userShapes;
323
- _worldSurface;
324
- _viewportSurface;
325
- _googleMapNode;
326
- _googleMap;
327
- _googleGeocoder;
328
- _prevCenterLat;
329
- _prevCenterLong;
330
- _googleStreetViewService;
331
- _googleMapPanorama;
332
- _prevZoom;
333
- _prevStreetView;
334
- _circleMap;
335
- _pinMap;
336
- _drawingManager;
337
- _prevCenterAddress;
338
- _userShapeSelection;
339
-
340
- constructor() {
341
- super();
342
-
343
- this._tag = "div";
344
-
345
- const context = this;
346
- function calcProjection(surface, lat, long) {
347
- const projection = context._overlay.getProjection();
348
- const retVal = projection.fromLatLngToDivPixel(new google.maps.LatLng(lat, long));
349
- const worldWidth = projection.getWorldWidth();
350
- const widgetX = parseFloat(surface.widgetX());
351
- const widgetY = parseFloat(surface.widgetY());
352
- const widgetWidth = parseFloat(surface.widgetWidth());
353
- retVal.x -= widgetX;
354
- retVal.y -= widgetY;
355
- while (retVal.x < 0) {
356
- retVal.x += worldWidth;
357
- }
358
- while (retVal.x > widgetWidth) {
359
- retVal.x -= worldWidth;
360
- }
361
- return retVal;
362
- }
363
-
364
- this._userShapes = new UserShapeSelectionBag(this);
365
-
366
- this._worldSurface = new AbsoluteSurface();
367
- this._worldSurface.project = function (lat, long) {
368
- return calcProjection(this, lat, long);
369
- };
370
-
371
- this._viewportSurface = new AbsoluteSurface();
372
- this._viewportSurface.project = function (lat, long) {
373
- return calcProjection(this, lat, long);
374
- };
375
- }
376
-
377
- data(_?) {
378
- const retVal = HTMLWidget.prototype.data.apply(this, arguments);
379
- return retVal;
380
- }
381
-
382
- getMapType() {
383
- switch (this.type()) {
384
- case "terrain":
385
- return google.maps.MapTypeId.TERRAIN;
386
- case "road":
387
- return google.maps.MapTypeId.ROADMAP;
388
- case "satellite":
389
- return google.maps.MapTypeId.SATELLITE;
390
- case "hybrid":
391
- return google.maps.MapTypeId.HYBRID;
392
- default:
393
- return google.maps.MapTypeId.ROADMAP;
394
- }
395
- }
396
-
397
- getMapOptions() {
398
- return {
399
- panControl: this.panControl(),
400
- zoomControl: this.zoomControl(),
401
- fullscreenControl: this.fullscreenControl(),
402
- mapTypeControl: this.mapTypeControl(),
403
- scaleControl: this.scaleControl(),
404
- streetViewControl: this.streetViewControl(),
405
- overviewMapControl: this.overviewMapControl(),
406
- overviewMapControlOptions: { opened: true },
407
- styles: this.googleMapStyles()
408
- };
409
- }
410
-
411
- size(_?) {
412
- const retVal = HTMLWidget.prototype.size.apply(this, arguments);
413
- if (arguments.length && this._googleMapNode) {
414
- this._googleMapNode
415
- .style("width", _.width + "px")
416
- .style("height", _.height + "px")
417
- ;
418
- google.maps.event.trigger(this._googleMap, "resize");
419
- }
420
- return retVal;
421
- }
422
-
423
- enter(domNode, element) {
424
- super.enter(domNode, element);
425
- const context = this;
426
- this._googleGeocoder = new google.maps.Geocoder();
427
- this._googleMapNode = element.append("div")
428
- .style("width", this.width() + "px")
429
- .style("height", this.height() + "px")
430
- ;
431
- this._googleMap = new google.maps.Map(this._googleMapNode.node(), {
432
- zoom: this.zoom(),
433
- center: new google.maps.LatLng(this.centerLat(), this.centerLong()),
434
- mapTypeId: this.getMapType(),
435
- disableDefaultUI: true
436
- });
437
- this._overlay = createOverlay(this._googleMap, this._worldSurface, this._viewportSurface);
438
- this._googleMap.addListener("center_changed", function () {
439
- context.centerLat(context._googleMap.center.lat());
440
- context._prevCenterLat = context.centerLat();
441
- context.centerLong(context._googleMap.center.lng());
442
- context._prevCenterLong = context.centerLong();
443
- context._googleMapPanorama.setPosition({ lat: context.centerLat(), lng: context.centerLong() });
444
- context.zoom(context._googleMap.getZoom());
445
- context._prevZoom = context.zoom();
446
- context._overlay.draw();
447
- });
448
- this._googleMap.addListener("zoom_changed", function () {
449
- context.zoom(context._googleMap.zoom);
450
- context._prevZoom = context.zoom();
451
- });
452
- this._googleStreetViewService = new google.maps.StreetViewService();
453
- this._googleMapPanorama = this._googleMap.getStreetView();
454
- this._googleMapPanorama.addListener("visible_changed", function () {
455
- context.streetView(context._googleMapPanorama.getVisible());
456
- context._prevStreetView = context.streetView();
457
- });
458
-
459
- this._circleMap = d3Map([]);
460
- this._pinMap = d3Map([]);
461
-
462
- this._prevCenterLat = this.centerLat();
463
- this._prevCenterLong = this.centerLong();
464
- this._prevZoom = this.zoom();
465
-
466
- // Init drawing tools with default options.
467
- const defOptions = {
468
- strokeWeight: 0,
469
- fillOpacity: 0.45,
470
- fillColor: "#1f77b4",
471
- editable: true,
472
- clickable: true
473
- };
474
- this._drawingManager = new google.maps.drawing.DrawingManager({
475
- drawingMode: google.maps.drawing.OverlayType.MARKER,
476
- drawingControl: true,
477
- drawingControlOptions: {
478
- position: google.maps.ControlPosition.TOP_CENTER,
479
- drawingModes: ["polygon", "rectangle", "circle"]
480
- },
481
- rectangleOptions: defOptions,
482
- circleOptions: defOptions,
483
- polygonOptions: defOptions
484
- });
485
-
486
- if (this.drawingState()) {
487
- this._userShapes.load(this.drawingState());
488
- }
489
- }
490
-
491
- update(domNode, element) {
492
- const context = this;
493
- this._googleMapNode
494
- .style("width", this.width() + "px")
495
- .style("height", this.height() + "px")
496
- ;
497
-
498
- this._googleMap.setMapTypeId(this.getMapType());
499
- this._googleMap.setOptions(this.getMapOptions());
500
-
501
- if (this.centerAddress_exists() && this._prevCenterAddress !== this.centerAddress()) {
502
- this._prevCenterAddress = this.centerAddress();
503
- this._googleGeocoder.geocode({ address: this.centerAddress() }, function (results, status) {
504
- if (status === google.maps.GeocoderStatus.OK) {
505
- const bounds = results[0].geometry.bounds || results[0].geometry.viewport;
506
- context._googleMap.fitBounds(bounds);
507
- if (context.streetView() && context.useComputedHeading()) {
508
- context.streetViewAt({
509
- lat: results[0].geometry.location.lat(),
510
- lng: results[0].geometry.location.lng()
511
- }, 50);
512
- }
513
- } else {
514
- context.statusError({
515
- data: results,
516
- status
517
- });
518
- }
519
- });
520
- }
521
- if (this._prevCenterLat !== this.centerLat() || this._prevCenterLong !== this.centerLong()) {
522
- this._googleMap.setCenter(new google.maps.LatLng(this.centerLat(), this.centerLong()));
523
-
524
- this._prevCenterLat = this.centerLat();
525
- this._prevCenterLong = this.centerLong();
526
- }
527
- if (this._prevZoom !== this.zoom()) {
528
- this._googleMap.setZoom(this.zoom());
529
-
530
- this._prevZoom = this.zoom();
531
- }
532
- this.updateCircles();
533
- this.updatePins();
534
- if (this._prevStreetView !== this.streetView()) {
535
- if (this.streetView()) {
536
- this._googleMapPanorama.setPosition({ lat: this.centerLat(), lng: this.centerLong() });
537
- this._googleMapPanorama.setPov({
538
- heading: 0,
539
- pitch: 0
540
- });
541
- this._googleMapPanorama.setVisible(true);
542
- } else {
543
- this._googleMapPanorama.setVisible(false);
544
- }
545
- this._prevStreetView = this.streetView();
546
- }
547
-
548
- // Enable or disable drawing tools.
549
- if (this.drawingTools()) {
550
- this._drawingManager.setMap(this._googleMap);
551
-
552
- // Add drawing complete listener to maintain array of drawingState.
553
- google.maps.event.addListener(
554
- this._drawingManager,
555
- "overlaycomplete",
556
- function () {
557
- GMap.prototype.onDrawingComplete.apply(context, arguments);
558
- });
559
- } else {
560
- this._drawingManager.setMap(null);
561
- google.maps.event.clearInstanceListeners(this._drawingManager);
562
- }
563
- }
564
-
565
- render(callback?) {
566
- const context = this;
567
- const args = arguments;
568
- requireGoogleMap().then(() => {
569
- super.render.apply(context, args);
570
- });
571
- return this;
572
- }
573
- streetViewAt(pos, radius = 1000) {
574
- const context = this;
575
- const source = this.outdoorStreetViewOnly() ? google.maps.StreetViewSource.OUTDOOR : google.maps.StreetViewSource.DEFAULT;
576
- this._googleStreetViewService.getPanorama({ location: pos, radius, source }, function (data, status) {
577
- if (status === "OK") {
578
- const marker = new google.maps.Marker({
579
- position: pos,
580
- map: context._googleMap
581
- });
582
- const heading = google.maps.geometry.spherical.computeHeading(data.location.latLng, new google.maps.LatLng(pos.lat, pos.lng));
583
- context._googleMapPanorama.setPano(data.location.pano);
584
-
585
- context._googleMapPanorama.setPov({
586
- heading,
587
- pitch: 0
588
- });
589
- if (!context.showStreetViewMarker()) {
590
- marker.setVisible(false);
591
- }
592
- context._googleMapPanorama.setVisible(true);
593
- const listener = google.maps.event.addListener(context._googleMap.getStreetView(), "visible_changed", function () {
594
- if (!this.getVisible()) {
595
- marker.setMap(null);
596
- google.maps.event.removeListener(listener);
597
- }
598
- });
599
- } else {
600
- context.statusError({ data, status });
601
- }
602
- });
603
- }
604
-
605
- updateCircles() {
606
- function rowID(row) {
607
- return row[0] + "_" + row[1];
608
- }
609
-
610
- const circle_enter = [];
611
- const circle_update = [];
612
- const circle_exit = d3Map(this._circleMap.keys(), function (d: any) { return d; });
613
- this.data().forEach(function (row) {
614
- circle_exit.remove(rowID(row));
615
- if (row[3] && !this._circleMap.has(rowID(row))) {
616
- circle_enter.push(row);
617
- } else if (row[3] && this._circleMap.has(rowID(row))) {
618
- circle_update.push(row);
619
- } else if (!row[3] && this._circleMap.has(rowID(row))) {
620
- circle_exit.set(rowID(row), true);
621
- }
622
- }, this);
623
-
624
- circle_enter.forEach(function (row) {
625
- const marker = this.createCircle(row[0], row[1], row[3]);
626
- this._circleMap.set(rowID(row), marker);
627
- }, this);
628
-
629
- circle_update.forEach(function (row) {
630
- // this._pinMap.get(rowID(row)).setIcon(this.createIcon(row[3]));
631
- }, this);
632
-
633
- const context = this;
634
- circle_exit.each(function (row) {
635
- context._circleMap.get(row).setMap(null);
636
- context._circleMap.remove(row);
637
- });
638
- }
639
-
640
- updatePins() {
641
- function rowID(row) {
642
- return row[0] + "_" + row[1];
643
- }
644
-
645
- const pin_enter = [];
646
- const pin_update = [];
647
- const pin_exit = d3Map(this._pinMap.keys(), function (d: any) { return d; });
648
- this.data().forEach(function (row) {
649
- pin_exit.remove(rowID(row));
650
- if (row[2] && !this._pinMap.has(rowID(row))) {
651
- pin_enter.push(row);
652
- } else if (row[2] && this._pinMap.has(rowID(row))) {
653
- pin_update.push(row);
654
- } else if (!row[2] && this._pinMap.has(rowID(row))) {
655
- pin_exit.set(rowID(row), true);
656
- }
657
- }, this);
658
-
659
- pin_enter.forEach(function (row) {
660
- const marker = this.createMarker(row[0], row[1], row[2]);
661
- this._pinMap.set(rowID(row), marker);
662
- }, this);
663
-
664
- pin_update.forEach(function (row) {
665
- this._pinMap.get(rowID(row)).setIcon(this.createIcon(row[2]));
666
- }, this);
667
-
668
- const context = this;
669
- pin_exit.each(function (row) {
670
- context._pinMap.get(row).setMap(null);
671
- context._pinMap.remove(row);
672
- });
673
- }
674
-
675
- createIcon(pinObj: { fillColor: string; fillOpacity?: number; strokeColor?: string }) {
676
- return {
677
- path: "M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30", // a 2,2 0 1,1 4,0 2,2 0 1,1",
678
- fillColor: pinObj.fillColor,
679
- fillOpacity: pinObj.fillOpacity || 0.8,
680
- scale: 0.5,
681
- strokeColor: pinObj.strokeColor || "black",
682
- strokeWeight: 0.25
683
- };
684
- }
685
-
686
- createMarker(lat, lng, pinObj: { fillColor: string; fillOpacity?: number; strokeColor?: string; title?: string }) {
687
- return new google.maps.Marker({
688
- position: new google.maps.LatLng(lat, lng),
689
- animation: google.maps.Animation.DROP,
690
- title: pinObj.title || "",
691
- icon: this.createIcon(pinObj),
692
- map: this._googleMap
693
- });
694
- }
695
-
696
- createCircle(lat, lng, circleObj: { radius?: number; fillColor?: string; strokeColor?: string }) {
697
- circleObj.radius = circleObj.radius || 1;
698
- return new google.maps.Circle({
699
- center: new google.maps.LatLng(lat, lng),
700
- radius: 16093 * circleObj.radius / 10, // 16093 === 10 miles in metres
701
- fillColor: circleObj.fillColor || "red",
702
- strokeColor: circleObj.strokeColor || circleObj.fillColor || "black",
703
- strokeWeight: 0.5,
704
- map: this._googleMap
705
- });
706
- }
707
-
708
- zoomTo(selection, singleMaxZoom?) {
709
- if (!this._renderCount) return this;
710
- singleMaxZoom = singleMaxZoom || this.singleZoomToMaxZoom();
711
- let foundCount = 0;
712
- const latlngbounds = new google.maps.LatLngBounds();
713
- selection.forEach(function (item) {
714
- const gLatLong = new google.maps.LatLng(item[0], item[1]);
715
- latlngbounds.extend(gLatLong);
716
- ++foundCount;
717
- });
718
- switch (foundCount) {
719
- case 0:
720
- break;
721
- case 1:
722
- this._googleMap.setCenter(latlngbounds.getCenter());
723
- this._googleMap.setZoom(singleMaxZoom);
724
- break;
725
- default:
726
- this._googleMap.fitBounds(latlngbounds);
727
- }
728
- return this;
729
- }
730
-
731
- zoomToFit() {
732
- return this.zoomTo(this.data());
733
- }
734
-
735
- drawingOptions(_) {
736
- if (!arguments.length) {
737
- return this._drawingManager;
738
- }
739
- this._drawingManager.setOptions(_);
740
- return this;
741
- }
742
-
743
- userShapeSelection(_) {
744
- if (!arguments.length) return this._userShapeSelection;
745
- if (this._userShapeSelection) {
746
- this._userShapeSelection.setEditable(false);
747
- }
748
- this._userShapeSelection = _;
749
- if (this._userShapeSelection) {
750
- this._userShapeSelection.setEditable(true);
751
- }
752
- return this;
753
- }
754
-
755
- deleteUserShape(_) {
756
- if (this._userShapeSelection === _) {
757
- this.userShapeSelection(null);
758
- }
759
- this._userShapes.remove(_);
760
- }
761
-
762
- onDrawingComplete(event) {
763
- if (event.type !== google.maps.drawing.OverlayType.MARKER) {
764
- this._drawingManager.setDrawingMode(null);
765
- const newShape = event.overlay;
766
- newShape.__hpcc_type = event.type;
767
- this._userShapes.add(newShape);
768
- const context = this;
769
- let ctrl = false;
770
- window.addEventListener("keydown", function (e: any) {
771
- if (e.keyIdentifier === "Control" || e.ctrlKey === true) {
772
- ctrl = true;
773
- }
774
- });
775
- window.addEventListener("keyup", function (e) {
776
- if (e.ctrlKey === false) {
777
- ctrl = false;
778
- }
779
- });
780
- google.maps.event.addListener(newShape, "click", function (ev) {
781
- context.userShapeSelection(newShape);
782
- if (ev && ctrl === true) {
783
- context.deleteUserShape(newShape);
784
- context.drawingState(
785
- JSON.stringify(context._userShapes.save()));
786
- }
787
- return false;
788
- });
789
- this.userShapeSelection(newShape);
790
- this.drawingState(
791
- JSON.stringify(this._userShapes.save()));
792
- }
793
- }
794
- statusError(response) {
795
- console.warn("Data not found for this location.");
796
- }
797
- }
798
- GMap.prototype._class += " map_GMap";
799
-
800
- export interface GMap {
801
- type(): string;
802
- type(_: string): this;
803
- type_exists(): boolean;
804
- centerLat(): number;
805
- centerLat(_: number);
806
- centerLat_exists(): boolean;
807
- centerLong(): number;
808
- centerLong(_: number): this;
809
- centerLong_exists(): boolean;
810
- centerAddress(): string;
811
- centerAddress(_: string): this;
812
- centerAddress_exists(): boolean;
813
- zoom(): number;
814
- zoom(_: number): this;
815
- zoom_exists(): boolean;
816
- singleZoomToMaxZoom(): number;
817
- singleZoomToMaxZoom(_: number): this;
818
- panControl(): boolean;
819
- panControl(_: boolean): this;
820
- panControl_exists(): boolean;
821
- zoomControl(): boolean;
822
- zoomControl(_: boolean): this;
823
- zoomControl_exists(): boolean;
824
- scaleControl(): boolean;
825
- scaleControl(_: boolean): this;
826
- scaleControl_exists(): boolean;
827
- mapTypeControl(): boolean;
828
- mapTypeControl(_: boolean): this;
829
- mapTypeControl_exists(): boolean;
830
- fullscreenControl(): boolean;
831
- fullscreenControl(_: boolean): this;
832
- fullscreenControl_exists(): boolean;
833
- streetViewControl(): boolean;
834
- streetViewControl(_: boolean): this;
835
- streetViewControl_exists(): boolean;
836
- overviewMapControl(): boolean;
837
- overviewMapControl(_: boolean): this;
838
- overviewMapControl_exists(): boolean;
839
- streetView(): boolean;
840
- streetView(_: boolean): this;
841
- streetView_exists(): boolean;
842
- drawingTools(): boolean;
843
- drawingTools(_: boolean): this;
844
- drawingTools_exists(): boolean;
845
- drawingState(): string;
846
- drawingState(_: string): this;
847
- drawingState_exists(): boolean;
848
- googleMapStyles(): object;
849
- googleMapStyles(_: object): this;
850
- googleMapStyles_exists(): boolean;
851
- useComputedHeading(): boolean;
852
- useComputedHeading(_: boolean): this;
853
- showStreetViewMarker(): boolean;
854
- showStreetViewMarker(_: boolean): this;
855
- outdoorStreetViewOnly(): boolean;
856
- outdoorStreetViewOnly(_: boolean): this;
857
- }
858
-
859
- GMap.prototype.publish("outdoorStreetViewOnly", false, "boolean", "If true, streetView will only display outdoor locations");
860
- GMap.prototype.publish("showStreetViewMarker", false, "boolean", "If true, streetView marker will be hidden");
861
- GMap.prototype.publish("useComputedHeading", false, "boolean", "If true, centerAddress streetView compute the ideal panorama heading");
862
- GMap.prototype.publish("type", "road", "set", "Map Type", ["terrain", "road", "satellite", "hybrid"], { tags: ["Basic"] });
863
- GMap.prototype.publish("centerLat", 42.877742, "number", "Center Latitude", null, { tags: ["Basic"] });
864
- GMap.prototype.publish("centerLong", -97.380979, "number", "Center Longitude", null, { tags: ["Basic"] });
865
- GMap.prototype.publish("centerAddress", null, "string", "Address to center map on", null, { tags: ["Basic"], optional: true });
866
- GMap.prototype.publish("zoom", 4, "number", "Zoom Level", null, { tags: ["Basic"] });
867
- GMap.prototype.publish("singleZoomToMaxZoom", 14, "number", "Max zoomTo level with single item");
868
- GMap.prototype.publish("panControl", true, "boolean", "Pan Controls", null, { tags: ["Basic"] });
869
- GMap.prototype.publish("zoomControl", true, "boolean", "Zoom Controls", null, { tags: ["Basic"] });
870
- GMap.prototype.publish("scaleControl", true, "boolean", "Scale Controls", null, { tags: ["Basic"] });
871
- GMap.prototype.publish("mapTypeControl", false, "boolean", "Map Type Controls", null, { tags: ["Basic"] });
872
- GMap.prototype.publish("fullscreenControl", false, "boolean", "Fullscreen Controls", null, { tags: ["Basic"] });
873
- GMap.prototype.publish("streetViewControl", false, "boolean", "StreetView Controls", null, { tags: ["Basic"] });
874
- GMap.prototype.publish("overviewMapControl", false, "boolean", "OverviewMap Controls", null, { tags: ["Basic"] });
875
- GMap.prototype.publish("streetView", false, "boolean", "Streetview", null, { tags: ["Basic"] });
876
- GMap.prototype.publish("drawingTools", false, "boolean", "Drawing Tools", null, { tags: ["Basic"] });
877
- GMap.prototype.publish("drawingState", "", "string", "Map Drawings", null, { disable: w => w.drawingTools() === false });
878
-
879
- GMap.prototype.publish("googleMapStyles", {}, "object", "Styling for map colors etc", null, { tags: ["Basic"] });
1
+ import { HTMLWidget } from "@hpcc-js/common";
2
+ import { AbsoluteSurface } from "@hpcc-js/layout";
3
+ import { promiseTimeout } from "@hpcc-js/util";
4
+ import { map as d3Map } from "d3-collection";
5
+ import * as _GoogleMapsLoader from "google-maps";
6
+
7
+ const GoogleMapsLoader = _GoogleMapsLoader.default || _GoogleMapsLoader;
8
+
9
+ import "../src/GMap.css";
10
+
11
+ declare const window: any;
12
+
13
+ export let google: any = null;
14
+
15
+ let timout = 15000;
16
+ export function requireGoogleMapTimeout(ms: number) {
17
+ timout = ms;
18
+ }
19
+
20
+ let _googleMapPromise;
21
+ export function requireGoogleMap(customGoogle?: any) {
22
+ if (!_googleMapPromise) {
23
+ _googleMapPromise = promiseTimeout(timout, new Promise<void>(function (resolve, reject) {
24
+ if (google) {
25
+ resolve();
26
+ } else if (customGoogle) {
27
+ google = customGoogle;
28
+ resolve();
29
+ } else {
30
+ if (!window.__hpcc_gmap_apikey) {
31
+ console.warn("__hpcc_gmap_apikey does not contain a valid API key, reverting to developers key (expect limited performance)");
32
+ }
33
+ try {
34
+ GoogleMapsLoader.KEY = window.__hpcc_gmap_apikey || "AIzaSyDwGn2i1i_pMZvnqYJN1BksD_tjYaCOWKg";
35
+ GoogleMapsLoader.LIBRARIES = ["geometry", "drawing"];
36
+ GoogleMapsLoader.load(function (_google) {
37
+ google = _google;
38
+ resolve();
39
+ });
40
+ } catch (e) {
41
+ console.warn(`Failed to initialize Google Map API: ${e.message}`);
42
+ resolve();
43
+ }
44
+ }
45
+ })).catch(e => {
46
+ console.warn(`Timed out initializing Google Map API after ${timout}ms.`);
47
+ });
48
+ }
49
+ return _googleMapPromise;
50
+ }
51
+
52
+ function createOverlay(map, worldSurface, viewportSurface) {
53
+ function Overlay(map2, worldSurface2, viewportSurface2) {
54
+ google.maps.OverlayView.call(this);
55
+ this._div = null;
56
+
57
+ this._worldSurface = worldSurface2;
58
+ this._viewportSurface = viewportSurface2;
59
+
60
+ this._map = map2;
61
+ this.setMap(map2);
62
+
63
+ const context = this;
64
+ google.maps.event.addListener(map2, "bounds_changed", function () {
65
+ context.draw();
66
+ });
67
+ google.maps.event.addListener(map2, "projection_changed", function () {
68
+ context.draw();
69
+ });
70
+
71
+ this._prevWorldMin = { x: 0, y: 0 };
72
+ this._prevWorldMax = { x: 0, y: 0 };
73
+ this._prevMin = { x: 0, y: 0 };
74
+ this._prevMax = { x: 0, y: 0 };
75
+ }
76
+ Overlay.prototype = google.maps.OverlayView.prototype;
77
+
78
+ Overlay.prototype.onAdd = function () {
79
+ this.div = document.createElement("div");
80
+
81
+ this._viewportSurface.target(null)
82
+ .target(this.div)
83
+ .units("pixels");
84
+
85
+ const panes = this.getPanes();
86
+ panes.overlayMouseTarget.appendChild(this.div);
87
+ };
88
+
89
+ Overlay.prototype.draw = function () {
90
+ const projection = this.getProjection();
91
+ if (!projection)
92
+ return;
93
+
94
+ const bounds = this._map.getBounds();
95
+ const center = projection.fromLatLngToDivPixel(bounds.getCenter());
96
+ const sw = projection.fromLatLngToDivPixel(bounds.getSouthWest());
97
+ const ne = projection.fromLatLngToDivPixel(bounds.getNorthEast());
98
+
99
+ const min = {
100
+ x: sw.x,
101
+ y: ne.y
102
+ };
103
+ const max = {
104
+ x: ne.x,
105
+ y: sw.y
106
+ };
107
+
108
+ const worldWidth = projection.getWorldWidth();
109
+ while (max.x < min.x + 100) { // Ignoe dateline from being the rect.
110
+ max.x += worldWidth;
111
+ }
112
+ while (min.x > center.x) {
113
+ min.x -= worldWidth;
114
+ max.x -= worldWidth;
115
+ }
116
+
117
+ if (min.x !== this._prevMin.x || min.y !== this._prevMin.y || max.x !== this._prevMax.x || max.y !== this._prevMax.y) {
118
+ this._viewportSurface
119
+ .resize({ width: 0, height: 0 })
120
+ .widgetX(min.x)
121
+ .widgetY(min.y)
122
+ .widgetWidth(max.x - min.x)
123
+ .widgetHeight(max.y - min.y)
124
+ ;
125
+ // FF Issue on initial render (GH-1855) ---
126
+ if (this._viewportSurface._renderCount) {
127
+ this._viewportSurface.render();
128
+ this._prevMin = min;
129
+ this._prevMax = max;
130
+ } else {
131
+ this._viewportSurface.lazyRender();
132
+ }
133
+ }
134
+
135
+ const worldMin = projection.fromLatLngToDivPixel(new google.maps.LatLng(85, -179.9));
136
+ const worldMax = projection.fromLatLngToDivPixel(new google.maps.LatLng(-85, 179.9));
137
+ while (worldMax.x < worldMin.x + 100) { // Ignoe dateline from being the rect.
138
+ worldMax.x += worldWidth;
139
+ }
140
+ while (worldMin.x > center.x) {
141
+ worldMin.x -= worldWidth;
142
+ worldMax.x -= worldWidth;
143
+ }
144
+ if (worldMin.x !== this._prevWorldMin.x || worldMin.y !== this._prevWorldMin.y || worldMax.x !== this._prevWorldMax.x || worldMax.y !== this._prevWorldMax.y) {
145
+ this._worldSurface
146
+ .widgetX(worldMin.x)
147
+ .widgetY(worldMin.y)
148
+ .widgetWidth(worldMax.x - worldMin.x)
149
+ .widgetHeight(worldMax.y - worldMin.y)
150
+ .render()
151
+ ;
152
+ this._prevWorldMin = worldMax;
153
+ this._prevWorldMax = worldMax;
154
+ }
155
+ };
156
+
157
+ Overlay.prototype.onRemove = function () {
158
+ this._viewportSurface.target(null);
159
+ this._div.parentNode.removeChild(this._div);
160
+ this._div = null;
161
+ };
162
+
163
+ return new Overlay(map, worldSurface, viewportSurface);
164
+ }
165
+
166
+ class UserShapeSelectionBag {
167
+ _userShapes: any[];
168
+ mapContext;
169
+
170
+ constructor(mapObj) {
171
+ this._userShapes = [];
172
+ this.mapContext = mapObj;
173
+ }
174
+
175
+ add(_) {
176
+ const idx = this._userShapes.indexOf(_);
177
+ if (idx >= 0) {
178
+ return;
179
+ }
180
+ this._userShapes.push(_);
181
+ }
182
+
183
+ remove(_) {
184
+ const idx = this._userShapes.indexOf(_);
185
+ if (idx >= 0) {
186
+ this._userShapes.splice(idx, 1);
187
+ }
188
+ _.setMap(null);
189
+ }
190
+
191
+ save() {
192
+ return this._userShapes.map(shape => this._saveShape(shape));
193
+ }
194
+
195
+ load(_) {
196
+ this._deserializeShapes(_);
197
+ }
198
+
199
+ _saveShape(shape) {
200
+ const retVal: any = {};
201
+
202
+ const createShapes = {
203
+ circle: (_) => {
204
+ retVal.type = "circle";
205
+ retVal.pos = {
206
+ lat: _.center.lat(),
207
+ lng: _.center.lng()
208
+ };
209
+ retVal.radius = _.radius;
210
+ },
211
+ rectangle: (_) => {
212
+ retVal.type = "rectangle";
213
+ retVal.bounds = {
214
+ ne: _.bounds.getNorthEast(),
215
+ sw: _.bounds.getSouthWest()
216
+ };
217
+ },
218
+ polygon: (_) => {
219
+ retVal.type = _.__hpcc_type;
220
+
221
+ const vertices = _.getPath();
222
+
223
+ retVal.vertices = [];
224
+
225
+ for (let i = 0; i < vertices.length; i++) {
226
+ retVal.vertices.push(vertices.getAt(i));
227
+ }
228
+ },
229
+ polyline: (_) => {
230
+ createShapes.polygon(_);
231
+ }
232
+ };
233
+
234
+ createShapes[shape.__hpcc_type](shape);
235
+ retVal.strokeWeight = shape.strokeWeight;
236
+ retVal.fillColor = shape.fillColor;
237
+ retVal.fillOpacity = shape.fillOpacity;
238
+ retVal.editable = shape.editable;
239
+ retVal.clickable = shape.clickable || true;
240
+
241
+ return retVal;
242
+ }
243
+
244
+ _deserializeShapes(_shapes) {
245
+
246
+ const shapes = JSON.parse(_shapes);
247
+
248
+ const defOptions = {
249
+ strokeWeight: 0,
250
+ fillOpacity: 0.45,
251
+ fillColor: "#1f77b4",
252
+ editable: true,
253
+ clickable: true
254
+ };
255
+
256
+ const createShapes = {
257
+ circle: (_, map) => {
258
+ const shape = new google.maps.Circle({
259
+ strokeWeight: _.strokeWeight || defOptions.strokeWeight,
260
+ fillColor: _.fillColor || defOptions.fillColor,
261
+ fillOpacity: _.fillOpacity || defOptions.fillOpacity,
262
+ editable: _.editable || defOptions.editable,
263
+ clickable: _.clickable || defOptions.clickable,
264
+ map,
265
+ center: _.pos,
266
+ radius: _.radius
267
+ });
268
+ return shape;
269
+ },
270
+ rectangle: (_, map) => {
271
+ const shape = new google.maps.Rectangle({
272
+ strokeWeight: _.strokeWeight || defOptions.strokeWeight,
273
+ fillColor: _.fillColor || defOptions.fillColor,
274
+ fillOpacity: _.fillOpacity || defOptions.fillOpacity,
275
+ editable: _.editable || defOptions.editable,
276
+ clickable: _.clickable || defOptions.clickable,
277
+ map,
278
+ bounds: {
279
+ north: _.bounds.ne.lat,
280
+ west: _.bounds.sw.lng,
281
+ south: _.bounds.sw.lat,
282
+ east: _.bounds.ne.lng
283
+ }
284
+ });
285
+ return shape;
286
+ },
287
+ polygon: (_, map) => {
288
+ const shape = new google.maps.Polygon({
289
+ strokeWeight: _.strokeWeight || defOptions.strokeWeight,
290
+ fillColor: _.fillColor || defOptions.fillColor,
291
+ fillOpacity: _.fillOpacity || defOptions.fillOpacity,
292
+ editable: _.editable || defOptions.editable,
293
+ clickable: _.clickable || defOptions.clickable,
294
+ map,
295
+ paths: _.vertices
296
+ });
297
+ return shape;
298
+ },
299
+ polyline: (_, map) => {
300
+ const shape = new google.maps.Polyline({
301
+ strokeWeight: _.strokeWeight || defOptions.strokeWeight,
302
+ fillColor: _.fillColor || defOptions.fillColor,
303
+ fillOpacity: _.fillOpacity || defOptions.fillOpacity,
304
+ editable: _.editable || defOptions.editable,
305
+ clickable: _.clickable || defOptions.clickable,
306
+ map,
307
+ path: _.vertices
308
+ });
309
+ return shape;
310
+ }
311
+ };
312
+
313
+ for (let i = 0; i < shapes.length; i++) {
314
+ const shape = createShapes[shapes[i].type](shapes[i], this.mapContext._googleMap);
315
+ this.mapContext.onDrawingComplete({ type: shapes[i].type, overlay: shape });
316
+ }
317
+ }
318
+ }
319
+
320
+ export class GMap extends HTMLWidget {
321
+ _overlay;
322
+ _userShapes;
323
+ _worldSurface;
324
+ _viewportSurface;
325
+ _googleMapNode;
326
+ _googleMap;
327
+ _googleGeocoder;
328
+ _prevCenterLat;
329
+ _prevCenterLong;
330
+ _googleStreetViewService;
331
+ _googleMapPanorama;
332
+ _prevZoom;
333
+ _prevStreetView;
334
+ _circleMap;
335
+ _pinMap;
336
+ _drawingManager;
337
+ _prevCenterAddress;
338
+ _userShapeSelection;
339
+
340
+ constructor() {
341
+ super();
342
+
343
+ this._tag = "div";
344
+
345
+ const context = this;
346
+ function calcProjection(surface, lat, long) {
347
+ const projection = context._overlay.getProjection();
348
+ const retVal = projection.fromLatLngToDivPixel(new google.maps.LatLng(lat, long));
349
+ const worldWidth = projection.getWorldWidth();
350
+ const widgetX = parseFloat(surface.widgetX());
351
+ const widgetY = parseFloat(surface.widgetY());
352
+ const widgetWidth = parseFloat(surface.widgetWidth());
353
+ retVal.x -= widgetX;
354
+ retVal.y -= widgetY;
355
+ while (retVal.x < 0) {
356
+ retVal.x += worldWidth;
357
+ }
358
+ while (retVal.x > widgetWidth) {
359
+ retVal.x -= worldWidth;
360
+ }
361
+ return retVal;
362
+ }
363
+
364
+ this._userShapes = new UserShapeSelectionBag(this);
365
+
366
+ this._worldSurface = new AbsoluteSurface();
367
+ this._worldSurface.project = function (lat, long) {
368
+ return calcProjection(this, lat, long);
369
+ };
370
+
371
+ this._viewportSurface = new AbsoluteSurface();
372
+ this._viewportSurface.project = function (lat, long) {
373
+ return calcProjection(this, lat, long);
374
+ };
375
+ }
376
+
377
+ data(_?) {
378
+ const retVal = HTMLWidget.prototype.data.apply(this, arguments);
379
+ return retVal;
380
+ }
381
+
382
+ getMapType() {
383
+ switch (this.type()) {
384
+ case "terrain":
385
+ return google.maps.MapTypeId.TERRAIN;
386
+ case "road":
387
+ return google.maps.MapTypeId.ROADMAP;
388
+ case "satellite":
389
+ return google.maps.MapTypeId.SATELLITE;
390
+ case "hybrid":
391
+ return google.maps.MapTypeId.HYBRID;
392
+ default:
393
+ return google.maps.MapTypeId.ROADMAP;
394
+ }
395
+ }
396
+
397
+ getMapOptions() {
398
+ return {
399
+ panControl: this.panControl(),
400
+ zoomControl: this.zoomControl(),
401
+ fullscreenControl: this.fullscreenControl(),
402
+ mapTypeControl: this.mapTypeControl(),
403
+ scaleControl: this.scaleControl(),
404
+ streetViewControl: this.streetViewControl(),
405
+ overviewMapControl: this.overviewMapControl(),
406
+ overviewMapControlOptions: { opened: true },
407
+ styles: this.googleMapStyles()
408
+ };
409
+ }
410
+
411
+ size(_?) {
412
+ const retVal = HTMLWidget.prototype.size.apply(this, arguments);
413
+ if (arguments.length && this._googleMapNode) {
414
+ this._googleMapNode
415
+ .style("width", _.width + "px")
416
+ .style("height", _.height + "px")
417
+ ;
418
+ google.maps.event.trigger(this._googleMap, "resize");
419
+ }
420
+ return retVal;
421
+ }
422
+
423
+ enter(domNode, element) {
424
+ super.enter(domNode, element);
425
+ const context = this;
426
+ this._googleGeocoder = new google.maps.Geocoder();
427
+ this._googleMapNode = element.append("div")
428
+ .style("width", this.width() + "px")
429
+ .style("height", this.height() + "px")
430
+ ;
431
+ this._googleMap = new google.maps.Map(this._googleMapNode.node(), {
432
+ zoom: this.zoom(),
433
+ center: new google.maps.LatLng(this.centerLat(), this.centerLong()),
434
+ mapTypeId: this.getMapType(),
435
+ disableDefaultUI: true
436
+ });
437
+ this._overlay = createOverlay(this._googleMap, this._worldSurface, this._viewportSurface);
438
+ this._googleMap.addListener("center_changed", function () {
439
+ context.centerLat(context._googleMap.center.lat());
440
+ context._prevCenterLat = context.centerLat();
441
+ context.centerLong(context._googleMap.center.lng());
442
+ context._prevCenterLong = context.centerLong();
443
+ context._googleMapPanorama.setPosition({ lat: context.centerLat(), lng: context.centerLong() });
444
+ context.zoom(context._googleMap.getZoom());
445
+ context._prevZoom = context.zoom();
446
+ context._overlay.draw();
447
+ });
448
+ this._googleMap.addListener("zoom_changed", function () {
449
+ context.zoom(context._googleMap.zoom);
450
+ context._prevZoom = context.zoom();
451
+ });
452
+ this._googleStreetViewService = new google.maps.StreetViewService();
453
+ this._googleMapPanorama = this._googleMap.getStreetView();
454
+ this._googleMapPanorama.addListener("visible_changed", function () {
455
+ context.streetView(context._googleMapPanorama.getVisible());
456
+ context._prevStreetView = context.streetView();
457
+ });
458
+
459
+ this._circleMap = d3Map([]);
460
+ this._pinMap = d3Map([]);
461
+
462
+ this._prevCenterLat = this.centerLat();
463
+ this._prevCenterLong = this.centerLong();
464
+ this._prevZoom = this.zoom();
465
+
466
+ // Init drawing tools with default options.
467
+ const defOptions = {
468
+ strokeWeight: 0,
469
+ fillOpacity: 0.45,
470
+ fillColor: "#1f77b4",
471
+ editable: true,
472
+ clickable: true
473
+ };
474
+ this._drawingManager = new google.maps.drawing.DrawingManager({
475
+ drawingMode: google.maps.drawing.OverlayType.MARKER,
476
+ drawingControl: true,
477
+ drawingControlOptions: {
478
+ position: google.maps.ControlPosition.TOP_CENTER,
479
+ drawingModes: ["polygon", "rectangle", "circle"]
480
+ },
481
+ rectangleOptions: defOptions,
482
+ circleOptions: defOptions,
483
+ polygonOptions: defOptions
484
+ });
485
+
486
+ if (this.drawingState()) {
487
+ this._userShapes.load(this.drawingState());
488
+ }
489
+ }
490
+
491
+ update(domNode, element) {
492
+ const context = this;
493
+ this._googleMapNode
494
+ .style("width", this.width() + "px")
495
+ .style("height", this.height() + "px")
496
+ ;
497
+
498
+ this._googleMap.setMapTypeId(this.getMapType());
499
+ this._googleMap.setOptions(this.getMapOptions());
500
+
501
+ if (this.centerAddress_exists() && this._prevCenterAddress !== this.centerAddress()) {
502
+ this._prevCenterAddress = this.centerAddress();
503
+ this._googleGeocoder.geocode({ address: this.centerAddress() }, function (results, status) {
504
+ if (status === google.maps.GeocoderStatus.OK) {
505
+ const bounds = results[0].geometry.bounds || results[0].geometry.viewport;
506
+ context._googleMap.fitBounds(bounds);
507
+ if (context.streetView() && context.useComputedHeading()) {
508
+ context.streetViewAt({
509
+ lat: results[0].geometry.location.lat(),
510
+ lng: results[0].geometry.location.lng()
511
+ }, 50);
512
+ }
513
+ } else {
514
+ context.statusError({
515
+ data: results,
516
+ status
517
+ });
518
+ }
519
+ });
520
+ }
521
+ if (this._prevCenterLat !== this.centerLat() || this._prevCenterLong !== this.centerLong()) {
522
+ this._googleMap.setCenter(new google.maps.LatLng(this.centerLat(), this.centerLong()));
523
+
524
+ this._prevCenterLat = this.centerLat();
525
+ this._prevCenterLong = this.centerLong();
526
+ }
527
+ if (this._prevZoom !== this.zoom()) {
528
+ this._googleMap.setZoom(this.zoom());
529
+
530
+ this._prevZoom = this.zoom();
531
+ }
532
+ this.updateCircles();
533
+ this.updatePins();
534
+ if (this._prevStreetView !== this.streetView()) {
535
+ if (this.streetView()) {
536
+ this._googleMapPanorama.setPosition({ lat: this.centerLat(), lng: this.centerLong() });
537
+ this._googleMapPanorama.setPov({
538
+ heading: 0,
539
+ pitch: 0
540
+ });
541
+ this._googleMapPanorama.setVisible(true);
542
+ } else {
543
+ this._googleMapPanorama.setVisible(false);
544
+ }
545
+ this._prevStreetView = this.streetView();
546
+ }
547
+
548
+ // Enable or disable drawing tools.
549
+ if (this.drawingTools()) {
550
+ this._drawingManager.setMap(this._googleMap);
551
+
552
+ // Add drawing complete listener to maintain array of drawingState.
553
+ google.maps.event.addListener(
554
+ this._drawingManager,
555
+ "overlaycomplete",
556
+ function () {
557
+ GMap.prototype.onDrawingComplete.apply(context, arguments);
558
+ });
559
+ } else {
560
+ this._drawingManager.setMap(null);
561
+ google.maps.event.clearInstanceListeners(this._drawingManager);
562
+ }
563
+ }
564
+
565
+ render(callback?) {
566
+ const context = this;
567
+ const args = arguments;
568
+ requireGoogleMap().then(() => {
569
+ super.render.apply(context, args);
570
+ });
571
+ return this;
572
+ }
573
+ streetViewAt(pos, radius = 1000) {
574
+ const context = this;
575
+ const source = this.outdoorStreetViewOnly() ? google.maps.StreetViewSource.OUTDOOR : google.maps.StreetViewSource.DEFAULT;
576
+ this._googleStreetViewService.getPanorama({ location: pos, radius, source }, function (data, status) {
577
+ if (status === "OK") {
578
+ const marker = new google.maps.Marker({
579
+ position: pos,
580
+ map: context._googleMap
581
+ });
582
+ const heading = google.maps.geometry.spherical.computeHeading(data.location.latLng, new google.maps.LatLng(pos.lat, pos.lng));
583
+ context._googleMapPanorama.setPano(data.location.pano);
584
+
585
+ context._googleMapPanorama.setPov({
586
+ heading,
587
+ pitch: 0
588
+ });
589
+ if (!context.showStreetViewMarker()) {
590
+ marker.setVisible(false);
591
+ }
592
+ context._googleMapPanorama.setVisible(true);
593
+ const listener = google.maps.event.addListener(context._googleMap.getStreetView(), "visible_changed", function () {
594
+ if (!this.getVisible()) {
595
+ marker.setMap(null);
596
+ google.maps.event.removeListener(listener);
597
+ }
598
+ });
599
+ } else {
600
+ context.statusError({ data, status });
601
+ }
602
+ });
603
+ }
604
+
605
+ updateCircles() {
606
+ function rowID(row) {
607
+ return row[0] + "_" + row[1];
608
+ }
609
+
610
+ const circle_enter = [];
611
+ const circle_update = [];
612
+ const circle_exit = d3Map(this._circleMap.keys(), function (d: any) { return d; });
613
+ this.data().forEach(function (row) {
614
+ circle_exit.remove(rowID(row));
615
+ if (row[3] && !this._circleMap.has(rowID(row))) {
616
+ circle_enter.push(row);
617
+ } else if (row[3] && this._circleMap.has(rowID(row))) {
618
+ circle_update.push(row);
619
+ } else if (!row[3] && this._circleMap.has(rowID(row))) {
620
+ circle_exit.set(rowID(row), true);
621
+ }
622
+ }, this);
623
+
624
+ circle_enter.forEach(function (row) {
625
+ const marker = this.createCircle(row[0], row[1], row[3]);
626
+ this._circleMap.set(rowID(row), marker);
627
+ }, this);
628
+
629
+ circle_update.forEach(function (row) {
630
+ // this._pinMap.get(rowID(row)).setIcon(this.createIcon(row[3]));
631
+ }, this);
632
+
633
+ const context = this;
634
+ circle_exit.each(function (row) {
635
+ context._circleMap.get(row).setMap(null);
636
+ context._circleMap.remove(row);
637
+ });
638
+ }
639
+
640
+ updatePins() {
641
+ function rowID(row) {
642
+ return row[0] + "_" + row[1];
643
+ }
644
+
645
+ const pin_enter = [];
646
+ const pin_update = [];
647
+ const pin_exit = d3Map(this._pinMap.keys(), function (d: any) { return d; });
648
+ this.data().forEach(function (row) {
649
+ pin_exit.remove(rowID(row));
650
+ if (row[2] && !this._pinMap.has(rowID(row))) {
651
+ pin_enter.push(row);
652
+ } else if (row[2] && this._pinMap.has(rowID(row))) {
653
+ pin_update.push(row);
654
+ } else if (!row[2] && this._pinMap.has(rowID(row))) {
655
+ pin_exit.set(rowID(row), true);
656
+ }
657
+ }, this);
658
+
659
+ pin_enter.forEach(function (row) {
660
+ const marker = this.createMarker(row[0], row[1], row[2]);
661
+ this._pinMap.set(rowID(row), marker);
662
+ }, this);
663
+
664
+ pin_update.forEach(function (row) {
665
+ this._pinMap.get(rowID(row)).setIcon(this.createIcon(row[2]));
666
+ }, this);
667
+
668
+ const context = this;
669
+ pin_exit.each(function (row) {
670
+ context._pinMap.get(row).setMap(null);
671
+ context._pinMap.remove(row);
672
+ });
673
+ }
674
+
675
+ createIcon(pinObj: { fillColor: string; fillOpacity?: number; strokeColor?: string }) {
676
+ return {
677
+ path: "M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30", // a 2,2 0 1,1 4,0 2,2 0 1,1",
678
+ fillColor: pinObj.fillColor,
679
+ fillOpacity: pinObj.fillOpacity || 0.8,
680
+ scale: 0.5,
681
+ strokeColor: pinObj.strokeColor || "black",
682
+ strokeWeight: 0.25
683
+ };
684
+ }
685
+
686
+ createMarker(lat, lng, pinObj: { fillColor: string; fillOpacity?: number; strokeColor?: string; title?: string }) {
687
+ return new google.maps.Marker({
688
+ position: new google.maps.LatLng(lat, lng),
689
+ animation: google.maps.Animation.DROP,
690
+ title: pinObj.title || "",
691
+ icon: this.createIcon(pinObj),
692
+ map: this._googleMap
693
+ });
694
+ }
695
+
696
+ createCircle(lat, lng, circleObj: { radius?: number; fillColor?: string; strokeColor?: string }) {
697
+ circleObj.radius = circleObj.radius || 1;
698
+ return new google.maps.Circle({
699
+ center: new google.maps.LatLng(lat, lng),
700
+ radius: 16093 * circleObj.radius / 10, // 16093 === 10 miles in metres
701
+ fillColor: circleObj.fillColor || "red",
702
+ strokeColor: circleObj.strokeColor || circleObj.fillColor || "black",
703
+ strokeWeight: 0.5,
704
+ map: this._googleMap
705
+ });
706
+ }
707
+
708
+ zoomTo(selection, singleMaxZoom?) {
709
+ if (!this._renderCount) return this;
710
+ singleMaxZoom = singleMaxZoom || this.singleZoomToMaxZoom();
711
+ let foundCount = 0;
712
+ const latlngbounds = new google.maps.LatLngBounds();
713
+ selection.forEach(function (item) {
714
+ const gLatLong = new google.maps.LatLng(item[0], item[1]);
715
+ latlngbounds.extend(gLatLong);
716
+ ++foundCount;
717
+ });
718
+ switch (foundCount) {
719
+ case 0:
720
+ break;
721
+ case 1:
722
+ this._googleMap.setCenter(latlngbounds.getCenter());
723
+ this._googleMap.setZoom(singleMaxZoom);
724
+ break;
725
+ default:
726
+ this._googleMap.fitBounds(latlngbounds);
727
+ }
728
+ return this;
729
+ }
730
+
731
+ zoomToFit() {
732
+ return this.zoomTo(this.data());
733
+ }
734
+
735
+ drawingOptions(_) {
736
+ if (!arguments.length) {
737
+ return this._drawingManager;
738
+ }
739
+ this._drawingManager.setOptions(_);
740
+ return this;
741
+ }
742
+
743
+ userShapeSelection(_) {
744
+ if (!arguments.length) return this._userShapeSelection;
745
+ if (this._userShapeSelection) {
746
+ this._userShapeSelection.setEditable(false);
747
+ }
748
+ this._userShapeSelection = _;
749
+ if (this._userShapeSelection) {
750
+ this._userShapeSelection.setEditable(true);
751
+ }
752
+ return this;
753
+ }
754
+
755
+ deleteUserShape(_) {
756
+ if (this._userShapeSelection === _) {
757
+ this.userShapeSelection(null);
758
+ }
759
+ this._userShapes.remove(_);
760
+ }
761
+
762
+ onDrawingComplete(event) {
763
+ if (event.type !== google.maps.drawing.OverlayType.MARKER) {
764
+ this._drawingManager.setDrawingMode(null);
765
+ const newShape = event.overlay;
766
+ newShape.__hpcc_type = event.type;
767
+ this._userShapes.add(newShape);
768
+ const context = this;
769
+ let ctrl = false;
770
+ window.addEventListener("keydown", function (e: any) {
771
+ if (e.keyIdentifier === "Control" || e.ctrlKey === true) {
772
+ ctrl = true;
773
+ }
774
+ });
775
+ window.addEventListener("keyup", function (e) {
776
+ if (e.ctrlKey === false) {
777
+ ctrl = false;
778
+ }
779
+ });
780
+ google.maps.event.addListener(newShape, "click", function (ev) {
781
+ context.userShapeSelection(newShape);
782
+ if (ev && ctrl === true) {
783
+ context.deleteUserShape(newShape);
784
+ context.drawingState(
785
+ JSON.stringify(context._userShapes.save()));
786
+ }
787
+ return false;
788
+ });
789
+ this.userShapeSelection(newShape);
790
+ this.drawingState(
791
+ JSON.stringify(this._userShapes.save()));
792
+ }
793
+ }
794
+ statusError(response) {
795
+ console.warn("Data not found for this location.");
796
+ }
797
+ }
798
+ GMap.prototype._class += " map_GMap";
799
+
800
+ export interface GMap {
801
+ type(): string;
802
+ type(_: string): this;
803
+ type_exists(): boolean;
804
+ centerLat(): number;
805
+ centerLat(_: number);
806
+ centerLat_exists(): boolean;
807
+ centerLong(): number;
808
+ centerLong(_: number): this;
809
+ centerLong_exists(): boolean;
810
+ centerAddress(): string;
811
+ centerAddress(_: string): this;
812
+ centerAddress_exists(): boolean;
813
+ zoom(): number;
814
+ zoom(_: number): this;
815
+ zoom_exists(): boolean;
816
+ singleZoomToMaxZoom(): number;
817
+ singleZoomToMaxZoom(_: number): this;
818
+ panControl(): boolean;
819
+ panControl(_: boolean): this;
820
+ panControl_exists(): boolean;
821
+ zoomControl(): boolean;
822
+ zoomControl(_: boolean): this;
823
+ zoomControl_exists(): boolean;
824
+ scaleControl(): boolean;
825
+ scaleControl(_: boolean): this;
826
+ scaleControl_exists(): boolean;
827
+ mapTypeControl(): boolean;
828
+ mapTypeControl(_: boolean): this;
829
+ mapTypeControl_exists(): boolean;
830
+ fullscreenControl(): boolean;
831
+ fullscreenControl(_: boolean): this;
832
+ fullscreenControl_exists(): boolean;
833
+ streetViewControl(): boolean;
834
+ streetViewControl(_: boolean): this;
835
+ streetViewControl_exists(): boolean;
836
+ overviewMapControl(): boolean;
837
+ overviewMapControl(_: boolean): this;
838
+ overviewMapControl_exists(): boolean;
839
+ streetView(): boolean;
840
+ streetView(_: boolean): this;
841
+ streetView_exists(): boolean;
842
+ drawingTools(): boolean;
843
+ drawingTools(_: boolean): this;
844
+ drawingTools_exists(): boolean;
845
+ drawingState(): string;
846
+ drawingState(_: string): this;
847
+ drawingState_exists(): boolean;
848
+ googleMapStyles(): object;
849
+ googleMapStyles(_: object): this;
850
+ googleMapStyles_exists(): boolean;
851
+ useComputedHeading(): boolean;
852
+ useComputedHeading(_: boolean): this;
853
+ showStreetViewMarker(): boolean;
854
+ showStreetViewMarker(_: boolean): this;
855
+ outdoorStreetViewOnly(): boolean;
856
+ outdoorStreetViewOnly(_: boolean): this;
857
+ }
858
+
859
+ GMap.prototype.publish("outdoorStreetViewOnly", false, "boolean", "If true, streetView will only display outdoor locations");
860
+ GMap.prototype.publish("showStreetViewMarker", false, "boolean", "If true, streetView marker will be hidden");
861
+ GMap.prototype.publish("useComputedHeading", false, "boolean", "If true, centerAddress streetView compute the ideal panorama heading");
862
+ GMap.prototype.publish("type", "road", "set", "Map Type", ["terrain", "road", "satellite", "hybrid"], { tags: ["Basic"] });
863
+ GMap.prototype.publish("centerLat", 42.877742, "number", "Center Latitude", null, { tags: ["Basic"] });
864
+ GMap.prototype.publish("centerLong", -97.380979, "number", "Center Longitude", null, { tags: ["Basic"] });
865
+ GMap.prototype.publish("centerAddress", null, "string", "Address to center map on", null, { tags: ["Basic"], optional: true });
866
+ GMap.prototype.publish("zoom", 4, "number", "Zoom Level", null, { tags: ["Basic"] });
867
+ GMap.prototype.publish("singleZoomToMaxZoom", 14, "number", "Max zoomTo level with single item");
868
+ GMap.prototype.publish("panControl", true, "boolean", "Pan Controls", null, { tags: ["Basic"] });
869
+ GMap.prototype.publish("zoomControl", true, "boolean", "Zoom Controls", null, { tags: ["Basic"] });
870
+ GMap.prototype.publish("scaleControl", true, "boolean", "Scale Controls", null, { tags: ["Basic"] });
871
+ GMap.prototype.publish("mapTypeControl", false, "boolean", "Map Type Controls", null, { tags: ["Basic"] });
872
+ GMap.prototype.publish("fullscreenControl", false, "boolean", "Fullscreen Controls", null, { tags: ["Basic"] });
873
+ GMap.prototype.publish("streetViewControl", false, "boolean", "StreetView Controls", null, { tags: ["Basic"] });
874
+ GMap.prototype.publish("overviewMapControl", false, "boolean", "OverviewMap Controls", null, { tags: ["Basic"] });
875
+ GMap.prototype.publish("streetView", false, "boolean", "Streetview", null, { tags: ["Basic"] });
876
+ GMap.prototype.publish("drawingTools", false, "boolean", "Drawing Tools", null, { tags: ["Basic"] });
877
+ GMap.prototype.publish("drawingState", "", "string", "Map Drawings", null, { disable: w => w.drawingTools() === false });
878
+
879
+ GMap.prototype.publish("googleMapStyles", {}, "object", "Styling for map colors etc", null, { tags: ["Basic"] });