geojs 1.8.10 → 1.9.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.
@@ -181,6 +181,44 @@ var annotationLayer = function (arg) {
181
181
  m_this._updateFromEvent(update);
182
182
  };
183
183
 
184
+ /**
185
+ * Check if there is a current boolean operation.
186
+ *
187
+ * @returns {string?} Either undefined for no current boolean operation or
188
+ * the name of the operation.
189
+ */
190
+ this.currentBooleanOperation = function () {
191
+ let op;
192
+ if (m_this._currentBooleanClass) {
193
+ op = m_this._currentBooleanClass.split('-')[1];
194
+ }
195
+ return op;
196
+ };
197
+
198
+ /**
199
+ * Check if the map is currently in a mode where we are adding an annotation
200
+ * with a boolean operation. If so, remove the current annotation from the
201
+ * layer, then apply it via the boolean operation.
202
+ */
203
+ this._handleBooleanOperation = function () {
204
+ const op = m_this.currentBooleanOperation();
205
+ if (!op || !m_this.currentAnnotation || !m_this.currentAnnotation.toPolygonList) {
206
+ return;
207
+ }
208
+ const newAnnot = m_this.currentAnnotation;
209
+ m_this.removeAnnotation(m_this.currentAnnotation, false);
210
+ if (m_this.annotations().length || (op !== 'difference' && op !== 'intersect')) {
211
+ const evt = {
212
+ annotation: newAnnot,
213
+ operation: op
214
+ };
215
+ m_this.geoTrigger(geo_event.annotation.boolean, evt);
216
+ if (evt.cancel !== false) {
217
+ util.polyops[op](m_this, newAnnot.toPolygonList(), {correspond: {}, keepAnnotations: 'exact', style: m_this});
218
+ }
219
+ }
220
+ };
221
+
184
222
  /**
185
223
  * Handle updating the current annotation based on an update state.
186
224
  *
@@ -196,6 +234,7 @@ var annotationLayer = function (arg) {
196
234
  m_this.mode(null);
197
235
  break;
198
236
  case 'done':
237
+ m_this._handleBooleanOperation();
199
238
  m_this.mode(null);
200
239
  break;
201
240
  }
@@ -205,6 +244,27 @@ var annotationLayer = function (arg) {
205
244
  }
206
245
  };
207
246
 
247
+ /**
248
+ * Check the state of the modifier keys and apply them if appropriate.
249
+ *
250
+ * @param {geo.event} evt The mouse move or click event.
251
+ */
252
+ this._handleMouseMoveModifiers = function (evt) {
253
+ if (m_this.mode() !== m_this.modes.edit && m_this.currentAnnotation.options('allowBooleanOperations') && m_this.currentAnnotation._coordinates().length < 2) {
254
+ if (evt.modifiers) {
255
+ const mod = (evt.modifiers.shift ? 's' : '') + (evt.modifiers.ctrl ? 'c' : '') + (evt.modifiers.meta || evt.modifiers.alt ? 'a' : '');
256
+ if (m_this._currentBooleanClass === m_this._booleanClasses[mod]) {
257
+ return;
258
+ }
259
+ m_this._currentBooleanClass = m_this._booleanClasses[mod];
260
+ const mapNode = m_this.map().node();
261
+ Object.values(m_this._booleanClasses).forEach((c) => {
262
+ mapNode.toggleClass(c, m_this._booleanClasses[mod] === c);
263
+ });
264
+ }
265
+ }
266
+ };
267
+
208
268
  /**
209
269
  * Handle mouse movement. If there is a current annotation, the movement
210
270
  * event is sent to it.
@@ -213,6 +273,7 @@ var annotationLayer = function (arg) {
213
273
  */
214
274
  this._handleMouseMove = function (evt) {
215
275
  if (m_this.mode() && m_this.currentAnnotation) {
276
+ m_this._handleMouseMoveModifiers(evt);
216
277
  var update = m_this.currentAnnotation.mouseMove(evt);
217
278
  if (update) {
218
279
  m_this.modified();
@@ -316,6 +377,7 @@ var annotationLayer = function (arg) {
316
377
  });
317
378
  retrigger = true;
318
379
  } else if (m_this.mode() && m_this.currentAnnotation) {
380
+ m_this._handleMouseMoveModifiers(evt);
319
381
  update = m_this.currentAnnotation.mouseClick(evt);
320
382
  m_this._updateFromEvent(update);
321
383
  retrigger = !m_this.mode();
@@ -518,6 +580,15 @@ var annotationLayer = function (arg) {
518
580
  edit: 'edit'
519
581
  };
520
582
 
583
+ /* Keys are short-hand for preferred event modifiers. Values are classes to
584
+ * apply to the map node. */
585
+ this._booleanClasses = {
586
+ s: 'annotation-union',
587
+ sc: 'annotation-intersect',
588
+ c: 'annotation-difference',
589
+ sa: 'annotation-xor'
590
+ };
591
+
521
592
  /**
522
593
  * Get or set the current mode.
523
594
  *
@@ -537,11 +608,15 @@ var annotationLayer = function (arg) {
537
608
  if (arg === undefined) {
538
609
  return m_mode;
539
610
  }
540
- if (arg !== m_mode || (arg === m_this.modes.edit && editAnnotation !== m_this.editAnnotation)) {
611
+ if (arg !== m_mode || (arg === m_this.modes.edit && editAnnotation !== m_this.editAnnotation) || (arg !== m_this.modes.edit && arg && !m_this.currentAnnotation)) {
541
612
  var createAnnotation, actions,
542
613
  mapNode = m_this.map().node(), oldMode = m_mode;
543
614
  m_mode = arg;
544
615
  mapNode.toggleClass('annotation-input', !!(m_mode && m_mode !== m_this.modes.edit));
616
+ if (!m_mode || m_mode === m_this.modes.edit) {
617
+ Object.values(m_this._booleanClasses).forEach((c) => mapNode.toggleClass(c, false));
618
+ m_this._currentBooleanClass = undefined;
619
+ }
545
620
  if (!m_keyHandler) {
546
621
  m_keyHandler = Mousetrap(mapNode[0]);
547
622
  }
@@ -618,7 +693,12 @@ var annotationLayer = function (arg) {
618
693
  */
619
694
  this.geojson = function (geojson, clear, gcs, includeCrs) {
620
695
  if (geojson !== undefined) {
621
- var reader = registry.createFileReader('geojsonReader', {layer: m_this});
696
+ var reader = registry.createFileReader('geojsonReader', {
697
+ layer: m_this,
698
+ lineStyle: require('./annotation/lineAnnotation').defaults.style,
699
+ pointStyle: require('./annotation/pointAnnotation').defaults.style,
700
+ polygonStyle: require('./annotation/polygonAnnotation').defaults.style
701
+ });
622
702
  if (!reader.canRead(geojson)) {
623
703
  return;
624
704
  }
@@ -1156,6 +1236,112 @@ var annotationLayer = function (arg) {
1156
1236
  return m_this;
1157
1237
  };
1158
1238
 
1239
+ /**
1240
+ * Return any annotation that has area as a polygon list: an array of
1241
+ * polygons, each of which is an array of polylines, each of which is an
1242
+ * array of points, each of which is a 2-tuple of numbers.
1243
+ *
1244
+ * @param {geo.util.polyop.spec} [opts] Ignored.
1245
+ * @returns {geo.polygonList} A list of polygons.
1246
+ */
1247
+ this.toPolygonList = function (opts) {
1248
+ const poly = [];
1249
+ opts = opts || {};
1250
+ opts.annotationIndices = [];
1251
+ m_annotations.forEach((annotation, idx) => {
1252
+ if (annotation.toPolygonList) {
1253
+ annotation.toPolygonList(opts).forEach((p) => poly.push(p));
1254
+ opts.annotationIndices.push(idx);
1255
+ }
1256
+ });
1257
+ return poly;
1258
+ };
1259
+
1260
+ /**
1261
+ * Replace appropriate annotations with a list of polygons.
1262
+ *
1263
+ * @param {geo.polygonList} poly A list of polygons.
1264
+ * @param {geo.util.polyop.spec} [opts] This contains annotationIndices and
1265
+ * correspondence used to track annotations.
1266
+ * @returns {this}
1267
+ */
1268
+ this.fromPolygonList = function (poly, opts) {
1269
+ if (!poly || !poly.length) {
1270
+ return m_this;
1271
+ }
1272
+ /* One of 'all', 'exact', 'none'; default is 'exact' */
1273
+ const keep = opts.keepAnnotations;
1274
+ const keepIds = {};
1275
+ const reusedIds = {};
1276
+ let correspond, exact, annot;
1277
+ if (opts.annotationIndices && opts.correspond && opts.correspond.poly1 && opts.annotationIndices.length === opts.correspond.poly1.length) {
1278
+ correspond = opts.correspond.poly1;
1279
+ exact = opts.correspond.exact1;
1280
+ annot = m_this.annotations();
1281
+ }
1282
+ if (keep !== 'all' && keep !== 'none') {
1283
+ annot.forEach((oldAnnot, idx) => {
1284
+ if (opts.annotationIndices.indexOf(idx) < 0) {
1285
+ keepIds[oldAnnot.id()] = true;
1286
+ }
1287
+ });
1288
+ }
1289
+ const polyAnnot = [];
1290
+ poly.forEach((p, idx) => {
1291
+ p = p.map((h) => h.map((pt) => ({x: pt[0], y: pt[1]})));
1292
+ const result = {
1293
+ vertices: p.length === 1 ? p[0] : {outer: p[0], inner: p.slice(1)}
1294
+ };
1295
+ if (correspond) {
1296
+ for (let i = 0; i < correspond.length; i += 1) {
1297
+ if (correspond[i] && correspond[i].indexOf(idx) >= 0) {
1298
+ const orig = annot[opts.annotationIndices[i]];
1299
+ if (keep !== 'all' && keep !== 'none' && exact && exact[i] && exact[i].indexOf(idx) >= 0) {
1300
+ keepIds[orig.id()] = true;
1301
+ return;
1302
+ }
1303
+ if (keep !== 'all' && reusedIds[orig.id()] === undefined) {
1304
+ result.annotationId = orig.id();
1305
+ reusedIds[orig.id()] = true;
1306
+ }
1307
+ ['name', 'description', 'label'].forEach((k) => {
1308
+ if (orig[k](undefined, true)) {
1309
+ result[k] = orig[k](undefined, true);
1310
+ }
1311
+ });
1312
+ Object.entries(orig.options()).forEach(([key, value]) => {
1313
+ if (['showLabel', 'style'].indexOf(key) >= 0 || key.endsWith('Style')) {
1314
+ result[key] = value;
1315
+ }
1316
+ });
1317
+ break;
1318
+ }
1319
+ }
1320
+ }
1321
+ polyAnnot.push(result);
1322
+ });
1323
+ let update = false;
1324
+ // delete extant annotations
1325
+ if (keep !== 'all' && annot) {
1326
+ annot
1327
+ .filter((oldAnnot) => keepIds[oldAnnot.id()] === undefined)
1328
+ .forEach((oldAnnot) => {
1329
+ m_this.removeAnnotation(oldAnnot, false);
1330
+ update = true;
1331
+ });
1332
+ }
1333
+ // add new annotations
1334
+ polyAnnot.forEach((p) => {
1335
+ m_this.addAnnotation(registry.createAnnotation('polygon', p), m_this.gcs(), false);
1336
+ update = true;
1337
+ });
1338
+ if (update) {
1339
+ m_this.modified();
1340
+ m_this.draw();
1341
+ }
1342
+ return m_this;
1343
+ };
1344
+
1159
1345
  /**
1160
1346
  * Initialize.
1161
1347
  *
@@ -0,0 +1,4 @@
1
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
2
+ <path d="M0,11l11,0l0,-11l2,0l0,11l11,0l0,2l-11,0l0,11l-2,0l0,-11l-11,0z" fill="black" stroke-width="0"/>
3
+ <path d="M24,27l8,0l0,2l-8,0z" fill="black" stroke-width="0"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
2
+ <path d="M0,11l11,0l0,-11l2,0l0,11l11,0l0,2l-11,0l0,11l-2,0l0,-11l-11,0z" fill="black" stroke-width="0"/>
3
+ <path d="M24,32l0,-4a4,4,0,0,1,8,0l0,4l-2,0l0,-4a2,2,0,0,0,-4,0l0,4z" fill="black" stroke-width="0"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
2
+ <path d="M0,11l11,0l0,-11l2,0l0,11l11,0l0,2l-11,0l0,11l-2,0l0,-11l-11,0z" fill="black" stroke-width="0"/>
3
+ <path d="M24,27l3,0l0,-3l2,0l0,3l3,0l0,2l-3,0l0,3l-2,0l0,-3l-3,0z" fill="black" stroke-width="0"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32">
2
+ <path d="M0,11l11,0l0,-11l2,0l0,11l11,0l0,2l-11,0l0,11l-2,0l0,-11l-11,0z" fill="black" stroke-width="0"/>
3
+ <path d="M32,30.59l-1.41,1.41l-2.12,-2.12l-2.12,2.12l-1.41,-1.41l2.12,-2.12l-2.12,-2.12l1.41,-1.41l2.12,2.12l2.12,-2.12l1.41,1.41l-2.12,2.12z" fill="black" stroke-width="0"/>
4
+ </svg>
package/src/event.js CHANGED
@@ -692,4 +692,18 @@ geo_event.annotation.state = 'geo_annotation_state';
692
692
  */
693
693
  geo_event.annotation.mode = 'geo_annotation_mode';
694
694
 
695
+ /**
696
+ * Triggered when an annotation can be combined via a boolean operation (union,
697
+ * intersect, difference, xor).
698
+ *
699
+ * @event geo.event.annotation.boolean
700
+ * @type {geo.event.base}
701
+ * @property {geo.annotation} annotation The annotation that is being operated
702
+ * on.
703
+ * @property {string} operation The operation being performed.
704
+ * @property {boolean} [cancel] If the handle sets this to false, don't apply
705
+ * the operation to the annotation layer.
706
+ */
707
+ geo_event.annotation.boolean = 'geo_annotation_boolean';
708
+
695
709
  module.exports = geo_event;
@@ -182,7 +182,7 @@ var lineFeature = function (arg) {
182
182
  const current = lineRecord[j];
183
183
  let p = posFunc(current, j, d, index);
184
184
  if (onlyInvertedY) {
185
- p.y = -p.y;
185
+ p = {x: p.x, y: -p.y};
186
186
  } else if (gcs !== mapgcs) {
187
187
  p = transform.transformCoordinates(gcs, mapgcs, p);
188
188
  }
package/src/main.styl CHANGED
@@ -49,6 +49,14 @@
49
49
 
50
50
  &.annotation-input
51
51
  cursor crosshair
52
+ &.annotation-intersect
53
+ cursor embedurl("./css/cursor-crosshair-intersect.svg") 12 12,crosshair
54
+ &.annotation-difference
55
+ cursor embedurl("./css/cursor-crosshair-difference.svg") 12 12,crosshair
56
+ &.annotation-union
57
+ cursor embedurl("./css/cursor-crosshair-union.svg") 12 12,crosshair
58
+ &.annotation-xor
59
+ cursor embedurl("./css/cursor-crosshair-xor.svg") 12 12,crosshair
52
60
 
53
61
  &.highlight-focus
54
62
  &:after
@@ -686,6 +686,56 @@ var polygonFeature = function (arg) {
686
686
  });
687
687
  };
688
688
 
689
+ /**
690
+ * Return the polygons as a polygon list: an array of polygons, each of which
691
+ * is an array of polylines, each of which is an array of points, each of
692
+ * which is a 2-tuple of numbers.
693
+ *
694
+ * @param {geo.util.polyop.spec} [opts] Ignored.
695
+ * @returns {geo.polygonList} A list of polygons.
696
+ */
697
+ this.toPolygonList = function (opts) {
698
+ const polyFunc = m_this.style.get('polygon');
699
+ const posFunc = m_this.style.get('position');
700
+ return m_this.data().map((d, i) => {
701
+ const polygon = polyFunc(d, i);
702
+ const outer = polygon.outer || (Array.isArray(polygon) ? polygon : []);
703
+ if (outer.length < 3) {
704
+ return [];
705
+ }
706
+ const resp = [outer.map((p, j) => {
707
+ const pos = posFunc(p, j, d, i);
708
+ return [pos.x, pos.y];
709
+ })];
710
+ if (polygon.inner) {
711
+ polygon.inner.forEach((h) => {
712
+ resp.push(h.map((p, j) => {
713
+ const pos = posFunc(p, j, d, i);
714
+ return [pos.x, pos.y];
715
+ }));
716
+ });
717
+ }
718
+ return resp;
719
+ });
720
+ };
721
+
722
+ /**
723
+ * Set the data, position accessor, and polygon accessor to use a list of
724
+ * polygons.
725
+ *
726
+ * @param {geo.polygonList} poly A list of polygons.
727
+ * @param {geo.util.polyop.spec} [opts] Ignored.
728
+ * @returns {this}
729
+ */
730
+ this.fromPolygonList = function (poly, opts) {
731
+ m_this.style({
732
+ position: (p) => ({x: p[0], y: p[1]}),
733
+ polygon: (p) => ({outer: p[0], inner: p.slice(1)})
734
+ });
735
+ m_this.data(poly);
736
+ return m_this;
737
+ };
738
+
689
739
  /**
690
740
  * Destroy.
691
741
  */
package/src/typedef.js CHANGED
@@ -54,6 +54,17 @@
54
54
  * @property {number} [z=0] Altitude coordinate.
55
55
  */
56
56
 
57
+ /**
58
+ * General represention of a point on the earth. The coordinates are most
59
+ * commonly in longitude and latitude, but the coordinate system is changed
60
+ * by the interface gcs. This is a list of numbers consisting of either [x, y]
61
+ * or [x, y, z], where therse are the horizontal, vertical, and altitude
62
+ * coordinates.
63
+ *
64
+ * @typedef geo.geoPositionFlat
65
+ * @type {number[]}
66
+ */
67
+
57
68
  /**
58
69
  * General represention of a two-dimensional point in any coordinate system.
59
70
  *
package/src/util/index.js CHANGED
@@ -8,6 +8,7 @@ module.exports = Object.assign(
8
8
  {
9
9
  DistanceGrid: require('./distanceGrid'),
10
10
  ClusterGroup: require('./clustering'),
11
- mesh: require('./mesh')
11
+ mesh: require('./mesh'),
12
+ polyops: require('./polyops')
12
13
  }
13
14
  );