Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up transforms by caching the transform class instances. #570

Merged
merged 3 commits into from
May 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 70 additions & 84 deletions src/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
};

Expand Down Expand Up @@ -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;
};

////////////////////////////////////////////////////////////////////////////
Expand All @@ -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;
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/quadFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
95 changes: 60 additions & 35 deletions src/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@manthey does it make sense to have a generic cache object?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean a generic cache for things besides transform? Or something else?

In general, we use only two or four transforms, so caching those in a module-level object is fine. Invalidating the cache when it reaches a certain size (rather than doing it via LRU) is quick and simple, and, since we won't generally have lots of transforms active at on time, it should be performant, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean a generic cache for things besides transform

Yes generic cache object since we have been caching multiple things now and I feel like we could use something like that (not for this PR though).

'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) {
transformCache[options.source] = {};
}
transformCache[options.source][options.target] = new transform(options);
}
return transformCache[options.source][options.target];
}

var m_this = this,
Expand Down Expand Up @@ -227,8 +247,39 @@ 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 && '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 && '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[0]) {
return [output];
}
return [{x: output.x, y: output.y}];
}
return transform.transformCoordinatesArray(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
zAcc = function () {
Expand Down Expand Up @@ -262,7 +313,7 @@ transform.transformCoordinates = function (
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) {
Expand Down Expand Up @@ -321,10 +372,10 @@ transform.transformCoordinates = function (
};
}
} 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');
}
}
}
Expand Down Expand Up @@ -353,28 +404,8 @@ transform.transformCoordinates = function (
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');
}
}

Expand All @@ -395,14 +426,8 @@ transform.transformCoordinates = function (
} 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) {
Expand Down
Loading