From 129c6515066c84cc7c0403d0525da0b5868282d0 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 4 May 2016 11:06:07 -0400 Subject: [PATCH 1/2] Speed up transforms by caching the transform class instances. For simple transforms, calculate them with less object creations. --- src/map.js | 154 +++++++++++++++++++++------------------------ src/quadFeature.js | 2 +- src/transform.js | 44 ++++++++++++- 3 files changed, 112 insertions(+), 88 deletions(-) diff --git a/src/map.js b/src/map.js index a033242e55..c6f53cbcf4 100644 --- a/src/map.js +++ b/src/map.js @@ -136,18 +136,18 @@ var map = function (arg) { * [0, width] and [0, height] instead. */ var mcx = ((m_maxBounds.left || 0) + (m_maxBounds.right || 0)) / 2, mcy = ((m_maxBounds.bottom || 0) + (m_maxBounds.top || 0)) / 2; - m_maxBounds.left = transform.transformCoordinates(m_ingcs, m_gcs, [{ + m_maxBounds.left = transform.transformCoordinates(m_ingcs, m_gcs, { x: m_maxBounds.left !== undefined ? m_maxBounds.left : -180, y: mcy - }])[0].x; - m_maxBounds.right = transform.transformCoordinates(m_ingcs, m_gcs, [{ + }).x; + m_maxBounds.right = transform.transformCoordinates(m_ingcs, m_gcs, { x: m_maxBounds.right !== undefined ? m_maxBounds.right : 180, y: mcy - }])[0].x; + }).x; m_maxBounds.top = (m_maxBounds.top !== undefined ? - transform.transformCoordinates(m_ingcs, m_gcs, [{ - x: mcx, y: m_maxBounds.top}])[0].y : m_maxBounds.right); + transform.transformCoordinates(m_ingcs, m_gcs, { + x: mcx, y: m_maxBounds.top}).y : m_maxBounds.right); m_maxBounds.bottom = (m_maxBounds.bottom !== undefined ? - transform.transformCoordinates(m_ingcs, m_gcs, [{ - x: mcx, y: m_maxBounds.bottom}])[0].y : m_maxBounds.left); + transform.transformCoordinates(m_ingcs, m_gcs, { + x: mcx, y: m_maxBounds.bottom}).y : m_maxBounds.left); m_unitsPerPixel = (arg.unitsPerPixel || ( m_maxBounds.right - m_maxBounds.left) / 256); @@ -410,19 +410,20 @@ var map = function (arg) { */ //////////////////////////////////////////////////////////////////////////// this.pan = function (delta, ignoreDiscreteZoom) { - var evt, unit; - evt = { + var evt = { geo: {}, screenDelta: delta }; - unit = m_this.unitsPerPixel(m_zoom); + if (delta.x || delta.y) { + var unit = m_this.unitsPerPixel(m_zoom); - var sinr = Math.sin(m_rotation), cosr = Math.cos(m_rotation); - m_camera.pan({ - x: (delta.x * cosr - (-delta.y) * sinr) * unit, - y: (delta.x * sinr + (-delta.y) * cosr) * unit - }); + var sinr = Math.sin(m_rotation), cosr = Math.cos(m_rotation); + m_camera.pan({ + x: (delta.x * cosr - (-delta.y) * sinr) * unit, + y: (delta.x * sinr + (-delta.y) * cosr) * unit + }); + } /* If m_clampBounds* is true, clamp the pan */ var bounds = fix_bounds(m_camera.bounds, m_rotation); if (bounds !== m_camera.bounds) { @@ -526,13 +527,10 @@ var map = function (arg) { m_zoom, m_center, m_rotation, null, ignoreDiscreteZoom), m_rotation); m_this.modified(); // trigger a pan event - m_this.geoTrigger( - geo_event.pan, - { - geo: coordinates, - screenDelta: null - } - ); + m_this.geoTrigger(geo_event.pan, { + geo: coordinates, + screenDelta: null + }); return m_this; }; @@ -696,12 +694,17 @@ var map = function (arg) { this.gcsToWorld = function (c, gcs) { gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs)); if (gcs !== m_gcs) { - c = transform.transformCoordinates(gcs, m_gcs, [c])[0]; + c = transform.transformCoordinates(gcs, m_gcs, c); } - return transform.affineForward( - {origin: m_origin}, - [c] - )[0]; + if (m_origin.x || m_origin.y || m_origin.z) { + c = transform.affineForward( + {origin: m_origin}, + [c] + )[0]; + } else if (!('z' in c)) { + c = {x: c.x, y: c.y, z: 0}; + } + return c; }; //////////////////////////////////////////////////////////////////////////// @@ -717,13 +720,17 @@ var map = function (arg) { */ //////////////////////////////////////////////////////////////////////////// this.worldToGcs = function (c, gcs) { - c = transform.affineInverse( - {origin: m_origin}, - [c] - )[0]; + if (m_origin.x || m_origin.y || m_origin.z) { + c = transform.affineInverse( + {origin: m_origin}, + [c] + )[0]; + } else if (!('z' in c)) { + c = {x: c.x, y: c.y, z: 0}; + } gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs)); if (gcs !== m_gcs) { - c = transform.transformCoordinates(m_gcs, gcs, [c])[0]; + c = transform.transformCoordinates(m_gcs, gcs, c); } return c; }; @@ -1038,7 +1045,7 @@ var map = function (arg) { var transitionEnd = $.extend(true, {}, m_transition.end); if (transitionEnd.center && m_gcs !== m_ingcs) { transitionEnd.center = transform.transformCoordinates( - m_gcs, m_ingcs, [transitionEnd.center])[0]; + m_gcs, m_ingcs, transitionEnd.center); } m_queuedTransition = $.extend( {}, transitionEnd || {}, m_queuedTransition || {}, opts); @@ -1086,8 +1093,7 @@ var map = function (arg) { opts = $.extend(true, {}, opts); opts.center = util.normalizeCoordinates(opts.center); if (gcs !== m_gcs) { - opts.center = transform.transformCoordinates(gcs, m_gcs, [ - opts.center])[0]; + opts.center = transform.transformCoordinates(gcs, m_gcs, opts.center); } } opts = $.extend(true, {}, defaultOpts, opts); @@ -1110,37 +1116,17 @@ var map = function (arg) { zoomOrigin: opts.zoomOrigin }; - if (opts.zCoord) { - m_transition.interp = opts.interp( - [ - m_transition.start.center.x, - m_transition.start.center.y, - zoom2z(m_transition.start.zoom), - m_transition.start.rotation - ], - [ - m_transition.end.center.x, - m_transition.end.center.y, - zoom2z(m_transition.end.zoom), - m_transition.end.rotation - ] - ); - } else { - m_transition.interp = opts.interp( - [ - m_transition.start.center.x, - m_transition.start.center.y, - m_transition.start.zoom, - m_transition.start.rotation - ], - [ - m_transition.end.center.x, - m_transition.end.center.y, - m_transition.end.zoom, - m_transition.end.rotation - ] - ); - } + m_transition.interp = opts.interp([ + m_transition.start.center.x, + m_transition.start.center.y, + opts.zCoord ? zoom2z(m_transition.start.zoom) : m_transition.start.zoom, + m_transition.start.rotation + ], [ + m_transition.end.center.x, + m_transition.end.center.y, + opts.zCoord ? zoom2z(m_transition.end.zoom) : m_transition.end.zoom, + m_transition.end.rotation + ]); function anim(time) { var done = m_transition.done, @@ -1303,33 +1289,33 @@ var map = function (arg) { gcs = (gcs === null ? m_gcs : (gcs === undefined ? m_ingcs : gcs)); if (bounds === undefined) { return { - left: transform.transformCoordinates(m_gcs, gcs, [{ - x: m_maxBounds.left, y: 0}])[0].x, - right: transform.transformCoordinates(m_gcs, gcs, [{ - x: m_maxBounds.right, y: 0}])[0].x, - bottom: transform.transformCoordinates(m_gcs, gcs, [{ - x: 0, y: m_maxBounds.bottom}])[0].y, - top: transform.transformCoordinates(m_gcs, gcs, [{ - x: 0, y: m_maxBounds.top}])[0].y + left: transform.transformCoordinates(m_gcs, gcs, { + x: m_maxBounds.left, y: 0}).x, + right: transform.transformCoordinates(m_gcs, gcs, { + x: m_maxBounds.right, y: 0}).x, + bottom: transform.transformCoordinates(m_gcs, gcs, { + x: 0, y: m_maxBounds.bottom}).y, + top: transform.transformCoordinates(m_gcs, gcs, { + x: 0, y: m_maxBounds.top}).y }; } var cx = ((bounds.left || 0) + (bounds.right || 0)) / 2, cy = ((bounds.bottom || 0) + (bounds.top || 0)) / 2; if (bounds.left !== undefined) { - m_maxBounds.left = transform.transformCoordinates(gcs, m_gcs, [{ - x: bounds.left, y: cy}])[0].x; + m_maxBounds.left = transform.transformCoordinates(gcs, m_gcs, { + x: bounds.left, y: cy}).x; } if (bounds.right !== undefined) { - m_maxBounds.right = transform.transformCoordinates(gcs, m_gcs, [{ - x: bounds.right, y: cy}])[0].x; + m_maxBounds.right = transform.transformCoordinates(gcs, m_gcs, { + x: bounds.right, y: cy}).x; } if (bounds.bottom !== undefined) { - m_maxBounds.bottom = transform.transformCoordinates(gcs, m_gcs, [{ - x: cx, y: bounds.bottom}])[0].y; + m_maxBounds.bottom = transform.transformCoordinates(gcs, m_gcs, { + x: cx, y: bounds.bottom}).y; } if (bounds.top !== undefined) { - m_maxBounds.top = transform.transformCoordinates(gcs, m_gcs, [{ - x: cx, y: bounds.top}])[0].y; + m_maxBounds.top = transform.transformCoordinates(gcs, m_gcs, { + x: cx, y: bounds.top}).y; } reset_minimum_zoom(); m_this.zoom(m_zoom); @@ -1380,7 +1366,7 @@ var map = function (arg) { y: (bounds.top + bounds.bottom) / 2 - m_origin.y }; if (gcs !== m_gcs) { - center = transform.transformCoordinates(m_gcs, gcs, [center])[0]; + center = transform.transformCoordinates(m_gcs, gcs, center); } return { zoom: zoom, diff --git a/src/quadFeature.js b/src/quadFeature.js index 037029167c..b7a72477e7 100644 --- a/src/quadFeature.js +++ b/src/quadFeature.js @@ -138,7 +138,7 @@ var quadFeature = function (arg) { map = m_this.layer().map(), order1 = [0, 1, 2, 0], order2 = [1, 2, 3, 1]; coordinate = transform.transformCoordinates( - map.ingcs(), map.gcs(), [coordinate])[0]; + map.ingcs(), map.gcs(), coordinate); if (!m_quads) { this._generateQuads(); } diff --git a/src/transform.js b/src/transform.js index af2bf69c32..e7e293f362 100644 --- a/src/transform.js +++ b/src/transform.js @@ -24,10 +24,30 @@ var proj4 = require('proj4'); */ ////////////////////////////////////////////////////////////////////////////// +var transformCache = {}; +/* Up to maxTransformCacheSize squared might be cached. When the maximum cache + * size is reached, the cache is completely emptied. Since we probably won't + * be rapidly switching between a large number of transforms, this is adequate + * simple behavior. */ +var maxTransformCacheSize = 10; + var transform = function (options) { 'use strict'; if (!(this instanceof transform)) { - return new transform(options); + options = options || {}; + if (!(options.source in transformCache)) { + if (Object.size(transformCache) >= maxTransformCacheSize) { + transformCache = {}; + } + transformCache[options.source] = {}; + } + if (!(options.target in transformCache[options.source])) { + if (Object.size(transformCache[options.source]) >= maxTransformCacheSize) { + options.source = {}; + } + transformCache[options.source][options.target] = new transform(options); + } + return transformCache[options.source][options.target]; } var m_this = this, @@ -227,8 +247,26 @@ transform.transformCoordinates = function ( return coordinates; } - var i, count, offset, xAcc, yAcc, zAcc, writer, output, projPoint, - trans = transform({source: srcPrj, target: tgtPrj}); + var trans = transform({source: srcPrj, target: tgtPrj}), output; + if (coordinates instanceof Object && coordinates.x !== undefined) { + output = trans.forward({x: coordinates.x, y: coordinates.y, z: coordinates.z || 0}); + if ('z' in coordinates) { + return output; + } + return {x: output.x, y: output.y}; + } + if (coordinates instanceof Array && coordinates.length === 1 && coordinates[0] instanceof Object) { + output = trans.forward({x: coordinates[0].x, y: coordinates[0].y, z: coordinates[0].z || 0}); + if ('z' in coordinates) { + return [output]; + } + return [{x: output.x, y: output.y}]; + } + return transform.transformCoordinatesGeneral(trans, coordinates, numberOfComponents); +}; + +transform.transformCoordinatesGeneral = function (trans, coordinates, numberOfComponents) { + var i, count, offset, xAcc, yAcc, zAcc, writer, output, projPoint; /// Default Z accessor zAcc = function () { From 346ac1a0b0b265605660808f9f2d9103765c548d Mon Sep 17 00:00:00 2001 From: David Manthey Date: Fri, 6 May 2016 10:45:52 -0400 Subject: [PATCH 2/2] Added tests and fix a few bugs. Removed code that is not reachable. Changed errors to throw new Error() rather than just strings. --- src/transform.js | 63 +++++++----------- tests/cases/transform.js | 138 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 38 deletions(-) diff --git a/src/transform.js b/src/transform.js index e7e293f362..0aa0e30352 100644 --- a/src/transform.js +++ b/src/transform.js @@ -43,7 +43,7 @@ var transform = function (options) { } if (!(options.target in transformCache[options.source])) { if (Object.size(transformCache[options.source]) >= maxTransformCacheSize) { - options.source = {}; + transformCache[options.source] = {}; } transformCache[options.source][options.target] = new transform(options); } @@ -248,24 +248,37 @@ transform.transformCoordinates = function ( } var trans = transform({source: srcPrj, target: tgtPrj}), output; - if (coordinates instanceof Object && coordinates.x !== undefined) { + if (coordinates instanceof Object && 'x' in coordinates && 'y' in coordinates) { output = trans.forward({x: coordinates.x, y: coordinates.y, z: coordinates.z || 0}); if ('z' in coordinates) { return output; } return {x: output.x, y: output.y}; } - if (coordinates instanceof Array && coordinates.length === 1 && coordinates[0] instanceof Object) { + if (coordinates instanceof Array && coordinates.length === 1 && coordinates[0] instanceof Object && 'x' in coordinates[0] && 'y' in coordinates[0]) { output = trans.forward({x: coordinates[0].x, y: coordinates[0].y, z: coordinates[0].z || 0}); - if ('z' in coordinates) { + if ('z' in coordinates[0]) { return [output]; } return [{x: output.x, y: output.y}]; } - return transform.transformCoordinatesGeneral(trans, coordinates, numberOfComponents); + return transform.transformCoordinatesArray(trans, coordinates, numberOfComponents); }; -transform.transformCoordinatesGeneral = function (trans, coordinates, numberOfComponents) { +/** + * Transform an array of coordinates from one projection into another. The + * transformation may occur in place (modifying the input coordinate array), + * depending on the input format. The coordinates can be an array of 2 or 3 + * values, or an array of either of those, or a single flat array with 2 or 3 + * components per coordinate. The array is modified in place. + * + * @param {object} trans The transformation object. + * @param {geoPosition[]} coordinates An array of coordinate objects + * @param {number} numberOfComponents for flat arrays, either 2 or 3. + * + * @returns {geoPosition[]} The transformed coordinates + */ +transform.transformCoordinatesArray = function (trans, coordinates, numberOfComponents) { var i, count, offset, xAcc, yAcc, zAcc, writer, output, projPoint; /// Default Z accessor @@ -300,7 +313,7 @@ transform.transformCoordinatesGeneral = function (trans, coordinates, numberOfCo output[index] = [x, y, z]; }; } else { - throw 'Invalid coordinates. Requires two or three components per array'; + throw new Error('Invalid coordinates. Requires two or three components per array'); } } else { if (coordinates.length === 2) { @@ -359,10 +372,10 @@ transform.transformCoordinatesGeneral = function (trans, coordinates, numberOfCo }; } } else { - throw 'Number of components should be two or three'; + throw new Error('Number of components should be two or three'); } } else { - throw 'Invalid coordinates'; + throw new Error('Invalid coordinates'); } } } @@ -391,28 +404,8 @@ transform.transformCoordinatesGeneral = function (trans, coordinates, numberOfCo output[index] = {x: x, y: y}; }; } - } else if (coordinates && 'x' in coordinates && 'y' in coordinates) { - xAcc = function () { - return coordinates.x; - }; - yAcc = function () { - return coordinates.y; - }; - - if ('z' in coordinates) { - zAcc = function () { - return coordinates.z; - }; - writer = function (index, x, y, z) { - output = {x: x, y: y, z: z}; - }; - } else { - writer = function (index, x, y) { - output = {x: x, y: y}; - }; - } } else { - throw 'Invalid coordinates'; + throw new Error('Invalid coordinates'); } } @@ -433,14 +426,8 @@ transform.transformCoordinatesGeneral = function (trans, coordinates, numberOfCo } else { handleArrayCoordinates(); } - } else if (coordinates && coordinates instanceof Object) { - count = 1; - offset = 1; - if (coordinates && 'x' in coordinates && 'y' in coordinates) { - handleObjectCoordinates(); - } else { - throw 'Coordinates are not valid'; - } + } else { + throw new Error('Coordinates are not valid'); } for (i = 0; i < count; i += offset) { diff --git a/tests/cases/transform.js b/tests/cases/transform.js index f66f6fb0fb..e75348b73b 100644 --- a/tests/cases/transform.js +++ b/tests/cases/transform.js @@ -5,6 +5,8 @@ describe('geo.transform', function () { var $ = require('jquery'); var geo = require('../test-utils').geo; + var closeToEqual = require('../test-utils').closeToEqual; + var closeToArray = require('../test-utils').closeToArray; function r2(pt1, pt2) { // euclidean norm @@ -175,4 +177,140 @@ describe('geo.transform', function () { expect(geo.transform.defs.hasOwnProperty('unknown:5002')).toBe(false); }); }); + + describe('transform cache', function () { + it('cache is used', function () { + var trans = geo.transform({source: 'EPSG:4326', target: 'EPSG:3857'}); + expect(geo.transform({source: 'EPSG:4326', target: 'EPSG:3857'})).toBe(trans); + }); + it('cache is cleared for targets', function () { + var trans = geo.transform({source: 'EPSG:4326', target: 'EPSG:3857'}); + for (var i = 0; i < 10; i += 1) { + var target = '+proj=eqc +ellps=GRS80 +lat_0=0 +lat_ts=' + i + ' +lon_0=0 +no_defs +towgs84=0,0,0,0,0,0,0 +units=m +x_0=0 +y_0=0'; + geo.transform({source: 'EPSG:4326', target: target}); + } + expect(geo.transform({source: 'EPSG:4326', target: 'EPSG:3857'})).not.toBe(trans); + }); + it('cache is cleared for sources', function () { + var trans = geo.transform({source: 'EPSG:4326', target: 'EPSG:3857'}); + for (var i = 0; i < 10; i += 1) { + var source = '+proj=eqc +ellps=GRS80 +lat_0=0 +lat_ts=' + i + ' +lon_0=0 +no_defs +towgs84=0,0,0,0,0,0,0 +units=m +x_0=0 +y_0=0'; + geo.transform({source: source, target: 'EPSG:3857'}); + } + expect(geo.transform({source: 'EPSG:4326', target: 'EPSG:3857'})).not.toBe(trans); + }); + }); + + describe('transformCoordinates', function () { + var source = '+proj=longlat +axis=esu', + target = '+proj=longlat +axis=enu'; + it('identity', function () { + var coor = {x: 1, y: 2, z: 3}; + expect(geo.transform.transformCoordinates( + 'EPSG:4326', 'EPSG:4326', coor)).toBe(coor); + }); + it('bad parameters', function () { + expect(function () { + geo.transform.transformCoordinates(source, target, undefined); + }).toThrow(new Error('Coordinates are not valid')); + expect(function () { + geo.transform.transformCoordinates(source, target, [[1], [2], [3]]); + }).toThrow(new Error('Invalid coordinates. Requires two or three components per array')); + expect(function () { + geo.transform.transformCoordinates(source, target, [1, 2, 3, 4, 5], 5); + }).toThrow(new Error('Number of components should be two or three')); + expect(function () { + geo.transform.transformCoordinates(source, target, [1, 2, 3, 4, 5]); + }).toThrow(new Error('Invalid coordinates')); + expect(function () { + geo.transform.transformCoordinates(source, target, [{z: 5}]); + }).toThrow(new Error('Invalid coordinates')); + }); + it('coordinate format - single object', function () { + expect(closeToEqual(geo.transform.transformCoordinates(source, target, {x: 1, y: 2}), {x: 1, y: -2})).toBe(true); + expect(closeToEqual(geo.transform.transformCoordinates(source, target, {x: 3, y: 4, z: 5}), {x: 3, y: -4, z: 5})).toBe(true); + }); + it('coordinate format - array with single object', function () { + var res; + res = geo.transform.transformCoordinates(source, target, [{x: 1, y: 2}]); + expect(res instanceof Array).toBe(true); + expect(res.length).toBe(1); + expect(closeToEqual(res[0], {x: 1, y: -2})).toBe(true); + res = geo.transform.transformCoordinates(source, target, [{x: 3, y: 4, z: 5}]); + expect(res instanceof Array).toBe(true); + expect(res.length).toBe(1); + expect(closeToEqual(res[0], {x: 3, y: -4, z: 5})).toBe(true); + }); + it('coordinate format - single array', function () { + expect(closeToArray(geo.transform.transformCoordinates(source, target, [1, 2]), [1, -2])).toBe(true); + expect(closeToArray(geo.transform.transformCoordinates(source, target, [3, 4, 5]), [3, -4, 5])).toBe(true); + expect(closeToArray(geo.transform.transformCoordinates(source, target, [1, 2, 3, 4, 5, 6], 2), [1, -2, 3, -4, 5, -6])).toBe(true); + expect(closeToArray(geo.transform.transformCoordinates(source, target, [1, 2, 3, 4, 5, 6], 3), [1, -2, 3, 4, -5, 6])).toBe(true); + }); + it('coordinate format - array of arrays', function () { + var res; + res = geo.transform.transformCoordinates(source, target, [[1, 2], [3, 4], [5, 6]]); + expect(res.length).toBe(3); + expect(closeToArray(res[0], [1, -2])).toBe(true); + expect(closeToArray(res[1], [3, -4])).toBe(true); + expect(closeToArray(res[2], [5, -6])).toBe(true); + res = geo.transform.transformCoordinates(source, target, [[1, 2, 3], [4, 5, 6]]); + expect(res.length).toBe(2); + expect(closeToArray(res[0], [1, -2, 3])).toBe(true); + expect(closeToArray(res[1], [4, -5, 6])).toBe(true); + }); + it('coordinate format - array of objects', function () { + var res; + res = geo.transform.transformCoordinates(source, target, [{x: 1, y: 2}, {x: 3, y: 4}, {x: 5, y: 6}]); + expect(res.length).toBe(3); + expect(closeToEqual(res[0], {x: 1, y: -2})).toBe(true); + expect(closeToEqual(res[1], {x: 3, y: -4})).toBe(true); + expect(closeToEqual(res[2], {x: 5, y: -6})).toBe(true); + res = geo.transform.transformCoordinates(source, target, [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}]); + expect(res.length).toBe(2); + expect(closeToEqual(res[0], {x: 1, y: -2, z: 3})).toBe(true); + expect(closeToEqual(res[1], {x: 4, y: -5, z: 6})).toBe(true); + }); + }); + + describe('affine functions', function () { + it('affineForward', function () { + var coor, res; + coor = [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}]; + res = geo.transform.affineForward({origin: {x: 0, y: 0}}, coor); + expect(coor).toEqual(res); + expect(res.length).toBe(2); + expect(res[0]).toEqual({x: 1, y: 2, z: 3}); + expect(res[1]).toEqual({x: 4, y: 5, z: 6}); + coor = [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}]; + res = geo.transform.affineForward({origin: {x: -2, y: -3}}, coor); + expect(coor).toEqual(res); + expect(res[0]).toEqual({x: 3, y: 5, z: 3}); + expect(res[1]).toEqual({x: 6, y: 8, z: 6}); + coor = [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}]; + res = geo.transform.affineForward({origin: {x: -2, y: -3}, scale: {x: 2, y: 3, z: 4}}, coor); + expect(coor).toEqual(res); + expect(res[0]).toEqual({x: 6, y: 15, z: 12}); + expect(res[1]).toEqual({x: 12, y: 24, z: 24}); + }); + it('affineInverse', function () { + var coor, res; + coor = [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}]; + res = geo.transform.affineInverse({origin: {x: 0, y: 0}}, coor); + expect(coor).toEqual(res); + expect(res.length).toBe(2); + expect(res[0]).toEqual({x: 1, y: 2, z: 3}); + expect(res[1]).toEqual({x: 4, y: 5, z: 6}); + coor = [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}]; + res = geo.transform.affineInverse({origin: {x: -2, y: -3}}, coor); + expect(coor).toEqual(res); + expect(res[0]).toEqual({x: -1, y: -1, z: 3}); + expect(res[1]).toEqual({x: 2, y: 2, z: 6}); + coor = [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}]; + res = geo.transform.affineInverse({origin: {x: -2, y: -3}, scale: {x: 2, y: 3, z: 4}}, coor); + expect(coor).toEqual(res); + expect(res[0]).toEqual({x: -3 / 2, y: -7 / 3, z: 3 / 4}); + expect(res[1]).toEqual({x: 0, y: -4 / 3, z: 6 / 4}); + }); + }); });