perf: Fixed size annotations.
Add options to the default annotation constraint function to allow for
fixed size annotations.
MODIFY
CHANGELOG.md
CHANGELOG.md
# GeoJS Change Log
+## Version 1.8.6
+
+### Improvements
+
+- Allow constraining rectangle and ellipse annotations to a list of fixed sizes ([#1205](../../pull/1205))
+
## Version 1.8.5
### Improvements

MODIFY
src/annotation/annotation.js
src/annotation/annotation.js
@@ -1266,10 +1266,12 @@ function continuousVerticesProcessAction(m_this, evt, name) {
* that the aspect ratio of a rectangle-like selection is a specific value or
* range of values.
*
- * @param {number|number[]} ratio Either a single aspect ratio or a list of
- * allowed aspect ratios. For instance, 1 will require that the selection
- * square, 2 would require that it is twice as wide as tall, [2, 1/2] would
- * allow it to be twice as wide or half as wide as it is tall.
+ * @param {number|number[]|geo.geoSize|geo.geoSize[]} ratio Either a single
+ * aspect ratio, a single size, or a list of allowed aspect ratios and sizes.
+ * For instance, 1 will require that the selection square, 2 would require
+ * that it is twice as wide as tall, [2, 1/2] would allow it to be twice as
+ * wide or half as wide as it is tall. Sizes (e.g., {width: 400, height:
+ * 500}) snap to that size.
* @returns {function} A function that can be passed to the mapIterator
* selectionConstraint or to an annotation constraint function.
*/
@@ -1315,16 +1317,23 @@ function constrainAspectRatio(ratio) {
const area = Math.abs(dist1 * dist3);
let shape, edge;
ratios.forEach((ratio) => {
- if (ratio !== 1 && !(index % 2)) {
- ratio = 1.0 / ratio;
+ let width, height;
+ if (ratio.width) {
+ width = ratio.width;
+ height = ratio.height;
+ } else {
+ width = (area * ratio) ** 0.5;
+ height = width / ratio;
+ }
+ if (width !== height && !(index % 2)) {
+ [width, height] = [height, width];
}
- const width = (area * ratio) ** 0.5;
- const score = (width - dist3) ** 2 + (width / ratio - dist1) ** 2;
+ const score = (width - dist3) ** 2 + (height - dist1) ** 2;
if (best === undefined || score < best) {
best = score;
shape = {
w: width,
- h: width / ratio
+ h: height
};
}
});
@@ -1366,10 +1375,17 @@ function constrainAspectRatio(ratio) {
/* Not in edit vertex or edge mode */
const area = Math.abs((pos.x - origin.x) * (pos.y - origin.y));
ratios.forEach((ratio) => {
- const width = (area * ratio) ** 0.5;
+ let width, height;
+ if (ratio.width) {
+ width = ratio.width;
+ height = ratio.height;
+ } else {
+ width = (area * ratio) ** 0.5;
+ height = width / ratio;
+ }
const adjusted = {
x: origin.x + Math.sign(pos.x - origin.x) * width,
- y: origin.y + Math.sign(pos.y - origin.y) * width / ratio
+ y: origin.y + Math.sign(pos.y - origin.y) * height
};
const score = (adjusted.x - pos.x) ** 2 + (adjusted.y - pos.y) ** 2;
if (best === undefined || score < best) {

MODIFY
src/typedef.js
src/typedef.js
* @property {number} [z=0] Altitude coordinate, often zero.
*/
+/**
+ * Represention of a size in gcs coordinates.
+ *
+ * @typedef geo.geoSize
+ * @type {object}
+ * @property {number} width Width in gcs coordinates.
+ * @property {number} height Height in gcs coordinates.
+ */
+
/**
* Represention of a size in pixels.
*

MODIFY
tests/cases/annotation.js
tests/cases/annotation.js
@@ -1583,6 +1583,71 @@ describe('geo.annotation', function () {
});
});
+ describe('contrainAspectRatio', function () {
+ it('ratio', function () {
+ const func = geo.annotation.constrainAspectRatio([2, 0.5]);
+
+ let result;
+ result = func(
+ {x: 40, y: 5},
+ {x: 0, y: 0});
+ expect(result.pos).toEqual({x: 20, y: 10});
+ result = func(
+ {x: 40, y: 5},
+ {x: 0, y: 0},
+ [{x: 0, y: 0}, {x: 10, y: 0}, {x: 10, y: 10}, {x: 0, y: 10}]);
+ expect(result.pos).toEqual({x: 20, y: 10});
+ result = func(
+ {x: 5, y: 40},
+ {x: 0, y: 0},
+ [{x: 0, y: 0}, {x: 10, y: 0}, {x: 10, y: 10}, {x: 0, y: 10}]);
+ expect(result.pos).toEqual({x: 10, y: 20});
+ result = func(
+ {x: 0, y: 0},
+ {x: 0, y: 0},
+ [{x: -10, y: 5}, {x: 30, y: 5}, {x: 30, y: 10}, {x: -10, y: 10}],
+ 'edge', [0, -Math.PI / 2, Math.PI, Math.PI / 2], 0);
+ expect(result.corners).toEqual([{x: 20, y: 20}, {x: 0, y: 20}, {x: 0, y: 10}, {x: 20, y: 10}]);
+ result = func(
+ {x: 0, y: 0},
+ {x: 0, y: 0},
+ [{x: -10, y: 5}, {x: 30, y: 5}, {x: 30, y: 10}, {x: -10, y: 10}],
+ 'vertex', [0, -Math.PI / 2, Math.PI, Math.PI / 2], 0);
+ expect(result.corners).toEqual([{x: 10, y: 20}, {x: 30, y: 20}, {x: 30, y: 10}, {x: 10, y: 10}]);
+ });
+ it('fixed size', function () {
+ const func = geo.annotation.constrainAspectRatio({width: 20, height: 10});
+
+ let result;
+ result = func(
+ {x: 40, y: 5},
+ {x: 0, y: 0});
+ expect(result.pos).toEqual({x: 20, y: 10});
+ result = func(
+ {x: 40, y: 5},
+ {x: 0, y: 0},
+ [{x: 0, y: 0}, {x: 10, y: 0}, {x: 10, y: 10}, {x: 0, y: 10}]);
+ expect(result.pos).toEqual({x: 20, y: 10});
+ result = func(
+ {x: 5, y: 40},
+ {x: 0, y: 0},
+ [{x: 0, y: 0}, {x: 10, y: 0}, {x: 10, y: 10}, {x: 0, y: 10}]);
+ expect(result.pos).toEqual({x: 20, y: 10});
+ result = func(
+ {x: 0, y: 0},
+ {x: 0, y: 0},
+ [{x: -10, y: 5}, {x: 30, y: 5}, {x: 30, y: 10}, {x: -10, y: 10}],
+ 'edge', [0, -Math.PI / 2, Math.PI, Math.PI / 2], 0);
+ expect(result.corners).toEqual([{x: 20, y: 20}, {x: 0, y: 20}, {x: 0, y: 10}, {x: 20, y: 10}]);
+ result = func(
+ {x: 0, y: 0},
+ {x: 0, y: 0},
+ [{x: -10, y: 5}, {x: 30, y: 5}, {x: 30, y: 10}, {x: -10, y: 10}],
+ 'vertex', [0, -Math.PI / 2, Math.PI, Math.PI / 2], 0);
+ expect(result.corners).toEqual([{x: 10, y: 20}, {x: 30, y: 20}, {x: 30, y: 10}, {x: 10, y: 10}]);
+ });
+ });
+
describe('annotation registry', function () {
var newshapeCount = 0;
it('listAnnotations', function () {

perf: Fixed size annotations.

Add options to the default annotation constraint function to allow for
fixed size annotations.