From ee2485534ad41a650334fcca35a697a12548b97d Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Feb 2014 17:48:17 -0700 Subject: [PATCH 1/8] Add an example that renders a simple fractal This works at resolutions where simplification is significantly reducing the number of points in the line. At high resolutions, navigation becomes unresponsive. Limiting draw instructions to a replay group's extent will fix this. --- examples/fractal.html | 64 +++++++++++++++++++++ examples/fractal.js | 125 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 examples/fractal.html create mode 100644 examples/fractal.js diff --git a/examples/fractal.html b/examples/fractal.html new file mode 100644 index 0000000000..b576c4d94b --- /dev/null +++ b/examples/fractal.html @@ -0,0 +1,64 @@ + + + + + + + + + + + Fractal Example + + + + + + +
+ +
+
+
+
+
+ +
+ +
+

Fractal Example

+

Example of a fractal.

+ +
+

See the fractal.js source to see how this is done.

+
+
fractal, vector
+
+ +
+ +
+ + + + + + + diff --git a/examples/fractal.js b/examples/fractal.js new file mode 100644 index 0000000000..3f0261ca35 --- /dev/null +++ b/examples/fractal.js @@ -0,0 +1,125 @@ +goog.require('ol.Feature'); +goog.require('ol.Map'); +goog.require('ol.View2D'); +goog.require('ol.geom.LineString'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); + +var radius = 10e6; +var cos30 = Math.cos(Math.PI / 6); +var sin30 = Math.sin(Math.PI / 6); +var rise = radius * sin30; +var run = radius * cos30; + +var triangle = new ol.geom.LineString([ + [0, radius], [run, -rise], [-run, -rise], [0, radius] +]); + +var feature = new ol.Feature(triangle); + +var layer = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: [feature] + }) +}); + +var map = new ol.Map({ + layers: [layer], + renderer: 'canvas', + target: 'map', + view: new ol.View2D({ + center: [0, 0], + zoom: 1 + }) +}); + +function makeFractal(depth) { + var geometry = /** @type {ol.geom.LineString} */ (triangle.clone()); + var graph = coordsToGraph(geometry.getCoordinates()); + for (var i = 0; i < depth; ++i) { + var node = graph; + while (node.next) { + var next = node.next; + injectNodes(node); + node = next; + } + } + var coordinates = graphToCoords(graph); + document.getElementById('count').innerHTML = coordinates.length; + geometry.setCoordinates(coordinates); + feature.setGeometry(geometry); +} + +function injectNodes(startNode) { + var endNode = startNode.next; + + var start = startNode.point; + var end = startNode.next.point; + var dx = end[0] - start[0]; + var dy = end[1] - start[1]; + + // first point at 1/3 along the segment + var firstNode = { + point: [start[0] + dx / 3, start[1] + dy / 3] + }; + + // second point at peak of _/\_ + var r = Math.sqrt(dx * dx + dy * dy) / (2 * cos30); + var a = Math.atan2(dy, dx) + Math.PI / 6; + var secondNode = { + point: [start[0] + r * Math.cos(a), start[1] + r * Math.sin(a)] + }; + + // third point at 2/3 along the segment + var thirdNode = { + point: [end[0] - dx / 3, end[1] - dy / 3] + }; + + startNode.next = firstNode; + firstNode.next = secondNode; + secondNode.next = thirdNode; + thirdNode.next = endNode; +} + + +function coordsToGraph(coordinates) { + var graph = { + point: coordinates[0] + }; + var length = coordinates.length; + for (var level = 0, node = graph; level < length - 1; ++level) { + node.next = { + point: coordinates[level + 1] + }; + node = node.next; + } + return graph; +} + +function graphToCoords(graph) { + var coordinates = [graph.point]; + for (var node = graph, i = 1; node.next; node = node.next, ++i) { + coordinates[i] = node.next.point; + } + return coordinates; +} + +var depthInput = document.getElementById('depth'); + +function update() { + makeFractal(Number(depthInput.value)); +} + +var updateTimer; + + +/** + * Regenerate fractal on depth change. Change events are debounced so updates + * only occur every 200ms. + */ +depthInput.onchange = function() { + window.clearTimeout(updateTimer); + updateTimer = window.setTimeout(update, 200); +}; + +update(); From f4746687e8b3b47606f990e50a9aa4d228ad0576 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 21 Feb 2014 19:25:49 -0700 Subject: [PATCH 2/8] Keep track of max extent for replay --- src/ol/render/canvas/canvasreplay.js | 46 +++++++++++++------ .../canvas/canvasvectorlayerrenderer.js | 2 +- src/ol/source/imagevectorsource.js | 2 +- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index f54a704e20..b7a03a87ca 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -47,10 +47,11 @@ ol.render.canvas.Instruction = { * @constructor * @implements {ol.render.IVectorContext} * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. * @protected * @struct */ -ol.render.canvas.Replay = function(tolerance) { +ol.render.canvas.Replay = function(tolerance, maxExtent) { /** * @protected @@ -58,6 +59,12 @@ ol.render.canvas.Replay = function(tolerance) { */ this.tolerance = tolerance; + /** + * @protected + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; + /** * @private * @type {Array.<*>} @@ -582,12 +589,13 @@ ol.render.canvas.Replay.prototype.setTextStyle = goog.abstractMethod; * @constructor * @extends {ol.render.canvas.Replay} * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. * @protected * @struct */ -ol.render.canvas.ImageReplay = function(tolerance) { +ol.render.canvas.ImageReplay = function(tolerance, maxExtent) { - goog.base(this, tolerance); + goog.base(this, tolerance, maxExtent); /** * @private @@ -810,12 +818,13 @@ ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) { * @constructor * @extends {ol.render.canvas.Replay} * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. * @protected * @struct */ -ol.render.canvas.LineStringReplay = function(tolerance) { +ol.render.canvas.LineStringReplay = function(tolerance, maxExtent) { - goog.base(this, tolerance); + goog.base(this, tolerance, maxExtent); /** * @private @@ -1027,12 +1036,13 @@ ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = * @constructor * @extends {ol.render.canvas.Replay} * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. * @protected * @struct */ -ol.render.canvas.PolygonReplay = function(tolerance) { +ol.render.canvas.PolygonReplay = function(tolerance, maxExtent) { - goog.base(this, tolerance); + goog.base(this, tolerance, maxExtent); /** * @private @@ -1360,12 +1370,13 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { * @constructor * @extends {ol.render.canvas.Replay} * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Maximum extent. * @protected * @struct */ -ol.render.canvas.TextReplay = function(tolerance) { +ol.render.canvas.TextReplay = function(tolerance, maxExtent) { - goog.base(this, tolerance); + goog.base(this, tolerance, maxExtent); /** * @private @@ -1659,9 +1670,10 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { * @constructor * @implements {ol.render.IReplayGroup} * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. * @struct */ -ol.render.canvas.ReplayGroup = function(tolerance) { +ol.render.canvas.ReplayGroup = function(tolerance, maxExtent) { /** * @private @@ -1669,6 +1681,12 @@ ol.render.canvas.ReplayGroup = function(tolerance) { */ this.tolerance_ = tolerance; + /** + * @private + * @type {ol.Extent} + */ + this.maxExtent_ = maxExtent; + /** * @private * @type {Object.} + * function(new: ol.render.canvas.Replay, number, ol.Extent)>} */ ol.render.canvas.BATCH_CONSTRUCTORS_ = { 'Image': ol.render.canvas.ImageReplay, diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 47c786aa5c..02f30f3dd9 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -212,7 +212,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = styleFunction = ol.feature.defaultStyleFunction; } var tolerance = frameStateResolution / (2 * pixelRatio); - var replayGroup = new ol.render.canvas.ReplayGroup(tolerance); + var replayGroup = new ol.render.canvas.ReplayGroup(tolerance, extent); vectorSource.forEachFeatureInExtent(extent, /** * @param {ol.Feature} feature Feature. diff --git a/src/ol/source/imagevectorsource.js b/src/ol/source/imagevectorsource.js index 7ec8e04888..16861f4bfc 100644 --- a/src/ol/source/imagevectorsource.js +++ b/src/ol/source/imagevectorsource.js @@ -111,7 +111,7 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resolution, pixelRatio, size, projection) { var tolerance = resolution / (2 * pixelRatio); - var replayGroup = new ol.render.canvas.ReplayGroup(tolerance); + var replayGroup = new ol.render.canvas.ReplayGroup(tolerance, extent); var loading = false; this.source_.forEachFeatureInExtent(extent, From cb11959f01cd8a639552be9a9a18f9871caaab3b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sat, 22 Feb 2014 18:14:58 -0700 Subject: [PATCH 3/8] Determining coordinate relationship to extent --- src/ol/extent.js | 47 +++++++++++++++++++++ test/spec/ol/extent.test.js | 82 ++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/src/ol/extent.js b/src/ol/extent.js index baff0fc59b..989b889212 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -1,5 +1,6 @@ goog.provide('ol.Extent'); goog.provide('ol.extent'); +goog.provide('ol.extent.Relationship'); goog.require('goog.asserts'); goog.require('ol.Coordinate'); @@ -15,6 +16,20 @@ goog.require('ol.TransformFunction'); ol.Extent; +/** + * Relationship to an extent. + * @enum {number} + */ +ol.extent.Relationship = { + UNKNOWN: 0, + INTERSECTING: 1, + ABOVE: 2, + RIGHT: 4, + BELOW: 8, + LEFT: 16 +}; + + /** * Builds an extent that includes all given coordinates. * @@ -150,6 +165,38 @@ ol.extent.containsExtent = function(extent1, extent2) { }; +/** + * Get the relationship between a coordinate and extent. + * @param {ol.Extent} extent The extent. + * @param {ol.Coordinate} coordinate The coordinate. + * @return {number} The relationship (bitwise compare with + * ol.extent.Relationship). + */ +ol.extent.coordinateRelationship = function(extent, coordinate) { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var x = coordinate[0]; + var y = coordinate[1]; + var relationship = ol.extent.Relationship.UNKNOWN; + if (x < minX) { + relationship = relationship | ol.extent.Relationship.LEFT; + } else if (x > maxX) { + relationship = relationship | ol.extent.Relationship.RIGHT; + } + if (y < minY) { + relationship = relationship | ol.extent.Relationship.BELOW; + } else if (y > maxY) { + relationship = relationship | ol.extent.Relationship.ABOVE; + } + if (relationship === ol.extent.Relationship.UNKNOWN) { + relationship = ol.extent.Relationship.INTERSECTING; + } + return relationship; +}; + + /** * @return {ol.Extent} Empty extent. * @todo stability experimental diff --git a/test/spec/ol/extent.test.js b/test/spec/ol/extent.test.js index 3bc115bb98..f6fee8f1f6 100644 --- a/test/spec/ol/extent.test.js +++ b/test/spec/ol/extent.test.js @@ -1,6 +1,5 @@ goog.provide('ol.test.extent'); - describe('ol.extent', function() { describe('buffer', function() { @@ -65,6 +64,86 @@ describe('ol.extent', function() { }); }); + describe('coordinateRelationship()', function() { + + var extent = [-180, -90, 180, 90]; + var INTERSECTING = ol.extent.Relationship.INTERSECTING; + var ABOVE = ol.extent.Relationship.ABOVE; + var RIGHT = ol.extent.Relationship.RIGHT; + var BELOW = ol.extent.Relationship.BELOW; + var LEFT = ol.extent.Relationship.LEFT; + + it('returns intersecting for within', function() { + var rel = ol.extent.coordinateRelationship(extent, [0, 0]); + expect(rel).to.be(INTERSECTING); + }); + + it('returns intersecting for touching top', function() { + var rel = ol.extent.coordinateRelationship(extent, [0, 90]); + expect(rel).to.be(INTERSECTING); + }); + + it('returns intersecting for touching right', function() { + var rel = ol.extent.coordinateRelationship(extent, [180, 0]); + expect(rel).to.be(INTERSECTING); + }); + + it('returns intersecting for touching bottom', function() { + var rel = ol.extent.coordinateRelationship(extent, [0, -90]); + expect(rel).to.be(INTERSECTING); + }); + + it('returns intersecting for touching left', function() { + var rel = ol.extent.coordinateRelationship(extent, [-180, 0]); + expect(rel).to.be(INTERSECTING); + }); + + it('above for north', function() { + var rel = ol.extent.coordinateRelationship(extent, [0, 100]); + expect(rel).to.be(ABOVE); + }); + + it('above and right for northeast', function() { + var rel = ol.extent.coordinateRelationship(extent, [190, 100]); + expect(rel & ABOVE).to.be(ABOVE); + expect(rel & RIGHT).to.be(RIGHT); + }); + + it('right for east', function() { + var rel = ol.extent.coordinateRelationship(extent, [190, 0]); + expect(rel).to.be(RIGHT); + }); + + it('below and right for southeast', function() { + var rel = ol.extent.coordinateRelationship(extent, [190, -100]); + expect(rel & BELOW).to.be(BELOW); + expect(rel & RIGHT).to.be(RIGHT); + }); + + it('below for south', function() { + var rel = ol.extent.coordinateRelationship(extent, [0, -100]); + expect(rel).to.be(BELOW); + }); + + it('below and left for southwest', function() { + var rel = ol.extent.coordinateRelationship(extent, [-190, -100]); + expect(rel & BELOW).to.be(BELOW); + expect(rel & LEFT).to.be(LEFT); + }); + + it('left for west', function() { + var rel = ol.extent.coordinateRelationship(extent, [-190, 0]); + expect(rel).to.be(LEFT); + }); + + it('above and left for northwest', function() { + var rel = ol.extent.coordinateRelationship(extent, [-190, 100]); + expect(rel & ABOVE).to.be(ABOVE); + expect(rel & LEFT).to.be(LEFT); + }); + + }); + describe('getCenter', function() { it('returns the expected center', function() { var extent = [1, 2, 3, 4]; @@ -267,4 +346,5 @@ describe('ol.extent', function() { goog.require('ol.extent'); +goog.require('ol.extent.Relationship'); goog.require('ol.proj'); From 6b018bbaf93b4adba51a0ed8f73d1cd2f452499f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Sat, 22 Feb 2014 18:39:04 -0700 Subject: [PATCH 4/8] Testing segment intersection with extent If needed, this function can be modified to take two additional coorinates that will be set to the intersection points. --- src/ol/extent.js | 53 +++++++++++++++ test/spec/ol/extent.test.js | 125 ++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 989b889212..766beeaa62 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -643,6 +643,59 @@ ol.extent.scaleFromCenter = function(extent, value) { }; +/** + * Determine if the segment between two coordinates intersects (crosses, + * touches, or is contained by) the provided extent. + * @param {ol.Extent} extent The extent. + * @param {ol.Coordinate} start Segment start coordinate. + * @param {ol.Coordinate} end Segment end coordinate. + * @return {boolean} The segment intersects the extent. + */ +ol.extent.segmentIntersects = function(extent, start, end) { + var intersects = false; + var startRel = ol.extent.coordinateRelationship(extent, start); + var endRel = ol.extent.coordinateRelationship(extent, end); + if (startRel === ol.extent.Relationship.INTERSECTING || + endRel === ol.extent.Relationship.INTERSECTING) { + intersects = true; + } else { + var minX = extent[0]; + var minY = extent[1]; + var maxX = extent[2]; + var maxY = extent[3]; + var startX = start[0]; + var startY = start[1]; + var endX = end[0]; + var endY = end[1]; + var slope = (endY - startY) / (endX - startX); + var x, y; + if (!!(endRel & ol.extent.Relationship.ABOVE) && + !(startRel & ol.extent.Relationship.ABOVE)) { + // potentially intersects top + x = endX - ((endY - maxY) / slope); + intersects = x >= minX && x <= maxX; + } else if (!!(endRel & ol.extent.Relationship.RIGHT) && + !(startRel & ol.extent.Relationship.RIGHT)) { + // potentially intersects right + y = endY - ((endX - maxX) * slope); + intersects = y >= minY && y <= maxY; + } else if (!!(endRel & ol.extent.Relationship.BELOW) && + !(startRel & ol.extent.Relationship.BELOW)) { + // potentially intersects bottom + x = endX - ((endY - minY) / slope); + intersects = x >= minX && x <= maxX; + } else if (!!(endRel & ol.extent.Relationship.LEFT) && + !(startRel & ol.extent.Relationship.LEFT)) { + // potentially intersects left + y = endY - ((endX - minX) * slope); + intersects = y >= minY && y <= maxY; + } + + } + return intersects; +}; + + /** * @param {ol.Extent} extent1 Extent 1. * @param {ol.Extent} extent2 Extent 2. diff --git a/test/spec/ol/extent.test.js b/test/spec/ol/extent.test.js index f6fee8f1f6..52901cff5f 100644 --- a/test/spec/ol/extent.test.js +++ b/test/spec/ol/extent.test.js @@ -300,6 +300,131 @@ describe('ol.extent', function() { }); }); + describe('segmentIntersects()', function() { + + var extent = [-180, -90, 180, 90]; + var north = [0, 100]; + var northeast = [190, 100]; + var east = [190, 0]; + var southeast = [190, -100]; + var south = [0, -100]; + var southwest = [-190, -100]; + var west = [-190, 0]; + var northwest = [-190, 100]; + var center = [0, 0]; + var top = [0, 90]; + var right = [180, 0]; + var bottom = [-90, 0]; + var left = [-180, 0]; + var inside = [10, 10]; + + it('returns true if contained', function() { + var intersects = ol.extent.segmentIntersects(extent, center, inside); + expect(intersects).to.be(true); + }); + + it('returns true if crosses top', function() { + var intersects = ol.extent.segmentIntersects(extent, center, north); + expect(intersects).to.be(true); + }); + + it('returns true if crosses right', function() { + var intersects = ol.extent.segmentIntersects(extent, center, east); + expect(intersects).to.be(true); + }); + + it('returns true if crosses bottom', function() { + var intersects = ol.extent.segmentIntersects(extent, center, south); + expect(intersects).to.be(true); + }); + + it('returns true if crosses left', function() { + var intersects = ol.extent.segmentIntersects(extent, center, west); + expect(intersects).to.be(true); + }); + + it('returns false if above', function() { + var intersects = ol.extent.segmentIntersects(extent, northwest, north); + expect(intersects).to.be(false); + }); + + it('returns false if right', function() { + var intersects = ol.extent.segmentIntersects(extent, northeast, east); + expect(intersects).to.be(false); + }); + + it('returns false if below', function() { + var intersects = ol.extent.segmentIntersects(extent, south, southwest); + expect(intersects).to.be(false); + }); + + it('returns false if left', function() { + var intersects = ol.extent.segmentIntersects(extent, west, southwest); + expect(intersects).to.be(false); + }); + + it('returns true if crosses top to bottom', function() { + var intersects = ol.extent.segmentIntersects(extent, north, south); + expect(intersects).to.be(true); + }); + + it('returns true if crosses bottom to top', function() { + var intersects = ol.extent.segmentIntersects(extent, south, north); + expect(intersects).to.be(true); + }); + + it('returns true if crosses left to right', function() { + var intersects = ol.extent.segmentIntersects(extent, west, east); + expect(intersects).to.be(true); + }); + + it('returns true if crosses right to left', function() { + var intersects = ol.extent.segmentIntersects(extent, east, west); + expect(intersects).to.be(true); + }); + + it('returns true if crosses northwest to east', function() { + var intersects = ol.extent.segmentIntersects(extent, northwest, east); + expect(intersects).to.be(true); + }); + + it('returns true if crosses south to west', function() { + var intersects = ol.extent.segmentIntersects(extent, south, west); + expect(intersects).to.be(true); + }); + + it('returns true if touches top', function() { + var intersects = ol.extent.segmentIntersects(extent, northwest, top); + expect(intersects).to.be(true); + }); + + it('returns true if touches right', function() { + var intersects = ol.extent.segmentIntersects(extent, southeast, right); + expect(intersects).to.be(true); + }); + + it('returns true if touches bottom', function() { + var intersects = ol.extent.segmentIntersects(extent, bottom, south); + expect(intersects).to.be(true); + }); + + it('returns true if touches left', function() { + var intersects = ol.extent.segmentIntersects(extent, left, west); + expect(intersects).to.be(true); + }); + + it('works for zero length inside', function() { + var intersects = ol.extent.segmentIntersects(extent, center, center); + expect(intersects).to.be(true); + }); + + it('works for zero length outside', function() { + var intersects = ol.extent.segmentIntersects(extent, north, north); + expect(intersects).to.be(false); + }); + + }); + describe('transform', function() { it('does transform', function() { From 50822a4d58119effbb0d05818ab66f38ea22d761 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 24 Feb 2014 16:58:55 -0700 Subject: [PATCH 5/8] Skip coordinates that don't affect the rendered extent Segments that intersect the replay group's extent are drawn. Any segment that represents a change in coordinate-extent relationship is drawn. This maintains the left/right relationship (or cross product) between points in the rendered extent and every rendered segment. Still left undone: clip the replay group's rendering to the max extent. --- src/ol/render/canvas/canvasreplay.js | 35 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index b7a03a87ca..bb1af25405 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -14,6 +14,7 @@ goog.require('ol.BrowserFeature'); goog.require('ol.array'); goog.require('ol.color'); goog.require('ol.extent'); +goog.require('ol.extent.Relationship'); goog.require('ol.geom.flat'); goog.require('ol.geom.simplify'); goog.require('ol.render.IReplayGroup'); @@ -133,12 +134,38 @@ ol.render.canvas.Replay = function(tolerance, maxExtent) { */ ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, close) { + var myEnd = this.coordinates.length; - var i; - for (i = offset; i < end; i += stride) { - this.coordinates[myEnd++] = flatCoordinates[i]; - this.coordinates[myEnd++] = flatCoordinates[i + 1]; + var extent = this.maxExtent; + var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var nextCoord = [NaN, NaN]; + var skipped = true; + + var i, lastRel, nextRel; + for (i = offset + stride; i < end; i += stride) { + nextCoord[0] = flatCoordinates[i]; + nextCoord[1] = flatCoordinates[i + 1]; + nextRel = ol.extent.coordinateRelationship(extent, nextCoord); + if (nextRel !== lastRel) { + if (skipped) { + this.coordinates[myEnd++] = lastCoord[0]; + this.coordinates[myEnd++] = lastCoord[1]; + } + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; + } else if (nextRel === ol.extent.Relationship.INTERSECTING) { + this.coordinates[myEnd++] = nextCoord[0]; + this.coordinates[myEnd++] = nextCoord[1]; + skipped = false; + } else { + skipped = true; + } + lastCoord[0] = nextCoord[0]; + lastCoord[1] = nextCoord[1]; + lastRel = nextRel; } + if (close) { this.coordinates[myEnd++] = flatCoordinates[offset]; this.coordinates[myEnd++] = flatCoordinates[offset + 1]; From 2750f53fda7a3c5f04d36e8a73d472a6b54a99ac Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 24 Feb 2014 22:37:01 -0700 Subject: [PATCH 6/8] Clip to max extent --- src/ol/render/canvas/canvasreplay.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index bb1af25405..84f5bd425d 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1817,6 +1817,15 @@ ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( ol.render.canvas.ReplayGroup.prototype.replay_ = function( zs, context, extent, pixelRatio, transform, viewRotation, renderGeometryFunction) { + + var pixelExtent = ol.geom.flat.transform2D(this.maxExtent_, 2, transform); + var width = pixelExtent[2] - pixelExtent[0]; + var height = pixelExtent[1] - pixelExtent[3]; + context.save(); + context.beginPath(); + context.rect(pixelExtent[0], pixelExtent[3], width, height); + context.clip(); + var i, ii, j, jj, replays, replayType, replay, result; for (i = 0, ii = zs.length; i < ii; ++i) { replays = this.replaysByZIndex_[zs[i].toString()]; @@ -1832,6 +1841,8 @@ ol.render.canvas.ReplayGroup.prototype.replay_ = function( } } } + + context.restore(); return undefined; }; From af1bd0228cbf7bc1711c7677856675380fc29853 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 25 Feb 2014 21:49:05 -0700 Subject: [PATCH 7/8] Function for applying a 2d transform to an extent --- src/ol/extent.js | 35 +++++++++++++++++++++++++++++++ test/spec/ol/extent.test.js | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/ol/extent.js b/src/ol/extent.js index 766beeaa62..ba146637fc 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -3,6 +3,7 @@ goog.provide('ol.extent'); goog.provide('ol.extent.Relationship'); goog.require('goog.asserts'); +goog.require('goog.vec.Mat4'); goog.require('ol.Coordinate'); goog.require('ol.Size'); goog.require('ol.TransformFunction'); @@ -729,3 +730,37 @@ ol.extent.transform = function(extent, transformFn, opt_extent) { var ys = [coordinates[1], coordinates[3], coordinates[5], coordinates[7]]; return ol.extent.boundingExtentXYs_(xs, ys, opt_extent); }; + + +/** + * Apply a 2d transform to an extent. + * @param {ol.Extent} extent Input extent. + * @param {goog.vec.Mat4.Number} transform The transform matrix. + * @param {ol.Extent=} opt_extent Optional extent for return values. + * @return {ol.Extent} The transformed extent. + */ +ol.extent.transform2D = function(extent, transform, opt_extent) { + var dest = goog.isDef(opt_extent) ? opt_extent : []; + var m00 = goog.vec.Mat4.getElement(transform, 0, 0); + var m10 = goog.vec.Mat4.getElement(transform, 1, 0); + var m01 = goog.vec.Mat4.getElement(transform, 0, 1); + var m11 = goog.vec.Mat4.getElement(transform, 1, 1); + var m03 = goog.vec.Mat4.getElement(transform, 0, 3); + var m13 = goog.vec.Mat4.getElement(transform, 1, 3); + var xi = [0, 2, 0, 2]; + var yi = [1, 1, 3, 3]; + var xs = []; + var ys = []; + var i, x, y; + for (i = 0; i < 4; ++i) { + x = extent[xi[i]]; + y = extent[yi[i]]; + xs[i] = m00 * x + m01 * y + m03; + ys[i] = m10 * x + m11 * y + m13; + } + dest[0] = Math.min.apply(null, xs); + dest[1] = Math.min.apply(null, ys); + dest[2] = Math.max.apply(null, xs); + dest[3] = Math.max.apply(null, ys); + return dest; +}; diff --git a/test/spec/ol/extent.test.js b/test/spec/ol/extent.test.js index 52901cff5f..23a21ea675 100644 --- a/test/spec/ol/extent.test.js +++ b/test/spec/ol/extent.test.js @@ -467,9 +467,50 @@ describe('ol.extent', function() { }); + describe('transform2D()', function() { + + var extent; + beforeEach(function() { + extent = [-180, -90, 180, 90]; + }); + + it('applies a translate transform', function() { + var mat = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeTranslate(mat, 10, 20, 0); + var transformed = ol.extent.transform2D(extent, mat); + expect(transformed).to.eql([-170, -70, 190, 110]); + }); + + it('applies a rotate transform', function() { + var mat = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeRotateZ(mat, Math.PI / 2); + var transformed = ol.extent.transform2D(extent, mat); + expect(transformed[0]).to.roughlyEqual(-90, 1e-5); + expect(transformed[1]).to.roughlyEqual(-180, 1e-5); + expect(transformed[2]).to.roughlyEqual(90, 1e-5); + expect(transformed[3]).to.roughlyEqual(180, 1e-5); + }); + + it('does not modify original', function() { + var mat = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeRotateZ(mat, Math.PI / 2); + ol.extent.transform2D(extent, mat); + expect(extent).to.eql([-180, -90, 180, 90]); + }); + + it('accepts an extent to modify', function() { + var mat = goog.vec.Mat4.createNumber(); + goog.vec.Mat4.makeScale(mat, 2, 0.5); + ol.extent.transform2D(extent, mat, extent); + expect(extent).to.eql([-360, -45, 360, 45]); + }); + + }); + }); +goog.require('goog.vec.Mat4'); goog.require('ol.extent'); goog.require('ol.extent.Relationship'); goog.require('ol.proj'); From 50001132be9b31ad8aad10e70778bc843586f0e0 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 25 Feb 2014 21:49:32 -0700 Subject: [PATCH 8/8] Transform extent coordinates before clipping --- src/ol/render/canvas/canvasreplay.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 84f5bd425d..15fef376c8 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1818,12 +1818,20 @@ ol.render.canvas.ReplayGroup.prototype.replay_ = function( zs, context, extent, pixelRatio, transform, viewRotation, renderGeometryFunction) { - var pixelExtent = ol.geom.flat.transform2D(this.maxExtent_, 2, transform); - var width = pixelExtent[2] - pixelExtent[0]; - var height = pixelExtent[1] - pixelExtent[3]; + var maxExtent = this.maxExtent_; + var minX = maxExtent[0]; + var minY = maxExtent[1]; + var maxX = maxExtent[2]; + var maxY = maxExtent[3]; + var flatClipCoords = ol.geom.flat.transform2D( + [minX, minY, minX, maxY, maxX, maxY, maxX, minY], 2, transform); context.save(); context.beginPath(); - context.rect(pixelExtent[0], pixelExtent[3], width, height); + context.moveTo(flatClipCoords[0], flatClipCoords[1]); + context.lineTo(flatClipCoords[2], flatClipCoords[3]); + context.lineTo(flatClipCoords[4], flatClipCoords[5]); + context.lineTo(flatClipCoords[6], flatClipCoords[7]); + context.closePath(); context.clip(); var i, ii, j, jj, replays, replayType, replay, result;