diff --git a/CHANGELOG.md b/CHANGELOG.md
index cdaeab9257..cd29256d4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
### Features
- Polygon operations ([#1198](../../pull/1198))
+- Use meta keys to modify annotations ([#1209](../../pull/1209))
## Version 1.8.10
diff --git a/src/annotation/annotation.js b/src/annotation/annotation.js
index cba55d88d0..8ec7c763d3 100644
--- a/src/annotation/annotation.js
+++ b/src/annotation/annotation.js
@@ -74,6 +74,12 @@ var editHandleFeatureLevel = 3;
* @property {boolean|string[]} [showLabel=true] `true` to show the annotation
* label on annotations in done or edit states. Alternately, a list of
* states in which to show the label. Falsy to not show the label.
+ * @property {boolean} [allowBooleanOperations] This defaults to `true` for
+ * annotations that have area and `false` for those without area (e.g.,
+ * false for lines and points). If it is truthy, then, when the annotation
+ * is being created, it checks the metakeys on the first click that defines
+ * a coordinate to determine what boolean polygon operation should be
+ * performaned on the completion of the annotation.
*/
/**
@@ -94,7 +100,7 @@ var annotation = function (type, args) {
}
var m_this = this,
- m_options = $.extend({}, {showLabel: true}, args || {}),
+ m_options = $.extend(true, {}, this.constructor.defaults, args || {}),
m_id = m_options.annotationId;
delete m_options.annotationId;
if (m_id === undefined || (m_options.layer && m_options.layer.annotationById(m_id))) {
@@ -797,10 +803,14 @@ var annotation = function (type, args) {
key = styles[i];
value = util.ensureFunction(objStyle[key])();
if (value !== undefined) {
+ let defvalue = ((m_this.constructor.defaults || {}).style || {})[key];
if (key.toLowerCase().match(/color$/)) {
value = util.convertColorToHex(value, 'needed');
+ defvalue = defvalue !== undefined ? util.convertColorToHex(defvalue, 'needed') : defvalue;
+ }
+ if (value !== defvalue) {
+ obj.properties[key] = value;
}
- obj.properties[key] = value;
}
}
for (i = 0; i < textFeature.usedStyles.length; i += 1) {
@@ -1408,6 +1418,13 @@ function constrainAspectRatio(ratio) {
return constraintFunction;
}
+/**
+ * This object contains the default options to initialize the class.
+ */
+annotation.defaults = {
+ showLabel: true
+};
+
module.exports = {
state: annotationState,
actionOwner: annotationActionOwner,
diff --git a/src/annotation/circleAnnotation.js b/src/annotation/circleAnnotation.js
index 8f3f0f5603..ad7956623c 100644
--- a/src/annotation/circleAnnotation.js
+++ b/src/annotation/circleAnnotation.js
@@ -19,13 +19,20 @@ const ellipseAnnotation = require('./ellipseAnnotation');
*/
var circleAnnotation = function (args, annotationName) {
'use strict';
- args = $.extend({}, args, {constraint: 1});
if (!(this instanceof circleAnnotation)) {
return new circleAnnotation(args, annotationName);
}
+ args = $.extend(true, {}, this.constructor.defaults, args, {constraint: 1});
ellipseAnnotation.call(this, args, annotationName || 'circle');
};
inherit(circleAnnotation, ellipseAnnotation);
+
+/**
+ * This object contains the default options to initialize the class.
+ */
+circleAnnotation.defaults = $.extend({}, ellipseAnnotation.defaults, {
+});
+
var circleRequiredFeatures = {};
circleRequiredFeatures[markerFeature.capabilities.feature] = true;
registerAnnotation('circle', circleAnnotation, circleRequiredFeatures);
diff --git a/src/annotation/ellipseAnnotation.js b/src/annotation/ellipseAnnotation.js
index e3efa8c2ba..425097911b 100644
--- a/src/annotation/ellipseAnnotation.js
+++ b/src/annotation/ellipseAnnotation.js
@@ -23,7 +23,7 @@ var ellipseAnnotation = function (args, annotationName) {
if (!(this instanceof ellipseAnnotation)) {
return new ellipseAnnotation(args, annotationName);
}
-
+ args = $.extend(true, {}, this.constructor.defaults, args);
rectangleAnnotation.call(this, args, annotationName || 'ellipse');
var m_this = this;
@@ -117,6 +117,12 @@ var ellipseAnnotation = function (args, annotationName) {
};
inherit(ellipseAnnotation, rectangleAnnotation);
+/**
+ * This object contains the default options to initialize the class.
+ */
+ellipseAnnotation.defaults = $.extend({}, rectangleAnnotation.defaults, {
+});
+
var ellipseRequiredFeatures = {};
ellipseRequiredFeatures[markerFeature.capabilities.feature] = true;
registerAnnotation('ellipse', ellipseAnnotation, ellipseRequiredFeatures);
diff --git a/src/annotation/lineAnnotation.js b/src/annotation/lineAnnotation.js
index 665be33e43..d0a6eeec09 100644
--- a/src/annotation/lineAnnotation.js
+++ b/src/annotation/lineAnnotation.js
@@ -37,8 +37,7 @@ var lineAnnotation = function (args) {
if (!(this instanceof lineAnnotation)) {
return new lineAnnotation(args);
}
-
- args = $.extend(true, {}, {
+ args = $.extend(true, {}, this.constructor.defaults, {
style: {
line: function (d) {
/* Return an array that has the same number of items as we have
@@ -48,16 +47,7 @@ var lineAnnotation = function (args) {
},
position: function (d, i) {
return m_this.options('vertices')[i];
- },
- strokeColor: {r: 0, g: 0, b: 0},
- strokeOpacity: 1,
- strokeWidth: 3,
- closed: false,
- lineCap: 'butt',
- lineJoin: 'miter'
- },
- highlightStyle: {
- strokeWidth: 5
+ }
},
createStyle: {
line: function (d) {
@@ -68,15 +58,9 @@ var lineAnnotation = function (args) {
},
position: function (d, i) {
return m_this.options('vertices')[i];
- },
- strokeColor: {r: 0, g: 0, b: 1},
- strokeOpacity: 1,
- strokeWidth: 3,
- closed: false,
- lineCap: 'butt',
- lineJoin: 'miter'
+ }
}
- }, args || {});
+ }, args);
args.vertices = args.vertices || args.coordinates || [];
delete args.coordinates;
annotation.call(this, 'line', args);
@@ -337,6 +321,31 @@ var lineAnnotation = function (args) {
};
inherit(lineAnnotation, annotation);
+/**
+ * This object contains the default options to initialize the class.
+ */
+lineAnnotation.defaults = $.extend({}, annotation.defaults, {
+ style: {
+ strokeColor: {r: 0, g: 0, b: 0},
+ strokeOpacity: 1,
+ strokeWidth: 3,
+ closed: false,
+ lineCap: 'butt',
+ lineJoin: 'miter'
+ },
+ highlightStyle: {
+ strokeWidth: 5
+ },
+ createStyle: {
+ strokeColor: {r: 0, g: 0, b: 1},
+ strokeOpacity: 1,
+ strokeWidth: 3,
+ closed: false,
+ lineCap: 'butt',
+ lineJoin: 'miter'
+ }
+});
+
var lineRequiredFeatures = {};
lineRequiredFeatures[lineFeature.capabilities.basic] = [annotationState.create];
registerAnnotation('line', lineAnnotation, lineRequiredFeatures);
diff --git a/src/annotation/pointAnnotation.js b/src/annotation/pointAnnotation.js
index 2dadfe3279..f51d93d20c 100644
--- a/src/annotation/pointAnnotation.js
+++ b/src/annotation/pointAnnotation.js
@@ -40,29 +40,7 @@ var pointAnnotation = function (args) {
return new pointAnnotation(args);
}
- args = $.extend(true, {}, {
- style: {
- fill: true,
- fillColor: {r: 0, g: 1, b: 0},
- fillOpacity: 0.25,
- radius: 10,
- scaled: false,
- stroke: true,
- strokeColor: {r: 0, g: 0, b: 0},
- strokeOpacity: 1,
- strokeWidth: 3
- },
- createStyle: {
- fillColor: {r: 0.3, g: 0.3, b: 0.3},
- fillOpacity: 0.25,
- strokeColor: {r: 0, g: 0, b: 1}
- },
- highlightStyle: {
- fillColor: {r: 0, g: 1, b: 1},
- fillOpacity: 0.5,
- strokeWidth: 5
- }
- }, args || {});
+ args = $.extend(true, {}, this.constructor.defaults, args);
args.position = args.position || (args.coordinates ? args.coordinates[0] : undefined);
delete args.coordinates;
annotation.call(this, 'point', args);
@@ -201,6 +179,33 @@ var pointAnnotation = function (args) {
};
inherit(pointAnnotation, annotation);
+/**
+ * This object contains the default options to initialize the class.
+ */
+pointAnnotation.defaults = $.extend({}, annotation.defaults, {
+ style: {
+ fill: true,
+ fillColor: {r: 0, g: 1, b: 0},
+ fillOpacity: 0.25,
+ radius: 10,
+ scaled: false,
+ stroke: true,
+ strokeColor: {r: 0, g: 0, b: 0},
+ strokeOpacity: 1,
+ strokeWidth: 3
+ },
+ createStyle: {
+ fillColor: {r: 0.3, g: 0.3, b: 0.3},
+ fillOpacity: 0.25,
+ strokeColor: {r: 0, g: 0, b: 1}
+ },
+ highlightStyle: {
+ fillColor: {r: 0, g: 1, b: 1},
+ fillOpacity: 0.5,
+ strokeWidth: 5
+ }
+});
+
var pointRequiredFeatures = {};
pointRequiredFeatures[pointFeature.capabilities.feature] = true;
registerAnnotation('point', pointAnnotation, pointRequiredFeatures);
diff --git a/src/annotation/polygonAnnotation.js b/src/annotation/polygonAnnotation.js
index b2485e323c..dde0234a43 100644
--- a/src/annotation/polygonAnnotation.js
+++ b/src/annotation/polygonAnnotation.js
@@ -43,27 +43,11 @@ var polygonAnnotation = function (args) {
return new polygonAnnotation(args);
}
- args = $.extend(true, {}, {
+ args = $.extend(true, {}, this.constructor.defaults, {
style: {
- fill: true,
- fillColor: {r: 0, g: 1, b: 0},
- fillOpacity: 0.25,
- polygon: function (d) { return d.polygon; },
- stroke: true,
- strokeColor: {r: 0, g: 0, b: 0},
- strokeOpacity: 1,
- strokeWidth: 3,
- uniformPolygon: true
- },
- highlightStyle: {
- fillColor: {r: 0, g: 1, b: 1},
- fillOpacity: 0.5,
- strokeWidth: 5
+ polygon: function (d) { return d.polygon; }
},
createStyle: {
- closed: false,
- fillColor: {r: 0.3, g: 0.3, b: 0.3},
- fillOpacity: 0.25,
line: function (d) {
const coord = m_this._coordinates();
/* Return an array that has the same number of items as we have
@@ -76,11 +60,9 @@ var polygonAnnotation = function (args) {
return d.x;
}
return m_this.options('vertices')[i];
- },
- stroke: false,
- strokeColor: {r: 0, g: 0, b: 1}
+ }
}
- }, args || {});
+ }, args);
args.vertices = args.vertices || args.coordinates || [];
delete args.coordinates;
annotation.call(this, 'polygon', args);
@@ -324,6 +306,35 @@ var polygonAnnotation = function (args) {
};
inherit(polygonAnnotation, annotation);
+/**
+ * This object contains the default options to initialize the class.
+ */
+polygonAnnotation.defaults = $.extend({}, annotation.defaults, {
+ style: {
+ fill: true,
+ fillColor: {r: 0, g: 1, b: 0},
+ fillOpacity: 0.25,
+ stroke: true,
+ strokeColor: {r: 0, g: 0, b: 0},
+ strokeOpacity: 1,
+ strokeWidth: 3,
+ uniformPolygon: true
+ },
+ highlightStyle: {
+ fillColor: {r: 0, g: 1, b: 1},
+ fillOpacity: 0.5,
+ strokeWidth: 5
+ },
+ createStyle: {
+ closed: false,
+ fillColor: {r: 0.3, g: 0.3, b: 0.3},
+ fillOpacity: 0.25,
+ stroke: false,
+ strokeColor: {r: 0, g: 0, b: 1}
+ },
+ allowBooleanOperations: true
+});
+
var polygonRequiredFeatures = {};
polygonRequiredFeatures[polygonFeature.capabilities.feature] = true;
polygonRequiredFeatures[lineFeature.capabilities.basic] = [annotationState.create];
diff --git a/src/annotation/rectangleAnnotation.js b/src/annotation/rectangleAnnotation.js
index 024bcfa3ff..a6f89238db 100644
--- a/src/annotation/rectangleAnnotation.js
+++ b/src/annotation/rectangleAnnotation.js
@@ -48,29 +48,7 @@ var rectangleAnnotation = function (args, annotationName) {
return new rectangleAnnotation(args, annotationName);
}
- args = $.extend(true, {}, {
- style: {
- fill: true,
- fillColor: {r: 0, g: 1, b: 0},
- fillOpacity: 0.25,
- polygon: function (d) { return d.polygon; },
- stroke: true,
- strokeColor: {r: 0, g: 0, b: 0},
- strokeOpacity: 1,
- strokeWidth: 3,
- uniformPolygon: true
- },
- highlightStyle: {
- fillColor: {r: 0, g: 1, b: 1},
- fillOpacity: 0.5,
- strokeWidth: 5
- },
- createStyle: {
- fillColor: {r: 0.3, g: 0.3, b: 0.3},
- fillOpacity: 0.25,
- strokeColor: {r: 0, g: 0, b: 1}
- }
- }, args || {});
+ args = $.extend(true, {}, this.constructor.defaults, args);
args.corners = args.corners || args.coordinates || [];
delete args.coordinates;
annotation.call(this, annotationName || 'rectangle', args);
@@ -408,6 +386,34 @@ var rectangleAnnotation = function (args, annotationName) {
};
inherit(rectangleAnnotation, annotation);
+/**
+ * This object contains the default options to initialize the class.
+ */
+rectangleAnnotation.defaults = $.extend({}, annotation.defaults, {
+ style: {
+ fill: true,
+ fillColor: {r: 0, g: 1, b: 0},
+ fillOpacity: 0.25,
+ polygon: function (d) { return d.polygon; },
+ stroke: true,
+ strokeColor: {r: 0, g: 0, b: 0},
+ strokeOpacity: 1,
+ strokeWidth: 3,
+ uniformPolygon: true
+ },
+ highlightStyle: {
+ fillColor: {r: 0, g: 1, b: 1},
+ fillOpacity: 0.5,
+ strokeWidth: 5
+ },
+ createStyle: {
+ fillColor: {r: 0.3, g: 0.3, b: 0.3},
+ fillOpacity: 0.25,
+ strokeColor: {r: 0, g: 0, b: 1}
+ },
+ allowBooleanOperations: true
+});
+
var rectangleRequiredFeatures = {};
rectangleRequiredFeatures[polygonFeature.capabilities.feature] = true;
registerAnnotation('rectangle', rectangleAnnotation, rectangleRequiredFeatures);
diff --git a/src/annotation/squareAnnotation.js b/src/annotation/squareAnnotation.js
index 820c2071e8..cddb7cf409 100644
--- a/src/annotation/squareAnnotation.js
+++ b/src/annotation/squareAnnotation.js
@@ -19,13 +19,20 @@ const rectangleAnnotation = require('./rectangleAnnotation');
*/
var squareAnnotation = function (args, annotationName) {
'use strict';
- args = $.extend({}, args, {constraint: 1});
if (!(this instanceof squareAnnotation)) {
return new squareAnnotation(args, annotationName);
}
+ args = $.extend(true, {}, this.constructor.defaults, args, {constraint: 1});
rectangleAnnotation.call(this, args, annotationName || 'square');
};
inherit(squareAnnotation, rectangleAnnotation);
+
+/**
+ * This object contains the default options to initialize the class.
+ */
+squareAnnotation.defaults = $.extend({}, rectangleAnnotation.defaults, {
+});
+
var squareRequiredFeatures = {};
squareRequiredFeatures[polygonFeature.capabilities.feature] = true;
registerAnnotation('square', squareAnnotation, squareRequiredFeatures);
diff --git a/src/annotationLayer.js b/src/annotationLayer.js
index 533c6f0b38..03d993c1a3 100644
--- a/src/annotationLayer.js
+++ b/src/annotationLayer.js
@@ -181,6 +181,44 @@ var annotationLayer = function (arg) {
m_this._updateFromEvent(update);
};
+ /**
+ * Check if there is a current boolean operation.
+ *
+ * @returns {string?} Either undefined for no current boolean operation or
+ * the name of the operation.
+ */
+ this.currentBooleanOperation = function () {
+ let op;
+ if (m_this._currentBooleanClass) {
+ op = m_this._currentBooleanClass.split('-')[1];
+ }
+ return op;
+ };
+
+ /**
+ * Check if the map is currently in a mode where we are adding an annotation
+ * with a boolean operation. If so, remove the current annotation from the
+ * layer, then apply it via the boolean operation.
+ */
+ this._handleBooleanOperation = function () {
+ const op = m_this.currentBooleanOperation();
+ if (!op || !m_this.currentAnnotation || !m_this.currentAnnotation.toPolygonList) {
+ return;
+ }
+ const newAnnot = m_this.currentAnnotation;
+ m_this.removeAnnotation(m_this.currentAnnotation, false);
+ if (m_this.annotations().length || (op !== 'difference' && op !== 'intersect')) {
+ const evt = {
+ annotation: newAnnot,
+ operation: op
+ };
+ m_this.geoTrigger(geo_event.annotation.boolean, evt);
+ if (evt.cancel !== false) {
+ util.polyops[op](m_this, newAnnot.toPolygonList(), {correspond: {}, keepAnnotations: 'exact', style: m_this});
+ }
+ }
+ };
+
/**
* Handle updating the current annotation based on an update state.
*
@@ -196,6 +234,7 @@ var annotationLayer = function (arg) {
m_this.mode(null);
break;
case 'done':
+ m_this._handleBooleanOperation();
m_this.mode(null);
break;
}
@@ -205,6 +244,27 @@ var annotationLayer = function (arg) {
}
};
+ /**
+ * Check the state of the modifier keys and apply them if appropriate.
+ *
+ * @param {geo.event} evt The mouse move or click event.
+ */
+ this._handleMouseMoveModifiers = function (evt) {
+ if (m_this.mode() !== m_this.modes.edit && m_this.currentAnnotation.options('allowBooleanOperations') && m_this.currentAnnotation._coordinates().length < 2) {
+ if (evt.modifiers) {
+ const mod = (evt.modifiers.shift ? 's' : '') + (evt.modifiers.ctrl ? 'c' : '') + (evt.modifiers.meta || evt.modifiers.alt ? 'a' : '');
+ if (m_this._currentBooleanClass === m_this._booleanClasses[mod]) {
+ return;
+ }
+ m_this._currentBooleanClass = m_this._booleanClasses[mod];
+ const mapNode = m_this.map().node();
+ Object.values(m_this._booleanClasses).forEach((c) => {
+ mapNode.toggleClass(c, m_this._booleanClasses[mod] === c);
+ });
+ }
+ }
+ };
+
/**
* Handle mouse movement. If there is a current annotation, the movement
* event is sent to it.
@@ -213,6 +273,7 @@ var annotationLayer = function (arg) {
*/
this._handleMouseMove = function (evt) {
if (m_this.mode() && m_this.currentAnnotation) {
+ m_this._handleMouseMoveModifiers(evt);
var update = m_this.currentAnnotation.mouseMove(evt);
if (update) {
m_this.modified();
@@ -316,6 +377,7 @@ var annotationLayer = function (arg) {
});
retrigger = true;
} else if (m_this.mode() && m_this.currentAnnotation) {
+ m_this._handleMouseMoveModifiers(evt);
update = m_this.currentAnnotation.mouseClick(evt);
m_this._updateFromEvent(update);
retrigger = !m_this.mode();
@@ -518,6 +580,15 @@ var annotationLayer = function (arg) {
edit: 'edit'
};
+ /* Keys are short-hand for preferred event modifiers. Values are classes to
+ * apply to the map node. */
+ this._booleanClasses = {
+ s: 'annotation-union',
+ sc: 'annotation-intersect',
+ c: 'annotation-difference',
+ sa: 'annotation-xor'
+ };
+
/**
* Get or set the current mode.
*
@@ -537,11 +608,15 @@ var annotationLayer = function (arg) {
if (arg === undefined) {
return m_mode;
}
- if (arg !== m_mode || (arg === m_this.modes.edit && editAnnotation !== m_this.editAnnotation)) {
+ if (arg !== m_mode || (arg === m_this.modes.edit && editAnnotation !== m_this.editAnnotation) || (arg !== m_this.modes.edit && arg && !m_this.currentAnnotation)) {
var createAnnotation, actions,
mapNode = m_this.map().node(), oldMode = m_mode;
m_mode = arg;
mapNode.toggleClass('annotation-input', !!(m_mode && m_mode !== m_this.modes.edit));
+ if (!m_mode || m_mode === m_this.modes.edit) {
+ Object.values(m_this._booleanClasses).forEach((c) => mapNode.toggleClass(c, false));
+ m_this._currentBooleanClass = undefined;
+ }
if (!m_keyHandler) {
m_keyHandler = Mousetrap(mapNode[0]);
}
@@ -618,7 +693,12 @@ var annotationLayer = function (arg) {
*/
this.geojson = function (geojson, clear, gcs, includeCrs) {
if (geojson !== undefined) {
- var reader = registry.createFileReader('geojsonReader', {layer: m_this});
+ var reader = registry.createFileReader('geojsonReader', {
+ layer: m_this,
+ lineStyle: require('./annotation/lineAnnotation').defaults.style,
+ pointStyle: require('./annotation/pointAnnotation').defaults.style,
+ polygonStyle: require('./annotation/polygonAnnotation').defaults.style
+ });
if (!reader.canRead(geojson)) {
return;
}
@@ -1199,6 +1279,13 @@ var annotationLayer = function (arg) {
exact = opts.correspond.exact1;
annot = m_this.annotations();
}
+ if (keep !== 'all' && keep !== 'none') {
+ annot.forEach((oldAnnot, idx) => {
+ if (opts.annotationIndices.indexOf(idx) < 0) {
+ keepIds[oldAnnot.id()] = true;
+ }
+ });
+ }
const polyAnnot = [];
poly.forEach((p, idx) => {
p = p.map((h) => h.map((pt) => ({x: pt[0], y: pt[1]})));
@@ -1218,7 +1305,9 @@ var annotationLayer = function (arg) {
reusedIds[orig.id()] = true;
}
['name', 'description', 'label'].forEach((k) => {
- result[k] = orig[k]();
+ if (orig[k](undefined, true)) {
+ result[k] = orig[k](undefined, true);
+ }
});
Object.entries(orig.options()).forEach(([key, value]) => {
if (['showLabel', 'style'].indexOf(key) >= 0 || key.endsWith('Style')) {
diff --git a/src/css/cursor-crosshair-difference.svg b/src/css/cursor-crosshair-difference.svg
new file mode 100644
index 0000000000..7f878e207d
--- /dev/null
+++ b/src/css/cursor-crosshair-difference.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/css/cursor-crosshair-intersect.svg b/src/css/cursor-crosshair-intersect.svg
new file mode 100644
index 0000000000..7fd92e3180
--- /dev/null
+++ b/src/css/cursor-crosshair-intersect.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/css/cursor-crosshair-union.svg b/src/css/cursor-crosshair-union.svg
new file mode 100644
index 0000000000..811ded9186
--- /dev/null
+++ b/src/css/cursor-crosshair-union.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/css/cursor-crosshair-xor.svg b/src/css/cursor-crosshair-xor.svg
new file mode 100644
index 0000000000..e13e9e28ac
--- /dev/null
+++ b/src/css/cursor-crosshair-xor.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/event.js b/src/event.js
index 3fd19a3723..50a49286e4 100644
--- a/src/event.js
+++ b/src/event.js
@@ -692,4 +692,18 @@ geo_event.annotation.state = 'geo_annotation_state';
*/
geo_event.annotation.mode = 'geo_annotation_mode';
+/**
+ * Triggered when an annotation can be combined via a boolean operation (union,
+ * intersect, difference, xor).
+ *
+ * @event geo.event.annotation.boolean
+ * @type {geo.event.base}
+ * @property {geo.annotation} annotation The annotation that is being operated
+ * on.
+ * @property {string} operation The operation being performed.
+ * @property {boolean} [cancel] If the handle sets this to false, don't apply
+ * the operation to the annotation layer.
+ */
+geo_event.annotation.boolean = 'geo_annotation_boolean';
+
module.exports = geo_event;
diff --git a/src/main.styl b/src/main.styl
index 928f12df33..13ca13c989 100644
--- a/src/main.styl
+++ b/src/main.styl
@@ -49,6 +49,14 @@
&.annotation-input
cursor crosshair
+ &.annotation-intersect
+ cursor embedurl("./css/cursor-crosshair-intersect.svg") 12 12,crosshair
+ &.annotation-difference
+ cursor embedurl("./css/cursor-crosshair-difference.svg") 12 12,crosshair
+ &.annotation-union
+ cursor embedurl("./css/cursor-crosshair-union.svg") 12 12,crosshair
+ &.annotation-xor
+ cursor embedurl("./css/cursor-crosshair-xor.svg") 12 12,crosshair
&.highlight-focus
&:after
diff --git a/src/util/polyops.js b/src/util/polyops.js
index f0f3d5c95f..8db224564a 100644
--- a/src/util/polyops.js
+++ b/src/util/polyops.js
@@ -39,7 +39,7 @@ var geo_map = require('../map');
* applied. If not specified , this is taken from the map second if
* available.
* @property {geo.map} [map] Used for ``ingcs`` and ``gcs`` if needed.
- * @property {string} [innerOperation="xor"] one of union, intersect, xor.
+ * @property {string} [innerOperation="union"] one of union, intersect, xor.
* Used to combine individual polygons in each of ``poly1`` and ``poly2``
* before the main operation is carried out.
* @property {object} [correspond] If present, information about the
@@ -181,6 +181,11 @@ function toPolygonList(poly, mode, opts) {
if (poly.toPolygonList) {
mode.style = poly;
poly = poly.toPolygonList(opts);
+ if (!poly.length) {
+ mode.min = mode.max = [0, 0];
+ mode.epsilon = 1e-10;
+ return poly;
+ }
} else {
mode.style = '';
if (poly.outer) {
@@ -401,8 +406,8 @@ function generalOperationProcess(op, poly1, poly2, opts) {
}
let seglist1 = poly1.map(p => PolyBool.segments({regions: p}));
let seglist2 = poly2.map(p => PolyBool.segments({regions: p}));
- seglist1 = polygonOperationSeglist(opts.innerOperation || 'xor', mode1.epsilon, seglist1);
- seglist2 = polygonOperationSeglist(opts.innerOperation || 'xor', mode2.epsilon, seglist2);
+ seglist1 = polygonOperationSeglist(opts.innerOperation || 'union', mode1.epsilon, seglist1);
+ seglist2 = polygonOperationSeglist(opts.innerOperation || 'union', mode2.epsilon, seglist2);
let seglist = seglist1;
if (seglist1[0] && seglist2[0]) {
/* We need to do the main operation with the same inversion flags */
diff --git a/tests/cases/annotationLayer.js b/tests/cases/annotationLayer.js
index d868ab901f..254bde1c3d 100644
--- a/tests/cases/annotationLayer.js
+++ b/tests/cases/annotationLayer.js
@@ -514,6 +514,47 @@ describe('geo.annotationLayer', function () {
expect(layer.mode()).toBe(null);
layer.options('clickToEdit', false);
});
+ it('_handleMouseClick with modifier', function () {
+ layer.removeAllAnnotations();
+ layer.mode('polygon');
+ expect(layer.annotations().length).toBe(1);
+ expect(layer.annotations()[0].options('vertices').length).toBe(0);
+ var time = Date.now();
+ layer._handleMouseClick({
+ buttonsDown: {left: true},
+ modifiers: {shift: true},
+ time: time,
+ map: {x: 10, y: 20},
+ mapgcs: map.displayToGcs({x: 10, y: 20}, null)
+ });
+ layer._handleMouseClick({
+ buttonsDown: {left: true},
+ time: time,
+ map: {x: 30, y: 20},
+ mapgcs: map.displayToGcs({x: 30, y: 20}, null)
+ });
+ layer._handleMouseClick({
+ buttonsDown: {left: true},
+ time: time + 1000,
+ map: {x: 30, y: 20},
+ mapgcs: map.displayToGcs({x: 30, y: 20}, null)
+ });
+ layer._handleMouseClick({
+ buttonsDown: {left: true},
+ time: time + 1000,
+ map: {x: 20, y: 50},
+ mapgcs: map.displayToGcs({x: 20, y: 50}, null)
+ });
+ layer._handleMouseClick({
+ buttonsDown: {left: true},
+ time: time + 1000,
+ map: {x: 20, y: 50},
+ mapgcs: map.displayToGcs({x: 20, y: 50}, null)
+ });
+ expect(layer.annotations()[0].options('vertices').length).toBe(3);
+ expect(layer.annotations()[0].state()).toBe(geo.annotation.state.done);
+ layer.removeAllAnnotations();
+ });
it('_handleMouseOn', function () {
var rect = geo.annotation.rectangleAnnotation({
layer: layer,
@@ -787,10 +828,10 @@ describe('geo.annotationLayer', function () {
};
layer.geojson(badattr, true);
var attr = layer.geojson().features[0].properties;
- expect(attr.radius).toBeGreaterThan(0);
- expect(attr.fillOpacity).toBeGreaterThan(0);
- expect(attr.fillColor).toBe('#00ff00');
- expect(attr.scaled).toBe(false);
+ expect(attr.radius).toBe(undefined);
+ expect(attr.fillOpacity).toBe(undefined);
+ expect(attr.fillColor).toBe(undefined);
+ expect(attr.scaled).toBe(undefined);
var goodattr = {
type: 'Feature',
geometry: {
diff --git a/tests/cases/polyops.js b/tests/cases/polyops.js
index 8d78541563..284f7f98e2 100644
--- a/tests/cases/polyops.js
+++ b/tests/cases/polyops.js
@@ -68,6 +68,12 @@ describe('geo.util.polyops', function () {
});
});
+ describe('toPolygonList', function () {
+ it('empty list', function () {
+ expect(geo.util.polyops.toPolygonList([])).toEqual([]);
+ });
+ });
+
var opTests = [{
a: [[0, 0], [10, 0], [10, 10], [0, 10]],
b: [[5, 0], [15, 0], [15, 5], [5, 5]],