diff --git a/examples/mapbox-vector-tiles-advanced.js b/examples/mapbox-vector-tiles-advanced.js index b960ad8964..9261d52a01 100644 --- a/examples/mapbox-vector-tiles-advanced.js +++ b/examples/mapbox-vector-tiles-advanced.js @@ -50,6 +50,7 @@ var map = new ol.Map({ '© ' + 'OpenStreetMap contributors', format: new ol.format.MVT(), + overlaps: false, tileGrid: new ol.tilegrid.TileGrid({ extent: ol.proj.get('EPSG:3857').getExtent(), resolutions: resolutions diff --git a/examples/mapbox-vector-tiles.js b/examples/mapbox-vector-tiles.js index ed3688bd4e..da9719a18e 100644 --- a/examples/mapbox-vector-tiles.js +++ b/examples/mapbox-vector-tiles.js @@ -21,6 +21,7 @@ var map = new ol.Map({ '© ' + 'OpenStreetMap contributors', format: new ol.format.MVT(), + overlaps: false, tileGrid: ol.tilegrid.createXYZ({maxZoom: 22}), tilePixelRatio: 16, url: 'http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + diff --git a/examples/osm-vector-tiles.js b/examples/osm-vector-tiles.js index bef1e300e1..aef7ca7c87 100644 --- a/examples/osm-vector-tiles.js +++ b/examples/osm-vector-tiles.js @@ -92,6 +92,7 @@ var map = new ol.Map({ new ol.layer.VectorTile({ source: new ol.source.VectorTile({ format: format, + overlaps: false, tileGrid: tileGrid, url: 'http://{a-c}.tile.openstreetmap.us/' + 'vectiles-land-usages/{z}/{x}/{y}.topojson' diff --git a/examples/topojson.js b/examples/topojson.js index 551636788a..2373203887 100644 --- a/examples/topojson.js +++ b/examples/topojson.js @@ -29,7 +29,8 @@ var style = new ol.style.Style({ var vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: 'data/topojson/world-110m.json', - format: new ol.format.TopoJSON() + format: new ol.format.TopoJSON(), + overlaps: false }), style: function(feature) { // don't want to render the full world polygon, which repeats all countries diff --git a/externs/olx.js b/externs/olx.js index 61e8fed4e5..f2711e433d 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4356,6 +4356,7 @@ olx.source.TileImageOptions.prototype.wrapX; * cacheSize: (number|undefined), * format: (ol.format.Feature|undefined), * logo: (string|olx.LogoOptions|undefined), + * overlaps: (boolean|undefined), * projection: ol.ProjectionLike, * state: (ol.source.State|undefined), * tileClass: (function(new: ol.VectorTile, ol.TileCoord, @@ -4405,6 +4406,17 @@ olx.source.VectorTileOptions.prototype.format; olx.source.VectorTileOptions.prototype.logo; +/** + * This source may have overlapping geometries. Default is `true`. Setting this + * to `false` (e.g. for sources with polygons that represent adminstrative + * boundaries or TopoJSON sources) allows the renderer to optimise fill and + * stroke operations. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.overlaps; + + /** * Projection. * @type {ol.ProjectionLike} @@ -5775,6 +5787,7 @@ olx.source.TileWMSOptions.prototype.wrapX; * format: (ol.format.Feature|undefined), * loader: (ol.FeatureLoader|undefined), * logo: (string|olx.LogoOptions|undefined), + * overlaps: (boolean|undefined), * strategy: (ol.LoadingStrategy|undefined), * url: (string|ol.FeatureUrlFunction|undefined), * useSpatialIndex: (boolean|undefined), @@ -5827,6 +5840,17 @@ olx.source.VectorOptions.prototype.loader; olx.source.VectorOptions.prototype.logo; +/** + * This source may have overlapping geometries. Default is `true`. Setting this + * to `false` (e.g. for sources with polygons that represent adminstrative + * boundaries or TopoJSON sources) allows the renderer to optimise fill and + * stroke operations. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorOptions.prototype.overlaps; + + /** * The loading strategy to use. By default an {@link ol.loadingstrategy.all} * strategy is used, a one-off strategy which loads all features at once. diff --git a/src/ol/color/color.js b/src/ol/color/color.js index 84ba8e6694..c322252c37 100644 --- a/src/ol/color/color.js +++ b/src/ol/color/color.js @@ -183,16 +183,6 @@ ol.color.fromStringInternal_ = function(s) { }; -/** - * @param {ol.ColorLike|string} color Color. - * @return {boolean} Is rgba. - */ -ol.color.isRgba = function(color) { - return Array.isArray(color) && color.length == 4 || - typeof color == 'string' && ol.color.rgbaColorRe_.test(color); -}; - - /** * @param {ol.Color} color Color. * @return {boolean} Is valid. diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 20066e4c56..97bba2b460 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -52,10 +52,11 @@ ol.render.canvas.Instruction = { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) { ol.render.VectorContext.call(this); /** @@ -75,7 +76,7 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { * @protected * @type {boolean} */ - this.transparency = false; + this.overlaps = overlaps; /** * @private @@ -266,7 +267,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( var pendingFill = 0; var pendingStroke = 0; var batchSize = - this.instructions != instructions || this.transparency ? 0 : 200; + this.instructions != instructions || this.overlaps ? 0 : 200; while (i < ii) { var instruction = instructions[i]; var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); @@ -691,11 +692,12 @@ ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); +ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) { + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -957,12 +959,13 @@ ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -1191,12 +1194,13 @@ ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = function(fillSt * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, overlaps) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -1457,10 +1461,6 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle var fillStyleColor = fillStyle.getColor(); state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ? fillStyleColor : ol.render.canvas.defaultFillStyle); - if (!this.transparency && ol.color.isRgba(state.fillStyle)) { - this.transparency = ol.color.asArray( - /** @type {ol.Color|string} */ (state.fillStyle))[0] != 1; - } } else { state.fillStyle = undefined; } @@ -1468,9 +1468,6 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle var strokeStyleColor = strokeStyle.getColor(); state.strokeStyle = ol.color.asString(strokeStyleColor ? strokeStyleColor : ol.render.canvas.defaultStrokeStyle); - if (!this.transparency && ol.color.isRgba(state.strokeStyle)) { - this.transparency = ol.color.asArray(state.strokeStyle)[3] != 1; - } var strokeStyleLineCap = strokeStyle.getLineCap(); state.lineCap = strokeStyleLineCap !== undefined ? strokeStyleLineCap : ol.render.canvas.defaultLineCap; @@ -1553,12 +1550,13 @@ ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Maximum extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay can have overlapping geometries. * @protected * @struct */ -ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution) { +ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) { - ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution); + ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps); /** * @private @@ -1862,11 +1860,12 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) { * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. * @param {number} resolution Resolution. + * @param {boolean} overlaps The replay group can have overlapping geometries. * @param {number=} opt_renderBuffer Optional rendering buffer. * @struct */ ol.render.canvas.ReplayGroup = function( - tolerance, maxExtent, resolution, opt_renderBuffer) { + tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) { /** * @private @@ -1880,6 +1879,12 @@ ol.render.canvas.ReplayGroup = function( */ this.maxExtent_ = maxExtent; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = overlaps; + /** * @private * @type {number} @@ -1998,7 +2003,7 @@ ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType) replayType + ' constructor missing from ol.render.canvas.BATCH_CONSTRUCTORS_'); replay = new Constructor(this.tolerance_, this.maxExtent_, - this.resolution_); + this.resolution_, this.overlaps_); replays[replayType] = replay; } return replay; @@ -2112,7 +2117,7 @@ ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function( * @private * @type {Object.} + * number, boolean)>} */ 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 18871e438e..0d9b168dd3 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -264,7 +264,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, lay var replayGroup = new ol.render.canvas.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution, vectorLayer.getRenderBuffer()); + resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer()); vectorSource.loadFeatures(extent, resolution, projection); /** * @param {ol.Feature} feature Feature. diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index d073d947b2..f07a23ed91 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -214,7 +214,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, } replayState.dirty = false; var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, - tileResolution, layer.getRenderBuffer()); + tileResolution, source.getOverlaps(), layer.getRenderBuffer()); var squaredTolerance = ol.renderer.vector.getSquaredTolerance( tileResolution, pixelRatio); diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js index c4b32b9b74..fb1ed4a803 100644 --- a/src/ol/renderer/dom/domvectorlayerrenderer.js +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -269,7 +269,7 @@ ol.renderer.dom.VectorLayer.prototype.prepareFrame = function(frameState, layerS var replayGroup = new ol.render.canvas.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution, vectorLayer.getRenderBuffer()); + resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer()); vectorSource.loadFeatures(extent, resolution, projection); /** * @param {ol.Feature} feature Feature. diff --git a/src/ol/source/imagevectorsource.js b/src/ol/source/imagevectorsource.js index f7e9324523..c20537aaa9 100644 --- a/src/ol/source/imagevectorsource.js +++ b/src/ol/source/imagevectorsource.js @@ -108,7 +108,7 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resol var replayGroup = new ol.render.canvas.ReplayGroup( ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, - resolution); + resolution, this.source_.getOverlaps()); this.source_.loadFeatures(extent, resolution, projection); diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 88f637cea9..260855b31f 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -96,6 +96,12 @@ ol.source.Vector = function(opt_options) { */ this.format_ = options.format; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps || true; + /** * @private * @type {string|ol.FeatureUrlFunction|undefined} @@ -704,6 +710,14 @@ ol.source.Vector.prototype.getFormat = function() { }; +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.Vector.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + /** * Get the url associated with this source. * diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js index b3fc3b5ee3..57cbf3c391 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -51,6 +51,12 @@ ol.source.VectorTile = function(options) { */ this.format_ = options.format ? options.format : null; + /** + * @private + * @type {boolean} + */ + this.overlaps_ = options.overlaps || true; + /** * @protected * @type {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string, @@ -62,6 +68,14 @@ ol.source.VectorTile = function(options) { ol.inherits(ol.source.VectorTile, ol.source.UrlTile); +/** + * @return {boolean} The source can have overlapping geometries. + */ +ol.source.VectorTile.prototype.getOverlaps = function() { + return this.overlaps_; +}; + + /** * @inheritDoc */ diff --git a/test.html b/test.html new file mode 100644 index 0000000000..ab049dd51a --- /dev/null +++ b/test.html @@ -0,0 +1,33 @@ + + + + + + OpenLayers 3 example + + + +

My Map

+
+ + + + diff --git a/test/spec/ol/color.test.js b/test/spec/ol/color.test.js index 12bcbdb3d5..d15e761d15 100644 --- a/test/spec/ol/color.test.js +++ b/test/spec/ol/color.test.js @@ -103,20 +103,6 @@ describe('ol.color', function() { }); - describe('ol.color.isRgba', function() { - it('identifies rgba arrays', function() { - expect(ol.color.isRgba([255, 255, 255, 1])).to.be(true); - expect(ol.color.isRgba([255, 255, 255])).to.be(false); - }); - it('identifies rgba strings', function() { - expect(ol.color.isRgba('rgba(255,255,255,1)')).to.be(true); - expect(ol.color.isRgba('rgb(255,255,255)')).to.be(false); - expect(ol.color.isRgba('#FFF')).to.be(false); - expect(ol.color.isRgba('#FFFFFF')).to.be(false); - expect(ol.color.isRgba('red')).to.be(false); - }); - }); - describe('ol.color.isValid', function() { it('identifies valid colors', function() { diff --git a/test/spec/ol/renderer/canvas/canvasreplay.test.js b/test/spec/ol/renderer/canvas/canvasreplay.test.js index 1f30b88ebc..496e8de250 100644 --- a/test/spec/ol/renderer/canvas/canvasreplay.test.js +++ b/test/spec/ol/renderer/canvas/canvasreplay.test.js @@ -5,11 +5,11 @@ describe('ol.render.canvas.ReplayGroup', function() { describe('#replay', function() { var context, replay, fillCount, strokeCount, beginPathCount; - var feature1, feature2, feature3, style1, style2, style3, transform; + var feature1, feature2, feature3, style1, style2, transform; beforeEach(function() { transform = goog.vec.Mat4.createNumber(); - replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1); + replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1, false); feature1 = new ol.Feature(new ol.geom.Polygon( [[[-90, -45], [-90, 0], [0, 0], [0, -45], [-90, -45]]])); feature2 = new ol.Feature(new ol.geom.Polygon( @@ -24,10 +24,6 @@ describe('ol.render.canvas.ReplayGroup', function() { fill: new ol.style.Fill({color: 'white'}), stroke: new ol.style.Stroke({color: 'black', width: 1}) }); - style3 = new ol.style.Style({ - fill: new ol.style.Fill({color: 'rgba(255,255,255,0.8)'}), - stroke: new ol.style.Stroke({color: 'rgba(0,0,0,0.8)', width: 1}) - }); fillCount = 0; strokeCount = 0; beginPathCount = 0; @@ -121,10 +117,11 @@ describe('ol.render.canvas.ReplayGroup', function() { expect(beginPathCount).to.be(1); }); - it('does not batch when transparent fills/strokes are used', function() { - ol.renderer.vector.renderFeature(replay, feature1, style3, 1); - ol.renderer.vector.renderFeature(replay, feature2, style3, 1); - ol.renderer.vector.renderFeature(replay, feature3, style3, 1); + it('does not batch when overlaps is set to true', function() { + replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1, true); + ol.renderer.vector.renderFeature(replay, feature1, style1, 1); + ol.renderer.vector.renderFeature(replay, feature2, style1, 1); + ol.renderer.vector.renderFeature(replay, feature3, style1, 1); replay.replay(context, 1, transform, 0, {}); expect(fillCount).to.be(3); expect(strokeCount).to.be(3); @@ -141,7 +138,7 @@ describe('ol.render.canvas.Replay', function() { it('creates a new replay batch', function() { var tolerance = 10; var extent = [-180, -90, 180, 90]; - var replay = new ol.render.canvas.Replay(tolerance, extent, 1); + var replay = new ol.render.canvas.Replay(tolerance, extent, 1, true); expect(replay).to.be.a(ol.render.canvas.Replay); }); @@ -151,7 +148,7 @@ describe('ol.render.canvas.Replay', function() { var replay; beforeEach(function() { - replay = new ol.render.canvas.Replay(1, [-180, -90, 180, 90], 1); + replay = new ol.render.canvas.Replay(1, [-180, -90, 180, 90], 1, true); }); it('appends coordinates that are within the max extent', function() { diff --git a/test_rendering/spec/ol/layer/vector.test.js b/test_rendering/spec/ol/layer/vector.test.js index f8e4c6bf8b..94738027d9 100644 --- a/test_rendering/spec/ol/layer/vector.test.js +++ b/test_rendering/spec/ol/layer/vector.test.js @@ -75,6 +75,9 @@ describe('ol.rendering.layer.Vector', function() { it('renders fill/stroke batches correctly with the canvas renderer', function(done) { map = createMap('canvas'); + source = new ol.source.Vector({ + overlaps: false + }); addPolygon(100); addCircle(200); addPolygon(250);