geojs 1.7.3 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geojs",
3
- "version": "1.7.3",
3
+ "version": "1.8.2",
4
4
  "description": "JavaScript Geo Visualization and Analysis Library",
5
5
  "homepage": "https://github.com/OpenGeoscience/geojs",
6
6
  "license": "Apache-2.0",
@@ -52,7 +52,7 @@
52
52
  "express": "^4.17.2",
53
53
  "fs-extra": "^10.0.0",
54
54
  "glob": "^7.2.0",
55
- "glslang-validator-prebuilt": "0.0.6",
55
+ "glslang-validator-prebuilt": "^0.0.7",
56
56
  "imports-loader": "^3.1.1",
57
57
  "jaguarjs-jsdoc": "^1.0.2",
58
58
  "jasmine-core": "^4.0.0",
@@ -60,7 +60,7 @@
60
60
  "jsdoc": "^3.6.10",
61
61
  "jsdoc-autoprivate": "0.0.1",
62
62
  "jsonlint-mod": "^1.7.6",
63
- "jstransformer-markdown-it": "^2.0.0",
63
+ "jstransformer-markdown-it": "jstransformers/jstransformer-markdown-it#snyk-fix-9cae6682d6a0ba4f72fd9fec4e488acd",
64
64
  "karma": "^6.3.15",
65
65
  "karma-chrome-launcher": "^3.1.0",
66
66
  "karma-coverage": "^2.1.1",
package/src/annotation.js CHANGED
@@ -6,6 +6,7 @@ var transform = require('./transform');
6
6
  var util = require('./util');
7
7
  var registerAnnotation = require('./registry').registerAnnotation;
8
8
  var lineFeature = require('./lineFeature');
9
+ var markerFeature = require('./markerFeature');
9
10
  var pointFeature = require('./pointFeature');
10
11
  var polygonFeature = require('./polygonFeature');
11
12
  var textFeature = require('./textFeature');
@@ -127,6 +128,14 @@ var annotation = function (type, args) {
127
128
  delete m_options.label;
128
129
  delete m_options.description;
129
130
 
131
+ if (m_options.constraint) {
132
+ if (util.isFunction(m_options.constraint)) {
133
+ this._selectionConstraint = m_options.constraint;
134
+ } else {
135
+ this._selectionConstraint = constrainAspectRatio(m_options.constraint);
136
+ }
137
+ }
138
+
130
139
  /**
131
140
  * Clean up any resources that the annotation is using.
132
141
  */
@@ -420,7 +429,7 @@ var annotation = function (type, args) {
420
429
  };
421
430
 
422
431
  /**
423
- * Set or get options.
432
+ * Get or set options.
424
433
  *
425
434
  * @param {string|object} [arg1] If `undefined`, return the options object.
426
435
  * If a string, either set or return the option of that name. If an
@@ -485,7 +494,7 @@ var annotation = function (type, args) {
485
494
  };
486
495
 
487
496
  /**
488
- * Set or get style.
497
+ * Get or set style.
489
498
  *
490
499
  * @param {string|object} [arg1] If `undefined`, return the current style
491
500
  * object. If a string and `arg2` is undefined, return the style
@@ -1182,10 +1191,10 @@ function continuousVerticesProcessAction(m_this, evt, name) {
1182
1191
  * finished rectangle. This uses styles for {@link geo.polygonFeature}.
1183
1192
  * @property {geo.polygonFeature.styleSpec} [editStyle] The style to apply to a
1184
1193
  * rectangle in edit mode.
1185
- */
1186
- /*
1187
- * @typedef {object} geo.rectangleAnnotation.subspec
1188
- * @typedef {geo.annotation.spec | geo.rectangleAnnotation.subspec} geo.rectangleAnnotation.spec
1194
+ * @property {number|number[]|function} [constraint] If specified, an aspect
1195
+ * ratio or list of aspect ratios to constraint the rectangle to. If a
1196
+ * function, a selection constraint function to call to adjust the
1197
+ * rectangle.
1189
1198
  */
1190
1199
 
1191
1200
  /**
@@ -1200,11 +1209,12 @@ function continuousVerticesProcessAction(m_this, evt, name) {
1200
1209
  * @extends geo.annotation
1201
1210
  *
1202
1211
  * @param {geo.rectangleAnnotation.spec?} [args] Options for the annotation.
1212
+ * @param {string} [annotationName='rectangle'] Override the annotation name.
1203
1213
  */
1204
- var rectangleAnnotation = function (args) {
1214
+ var rectangleAnnotation = function (args, annotationName) {
1205
1215
  'use strict';
1206
1216
  if (!(this instanceof rectangleAnnotation)) {
1207
- return new rectangleAnnotation(args);
1217
+ return new rectangleAnnotation(args, annotationName);
1208
1218
  }
1209
1219
 
1210
1220
  args = $.extend(true, {}, {
@@ -1232,7 +1242,7 @@ var rectangleAnnotation = function (args) {
1232
1242
  }, args || {});
1233
1243
  args.corners = args.corners || args.coordinates || [];
1234
1244
  delete args.coordinates;
1235
- annotation.call(this, 'rectangle', args);
1245
+ annotation.call(this, annotationName || 'rectangle', args);
1236
1246
 
1237
1247
  var m_this = this,
1238
1248
  s_actions = this.actions,
@@ -1257,7 +1267,8 @@ var rectangleAnnotation = function (args) {
1257
1267
  owner: annotationActionOwner,
1258
1268
  input: 'left',
1259
1269
  modifiers: {shift: false, ctrl: false},
1260
- selectionRectangle: true
1270
+ selectionRectangle: true,
1271
+ selectionConstraint: this._selectionConstraint
1261
1272
  }];
1262
1273
  default:
1263
1274
  return s_actions.apply(m_this, arguments);
@@ -1288,6 +1299,9 @@ var rectangleAnnotation = function (args) {
1288
1299
  map.displayToGcs({x: evt.upperRight.x, y: evt.upperRight.y}, null),
1289
1300
  map.displayToGcs({x: evt.upperRight.x, y: evt.lowerLeft.y}, null)
1290
1301
  ];
1302
+ if (this._selectionConstraint && evt.mouse && evt.state.origin) {
1303
+ this._selectionConstraint(evt.mouse.mapgcs, evt.state.origin.mapgcs, corners);
1304
+ }
1291
1305
  /* Don't keep rectangles that have nearly zero area in display pixels */
1292
1306
  if (layer.displayDistance(corners[0], null, corners[1], null) *
1293
1307
  layer.displayDistance(corners[0], null, corners[3], null) < 0.01) {
@@ -1307,29 +1321,17 @@ var rectangleAnnotation = function (args) {
1307
1321
  var opt = m_this.options(),
1308
1322
  state = m_this.state(),
1309
1323
  features;
1310
- switch (state) {
1311
- case annotationState.create:
1312
- features = [];
1313
- if (opt.corners && opt.corners.length >= 4) {
1314
- features = [{
1315
- polygon: {
1316
- polygon: opt.corners,
1317
- style: m_this.styleForState(state)
1318
- }
1319
- }];
1324
+ features = [];
1325
+ if (opt.corners && opt.corners.length >= 4) {
1326
+ features = [{
1327
+ polygon: {
1328
+ polygon: opt.corners,
1329
+ style: m_this.styleForState(state)
1320
1330
  }
1321
- break;
1322
- default:
1323
- features = [{
1324
- polygon: {
1325
- polygon: opt.corners,
1326
- style: m_this.styleForState(state)
1327
- }
1328
- }];
1329
- if (state === annotationState.edit) {
1330
- m_this._addEditHandles(features, opt.corners);
1331
- }
1332
- break;
1331
+ }];
1332
+ }
1333
+ if (state === annotationState.edit) {
1334
+ m_this._addEditHandles(features, opt.corners);
1333
1335
  }
1334
1336
  return features;
1335
1337
  };
@@ -1410,6 +1412,9 @@ var rectangleAnnotation = function (args) {
1410
1412
  corners[2] = $.extend({}, evt.mapgcs);
1411
1413
  corners[1] = map.displayToGcs(c1, null);
1412
1414
  corners[3] = map.displayToGcs(c3, null);
1415
+ if (this._selectionConstraint) {
1416
+ this._selectionConstraint(evt.mapgcs, corners[0], corners);
1417
+ }
1413
1418
  };
1414
1419
 
1415
1420
  /**
@@ -1530,6 +1535,9 @@ var rectangleAnnotation = function (args) {
1530
1535
  corners[(index + 1) % 4].y += delta1.y;
1531
1536
  corners[(index + 3) % 4].x += delta2.x;
1532
1537
  corners[(index + 3) % 4].y += delta2.y;
1538
+ if (this._selectionConstraint) {
1539
+ corners = this._selectionConstraint(evt.mouse.mapgcs, evt.state.origin.mapgcs, corners, 'vertex', ang, index).corners;
1540
+ }
1533
1541
  m_this.options('corners', corners);
1534
1542
  return true;
1535
1543
  case 'edge':
@@ -1545,6 +1553,9 @@ var rectangleAnnotation = function (args) {
1545
1553
  corners[index].y += delta.y;
1546
1554
  corners[(index + 1) % 4].x += delta.x;
1547
1555
  corners[(index + 1) % 4].y += delta.y;
1556
+ if (this._selectionConstraint) {
1557
+ corners = this._selectionConstraint(evt.mouse.mapgcs, evt.state.origin.mapgcs, corners, 'edge', ang, index).corners;
1558
+ }
1548
1559
  m_this.options('corners', corners);
1549
1560
  return true;
1550
1561
  }
@@ -1557,6 +1568,127 @@ var rectangleRequiredFeatures = {};
1557
1568
  rectangleRequiredFeatures[polygonFeature.capabilities.feature] = true;
1558
1569
  registerAnnotation('rectangle', rectangleAnnotation, rectangleRequiredFeatures);
1559
1570
 
1571
+ /**
1572
+ * Square annotation class.
1573
+ *
1574
+ * Squares are a subset of rectangles with a fixed aspect ratio.
1575
+ *
1576
+ * @class
1577
+ * @alias geo.squareAnnotation
1578
+ * @extends geo.annotation
1579
+ *
1580
+ * @param {geo.squareAnnotation.spec?} [args] Options for the annotation.
1581
+ * @param {string} [annotationName='square'] Override the annotation name.
1582
+ */
1583
+ var squareAnnotation = function (args, annotationName) {
1584
+ 'use strict';
1585
+ args = $.extend({}, args, {constraint: 1});
1586
+ if (!(this instanceof squareAnnotation)) {
1587
+ return new squareAnnotation(args, annotationName);
1588
+ }
1589
+ rectangleAnnotation.call(this, args, annotationName || 'square');
1590
+ };
1591
+ inherit(squareAnnotation, rectangleAnnotation);
1592
+ var squareRequiredFeatures = {};
1593
+ squareRequiredFeatures[polygonFeature.capabilities.feature] = true;
1594
+ registerAnnotation('square', squareAnnotation, squareRequiredFeatures);
1595
+
1596
+ /**
1597
+ * Ellipse annotation class.
1598
+ *
1599
+ * Ellipses are always rendered as markers.
1600
+ *
1601
+ * @class
1602
+ * @alias geo.ellipseAnnotation
1603
+ * @extends geo.annotation
1604
+ *
1605
+ * @param {geo.ellipseAnnotation.spec?} [args] Options for the annotation.
1606
+ * @param {string} [annotationName='ellipse'] Override the annotation name.
1607
+ */
1608
+ var ellipseAnnotation = function (args, annotationName) {
1609
+ 'use strict';
1610
+ if (!(this instanceof ellipseAnnotation)) {
1611
+ return new ellipseAnnotation(args, annotationName);
1612
+ }
1613
+
1614
+ rectangleAnnotation.call(this, args, annotationName || 'ellipse');
1615
+
1616
+ var m_this = this;
1617
+
1618
+ /**
1619
+ * Get a list of renderable features for this annotation.
1620
+ *
1621
+ * @returns {array} An array of features.
1622
+ */
1623
+ this.features = function () {
1624
+ var opt = m_this.options(),
1625
+ state = m_this.state(),
1626
+ features;
1627
+ features = [];
1628
+ if (opt.corners && opt.corners.length >= 4) {
1629
+ const style = m_this.styleForState(state);
1630
+ const w = ((opt.corners[0].x - opt.corners[1].x) ** 2 + (opt.corners[0].y - opt.corners[1].y) ** 2) ** 0.5;
1631
+ const h = ((opt.corners[0].x - opt.corners[3].x) ** 2 + (opt.corners[0].y - opt.corners[3].y) ** 2) ** 0.5;
1632
+ const radius = Math.max(w, h) / 2 / m_this.layer().map().unitsPerPixel(0);
1633
+ const aspect = w ? h / w : 1e20;
1634
+ const rotation = -Math.atan2(opt.corners[1].y - opt.corners[0].y, opt.corners[1].x - opt.corners[0].x);
1635
+ features = [{
1636
+ marker: {
1637
+ x: (opt.corners[0].x + opt.corners[1].x + opt.corners[2].x + opt.corners[3].x) / 4,
1638
+ y: (opt.corners[0].y + opt.corners[1].y + opt.corners[2].y + opt.corners[3].y) / 4,
1639
+ style: $.extend(
1640
+ {}, style,
1641
+ {
1642
+ radius: radius,
1643
+ symbolValue: aspect,
1644
+ rotation: rotation,
1645
+ strokeOffset: 0,
1646
+ radiusIncludesStroke: false,
1647
+ scaleWithZoom: markerFeature.scaleMode.fill,
1648
+ rotateWithMap: true,
1649
+ strokeOpacity: style.stroke === false ? 0 : style.strokeOpacity,
1650
+ fillOpacity: style.fill === false ? 0 : style.fillOpacity
1651
+ })
1652
+ }
1653
+ }];
1654
+ }
1655
+ if (state === annotationState.edit) {
1656
+ m_this._addEditHandles(features, opt.corners);
1657
+ }
1658
+ return features;
1659
+ };
1660
+ };
1661
+ inherit(ellipseAnnotation, rectangleAnnotation);
1662
+
1663
+ var ellipseRequiredFeatures = {};
1664
+ ellipseRequiredFeatures[markerFeature.capabilities.feature] = true;
1665
+ registerAnnotation('ellipse', ellipseAnnotation, ellipseRequiredFeatures);
1666
+
1667
+ /**
1668
+ * Circle annotation class.
1669
+ *
1670
+ * Circles are a subset of rectangles with a fixed aspect ratio.
1671
+ *
1672
+ * @class
1673
+ * @alias geo.circleAnnotation
1674
+ * @extends geo.annotation
1675
+ *
1676
+ * @param {geo.circleAnnotation.spec?} [args] Options for the annotation.
1677
+ * @param {string} [annotationName='circle'] Override the annotation name.
1678
+ */
1679
+ var circleAnnotation = function (args, annotationName) {
1680
+ 'use strict';
1681
+ args = $.extend({}, args, {constraint: 1});
1682
+ if (!(this instanceof circleAnnotation)) {
1683
+ return new circleAnnotation(args, annotationName);
1684
+ }
1685
+ ellipseAnnotation.call(this, args, annotationName || 'circle');
1686
+ };
1687
+ inherit(circleAnnotation, ellipseAnnotation);
1688
+ var circleRequiredFeatures = {};
1689
+ circleRequiredFeatures[markerFeature.capabilities.feature] = true;
1690
+ registerAnnotation('circle', circleAnnotation, circleRequiredFeatures);
1691
+
1560
1692
  /**
1561
1693
  * Polygon annotation specification. Extends {@link geo.annotation.spec}.
1562
1694
  *
@@ -2377,6 +2509,137 @@ var pointRequiredFeatures = {};
2377
2509
  pointRequiredFeatures[pointFeature.capabilities.feature] = true;
2378
2510
  registerAnnotation('point', pointAnnotation, pointRequiredFeatures);
2379
2511
 
2512
+ /**
2513
+ * Return a function that can be used as a selectionConstraint that requires
2514
+ * that the aspect ratio of a rectangle-like selection is a specific value or
2515
+ * range of values.
2516
+ *
2517
+ * @param {number|number[]} ratio Either a single aspect ratio or a list of
2518
+ * allowed aspect ratios. For instance, 1 will require that the selection
2519
+ * square, 2 would require that it is twice as wide as tall, [2, 1/2] would
2520
+ * allow it to be twice as wide or half as wide as it is tall.
2521
+ * @returns {function} A function that can be passed to the mapIterator
2522
+ * selectionConstraint or to an annotation constraint function.
2523
+ */
2524
+ function constrainAspectRatio(ratio) {
2525
+ const ratios = Array.isArray(ratio) ? ratio : [ratio];
2526
+
2527
+ /**
2528
+ * Constrain a mouse action or annotation action to a list of aspect ratios.
2529
+ *
2530
+ * @param {geo.geoPosition} pos Mouse or new location in map gcs.
2531
+ * @param {geo.geoPosition} origin Origin in map gcs when the activity
2532
+ * started.
2533
+ * @param {geo.geoPosition} [corners] If specified, an array of corner
2534
+ * locations in mapgcs. This may be modified.
2535
+ * @param {string?} [mode] 'edge', 'vertex' or falsy. A falsy value implies
2536
+ * this is just the most recent point in the annotation, otherwise it is
2537
+ * the portion of the annotation being modified.
2538
+ * @param {number} [ang] A list of angles of each side of the polygon
2539
+ * represented by corners or the original annotation.
2540
+ * @param {integer} [index] The specific vertex or edge that is being
2541
+ * modified.
2542
+ * @returns {object} An object with the ``origin`` (this is what is passed
2543
+ * in), a new position as ``pos``, and the updated corners as ``corners``.
2544
+ */
2545
+ function constraintFunction(pos, origin, corners, mode, ang, index) {
2546
+ let newpos = pos;
2547
+ let best;
2548
+ if (!corners) {
2549
+ corners = [
2550
+ {x: origin.x, y: origin.y},
2551
+ {x: pos.x, y: origin.y},
2552
+ {x: pos.x, y: pos.y},
2553
+ {x: origin.x, y: pos.y}
2554
+ ];
2555
+ }
2556
+ if (mode) {
2557
+ /* Edit a vertex or edge */
2558
+ const i1 = (index + 1) % 4;
2559
+ const i2 = (index + 2) % 4;
2560
+ const i3 = (index + 3) % 4;
2561
+ const dist1 = ((corners[index].x - corners[i1].x) ** 2 + (corners[index].y - corners[i1].y) ** 2) ** 0.5;
2562
+ const dist3 = ((corners[index].x - corners[i3].x) ** 2 + (corners[index].y - corners[i3].y) ** 2) ** 0.5;
2563
+ const area = Math.abs(dist1 * dist3);
2564
+ let shape, edge;
2565
+ ratios.forEach((ratio) => {
2566
+ if (ratio !== 1 && !(index % 2)) {
2567
+ ratio = 1.0 / ratio;
2568
+ }
2569
+ const width = (area * ratio) ** 0.5;
2570
+ const score = (width - dist3) ** 2 + (width / ratio - dist1) ** 2;
2571
+ if (best === undefined || score < best) {
2572
+ best = score;
2573
+ shape = {
2574
+ w: width,
2575
+ h: width / ratio
2576
+ };
2577
+ }
2578
+ });
2579
+ const ang1 = ang[i1];
2580
+ const delta1 = {
2581
+ x: -shape.w * Math.cos(ang1),
2582
+ y: -shape.w * Math.sin(ang1)
2583
+ };
2584
+ const ang2 = ang[index];
2585
+ const delta2 = {
2586
+ x: -shape.h * Math.cos(ang2),
2587
+ y: -shape.h * Math.sin(ang2)
2588
+ };
2589
+ switch (mode) {
2590
+ case 'vertex':
2591
+ corners[index].x = corners[i2].x + delta1.x + delta2.x;
2592
+ corners[index].y = corners[i2].y + delta1.y + delta2.y;
2593
+ corners[i1].x = corners[i2].x + delta1.x;
2594
+ corners[i1].y = corners[i2].y + delta1.y;
2595
+ corners[i3].x = corners[i2].x + delta2.x;
2596
+ corners[i3].y = corners[i2].y + delta2.y;
2597
+ break;
2598
+ case 'edge':
2599
+ edge = {
2600
+ x: (corners[i2].x + corners[i3].x) * 0.5,
2601
+ y: (corners[i2].y + corners[i3].y) * 0.5
2602
+ };
2603
+ corners[i2].x = edge.x + delta2.x / 2;
2604
+ corners[i2].y = edge.y + delta2.y / 2;
2605
+ corners[index].x = edge.x + delta1.x - delta2.x / 2;
2606
+ corners[index].y = edge.y + delta1.y - delta2.y / 2;
2607
+ corners[i1].x = edge.x + delta1.x + delta2.x / 2;
2608
+ corners[i1].y = edge.y + delta1.y + delta2.y / 2;
2609
+ corners[i3].x = edge.x - delta2.x / 2;
2610
+ corners[i3].y = edge.y - delta2.y / 2;
2611
+ break;
2612
+ }
2613
+ } else {
2614
+ /* Not in edit vertex or edge mode */
2615
+ const area = Math.abs((pos.x - origin.x) * (pos.y - origin.y));
2616
+ ratios.forEach((ratio) => {
2617
+ const width = (area * ratio) ** 0.5;
2618
+ const adjusted = {
2619
+ x: origin.x + Math.sign(pos.x - origin.x) * width,
2620
+ y: origin.y + Math.sign(pos.y - origin.y) * width / ratio
2621
+ };
2622
+ const score = (adjusted.x - pos.x) ** 2 + (adjusted.y - pos.y) ** 2;
2623
+ if (best === undefined || score < best) {
2624
+ best = score;
2625
+ newpos = adjusted;
2626
+ }
2627
+ });
2628
+ corners[0].y = corners[1].y = origin.y;
2629
+ corners[0].x = corners[3].x = origin.x;
2630
+ corners[1].x = corners[2].x = newpos.x;
2631
+ corners[2].y = corners[3].y = newpos.y;
2632
+ }
2633
+ return {
2634
+ corners: corners,
2635
+ origin: origin,
2636
+ pos: newpos
2637
+ };
2638
+ }
2639
+
2640
+ return constraintFunction;
2641
+ }
2642
+
2380
2643
  module.exports = {
2381
2644
  state: annotationState,
2382
2645
  actionOwner: annotationActionOwner,
@@ -2385,6 +2648,10 @@ module.exports = {
2385
2648
  pointAnnotation: pointAnnotation,
2386
2649
  polygonAnnotation: polygonAnnotation,
2387
2650
  rectangleAnnotation: rectangleAnnotation,
2651
+ squareAnnotation: squareAnnotation,
2652
+ ellipseAnnotation: ellipseAnnotation,
2653
+ circleAnnotation: circleAnnotation,
2388
2654
  _editHandleFeatureLevel: editHandleFeatureLevel,
2389
- defaultEditHandleStyle: defaultEditHandleStyle
2655
+ defaultEditHandleStyle: defaultEditHandleStyle,
2656
+ constrainAspectRatio: constrainAspectRatio
2390
2657
  };
@@ -529,10 +529,12 @@ var annotationLayer = function (arg) {
529
529
  * {@link geo.listAnnotations}.
530
530
  * @param {geo.annotation} [editAnnotation] If `arg === this.modes.edit`,
531
531
  * this is the annotation that should be edited.
532
+ * @param {object} [options] Additional options to pass when creating an
533
+ * annotation.
532
534
  * @returns {string|null|this} The current mode or the layer.
533
535
  * @fires geo.event.annotation.mode
534
536
  */
535
- this.mode = function (arg, editAnnotation) {
537
+ this.mode = function (arg, editAnnotation, options) {
536
538
  if (arg === undefined) {
537
539
  return m_mode;
538
540
  }
@@ -562,32 +564,21 @@ var annotationLayer = function (arg) {
562
564
  }
563
565
  m_this.currentAnnotation = null;
564
566
  }
565
- switch (m_mode) {
566
- case m_this.modes.edit:
567
- m_this.currentAnnotation = editAnnotation;
568
- m_this.currentAnnotation.state(geo_annotation.state.edit);
569
- m_this.modified();
570
- break;
571
- case 'line':
572
- createAnnotation = geo_annotation.lineAnnotation;
573
- break;
574
- case 'point':
575
- createAnnotation = geo_annotation.pointAnnotation;
576
- break;
577
- case 'polygon':
578
- createAnnotation = geo_annotation.polygonAnnotation;
579
- break;
580
- case 'rectangle':
581
- createAnnotation = geo_annotation.rectangleAnnotation;
582
- break;
567
+ if (m_mode === m_this.modes.edit) {
568
+ m_this.currentAnnotation = editAnnotation;
569
+ m_this.currentAnnotation.state(geo_annotation.state.edit);
570
+ m_this.modified();
571
+ } else if (registry.registries.annotations[m_mode]) {
572
+ createAnnotation = registry.registries.annotations[m_mode].func;
583
573
  }
584
574
  m_this.map().interactor().removeAction(
585
575
  undefined, undefined, geo_annotation.actionOwner);
586
576
  if (createAnnotation) {
587
- m_this.currentAnnotation = createAnnotation({
577
+ options = $.extend({}, options || {}, {
588
578
  state: geo_annotation.state.create,
589
579
  layer: m_this
590
580
  });
581
+ m_this.currentAnnotation = createAnnotation(options);
591
582
  m_this.addAnnotation(m_this.currentAnnotation, null);
592
583
  actions = m_this.currentAnnotation.actions(geo_annotation.state.create);
593
584
  $.each(actions, function (idx, action) {
@@ -700,7 +691,7 @@ var annotationLayer = function (arg) {
700
691
  options.style = options.style || {};
701
692
  options.labelStyle = options.labelStyle || {};
702
693
  delete options.annotationType;
703
- // the geoJSON reader can only emit line, polygon, and point
694
+ // the geoJSON reader can emit line, polygon, point, and marker
704
695
  switch (feature.featureType) {
705
696
  case 'line':
706
697
  position = feature.line()(data, data_idx);
@@ -725,6 +716,9 @@ var annotationLayer = function (arg) {
725
716
  }
726
717
  }
727
718
  break;
719
+ case 'marker':
720
+ position = data.geometry.coordinates[0].slice(0, 4);
721
+ break;
728
722
  case 'point':
729
723
  position = [feature.position()(data, data_idx)];
730
724
  break;
@@ -984,8 +978,9 @@ var annotationLayer = function (arg) {
984
978
  $.each([
985
979
  'closed', 'fill', 'fillColor', 'fillOpacity', 'line',
986
980
  'lineCap', 'lineJoin', 'polygon', 'position', 'radius',
987
- 'stroke', 'strokeColor', 'strokeOffset', 'strokeOpacity',
988
- 'strokeWidth', 'uniformPolygon'
981
+ 'radiusIncludesStroke', 'rotateWithMap', 'rotation',
982
+ 'scaleWithZoom', 'stroke', 'strokeColor', 'strokeOffset',
983
+ 'strokeOpacity', 'strokeWidth', 'symbolValue', 'uniformPolygon'
989
984
  ], function (keyidx, key) {
990
985
  var origFunc;
991
986
  if (feature.style()[key] !== undefined) {
@@ -31,6 +31,8 @@ var geojsonReader = function (arg) {
31
31
 
32
32
  var $ = require('jquery');
33
33
  var convertColor = require('./util').convertColor;
34
+ var markerFeature = require('./markerFeature');
35
+ var transform = require('./transform');
34
36
 
35
37
  var m_this = this,
36
38
  m_options = {
@@ -309,6 +311,22 @@ var geojsonReader = function (arg) {
309
311
  */
310
312
  this.read = function (file, done, progress) {
311
313
  var promise = new Promise(function (resolve, reject) {
314
+ /**
315
+ * Check if a feature is a circle or an ellipse.
316
+ *
317
+ * @param {geojson.object} f A geojson feature.
318
+ * @returns {boolean} true if this should be rendered as an ellipse or
319
+ * circle.
320
+ */
321
+ function _isEllipse(f) {
322
+ if (f.geometry.type !== 'Polygon' || f.geometry.coordinates.length !== 1 || f.geometry.coordinates[0].length !== 5) {
323
+ return false;
324
+ }
325
+ return (
326
+ (f.properties || {}).annotationType === 'ellipse' ||
327
+ (f.properties || {}).annotationType === 'circle');
328
+ }
329
+
312
330
  /**
313
331
  * Given a parsed GeoJSON object, convert it into features on the
314
332
  * reader's layer.
@@ -384,7 +402,7 @@ var geojsonReader = function (arg) {
384
402
  }
385
403
 
386
404
  // process polygons
387
- const polygons = features.filter(f => f.geometry.type === 'Polygon');
405
+ const polygons = features.filter(f => f.geometry.type === 'Polygon' && !_isEllipse(f));
388
406
  if (polygons.length) {
389
407
  feature = m_this.layer().createFeature('polygon');
390
408
  if (feature) {
@@ -410,6 +428,55 @@ var geojsonReader = function (arg) {
410
428
  allFeatures.push(feature);
411
429
  }
412
430
  }
431
+ // handle ellipses and circle
432
+ const ellipses = features.filter(_isEllipse);
433
+ if (ellipses.length) {
434
+ feature = m_this.layer().createFeature('marker');
435
+ if (feature) {
436
+ ellipses.forEach((d) => {
437
+ const map = m_this.layer().map();
438
+ const coord = transform.transformCoordinates(map.ingcs(), map.gcs(), d.geometry.coordinates[0]);
439
+ const w = ((coord[0][0] - coord[1][0]) ** 2 + (coord[0][1] - coord[1][1]) ** 2) ** 0.5;
440
+ const h = ((coord[0][0] - coord[3][0]) ** 2 + (coord[0][1] - coord[3][1]) ** 2) ** 0.5;
441
+ const radius = Math.max(w, h) / 2 / map.unitsPerPixel(0);
442
+ const aspect = w ? h / w : 1e20;
443
+ const rotation = -Math.atan2(coord[1][1] - coord[0][1], coord[1][0] - coord[0][0]);
444
+ const pos = transform.transformCoordinates(map.gcs(), map.ingcs(), {
445
+ x: (coord[0][0] + coord[1][0] + coord[2][0] + coord[3][0]) / 4,
446
+ y: (coord[0][1] + coord[1][1] + coord[2][1] + coord[3][1]) / 4
447
+ });
448
+ d._props = {
449
+ pos: pos,
450
+ radius: radius,
451
+ aspect: aspect,
452
+ rotation: rotation
453
+ };
454
+ });
455
+ feature
456
+ .data(ellipses)
457
+ .position((d) => d._props.pos)
458
+ // create an object with each property in m_options.polygonStyle,
459
+ // mapping the values through the _style function.
460
+ .style(
461
+ [{}].concat(Object.keys(m_options.polygonStyle)).reduce(
462
+ (styleObj, key) => ({
463
+ [key]: ellipses.some(d => d.properties && d.properties[key] !== undefined) ?
464
+ m_this._style(key, m_options.polygonStyle[key]) :
465
+ m_options.polygonStyle[key],
466
+ radius: (d) => d._props.radius,
467
+ radiusIncludesStroke: false,
468
+ symbolValue: (d) => d._props.aspect,
469
+ rotation: (d) => d._props.rotation,
470
+ strokeOffset: 0,
471
+ rotateWithMap: true,
472
+ scaleWithZoom: markerFeature.scaleMode.fill,
473
+ ...styleObj
474
+ }
475
+ ))
476
+ );
477
+ allFeatures.push(feature);
478
+ }
479
+ }
413
480
  if (done) {
414
481
  done(allFeatures);
415
482
  }
package/src/map.js CHANGED
@@ -569,8 +569,7 @@ var map = function (arg) {
569
569
  };
570
570
 
571
571
  /**
572
- * Set center of the map to the given geographic coordinates, or get the
573
- * current center. Uses bare objects {x: 0, y: 0}.
572
+ * Get or set the center of the map in the given geographic coordinates.
574
573
  *
575
574
  * @param {geo.geoPosition} coordinates If specified, the new center of the
576
575
  * map.