From be5f12509ad3d35505a7ee2f5955f68a1c3abfe6 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Fri, 13 May 2022 09:49:41 -0400 Subject: [PATCH] perf: Fixed size annotations. Add options to the default annotation constraint function to allow for fixed size annotations. --- CHANGELOG.md | 8 ++++- src/annotation/annotation.js | 38 +++++++++++++++------ src/typedef.js | 9 +++++ tests/cases/annotation.js | 65 ++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 306e6bca45..eedb91839b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ # 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 --Optimize reordering fetch queue ([#1203](../../pull/1203)) +- Optimize reordering fetch queue ([#1203](../../pull/1203)) ## Version 1.8.4 diff --git a/src/annotation/annotation.js b/src/annotation/annotation.js index 600a785bd1..cba55d88d0 100644 --- a/src/annotation/annotation.js +++ b/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; } - const width = (area * ratio) ** 0.5; - const score = (width - dist3) ** 2 + (width / ratio - dist1) ** 2; + if (width !== height && !(index % 2)) { + [width, height] = [height, width]; + } + 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) { diff --git a/src/typedef.js b/src/typedef.js index 2f37fe135e..7f56b55fd5 100644 --- a/src/typedef.js +++ b/src/typedef.js @@ -74,6 +74,15 @@ * @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. * diff --git a/tests/cases/annotation.js b/tests/cases/annotation.js index 0820e8256a..41b997ca6e 100644 --- a/tests/cases/annotation.js +++ b/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 () {