Merge pull request #7328 from ahocevar/declutter
Declutter text and images
@@ -15,6 +15,7 @@ var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
|
|||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
layers: [
|
layers: [
|
||||||
new ol.layer.VectorTile({
|
new ol.layer.VectorTile({
|
||||||
|
declutter: true,
|
||||||
source: new ol.source.VectorTile({
|
source: new ol.source.VectorTile({
|
||||||
attributions: '© <a href="https://www.mapbox.com/map-feedback/">Mapbox</a> ' +
|
attributions: '© <a href="https://www.mapbox.com/map-feedback/">Mapbox</a> ' +
|
||||||
'© <a href="https://www.openstreetmap.org/copyright">' +
|
'© <a href="https://www.openstreetmap.org/copyright">' +
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ var map = new ol.Map({
|
|||||||
imagerySet: 'Aerial'
|
imagerySet: 'Aerial'
|
||||||
})
|
})
|
||||||
}), new ol.layer.Vector({
|
}), new ol.layer.Vector({
|
||||||
|
declutter: true,
|
||||||
source: new ol.source.Vector({
|
source: new ol.source.Vector({
|
||||||
format: new ol.format.GeoJSON(),
|
format: new ol.format.GeoJSON(),
|
||||||
url: 'data/geojson/vienna-streets.geojson'
|
url: 'data/geojson/vienna-streets.geojson'
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ title: Vector Label Decluttering
|
|||||||
shortdesc: Label decluttering with a custom renderer.
|
shortdesc: Label decluttering with a custom renderer.
|
||||||
resources:
|
resources:
|
||||||
- https://cdn.polyfill.io/v2/polyfill.min.js?features=Set"
|
- https://cdn.polyfill.io/v2/polyfill.min.js?features=Set"
|
||||||
- https://unpkg.com/rbush@2.0.1/rbush.min.js
|
|
||||||
- https://unpkg.com/labelgun@0.1.1/lib/labelgun.min.js
|
|
||||||
docs: >
|
docs: >
|
||||||
A custom `renderer` function is used instead of an `ol.style.Text` to label the countries of the world. Only texts that are not wider than their country's bounding box are considered and handed over to [Labelgun](https://github.com/Geovation/labelgun) for decluttering.
|
Decluttering is used to avoid overlapping labels with `exceedLength: true` set on the text style. For MultiPolygon geometries, only the widest polygon is selected in a custom `geometry` function.
|
||||||
tags: "vector, renderer, labelgun, label"
|
tags: "vector, decluttering, labels"
|
||||||
---
|
---
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// NOCOMPILE
|
|
||||||
/* global labelgun */
|
|
||||||
goog.require('ol.Map');
|
goog.require('ol.Map');
|
||||||
goog.require('ol.View');
|
goog.require('ol.View');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
@@ -9,26 +7,7 @@ goog.require('ol.source.Vector');
|
|||||||
goog.require('ol.style.Fill');
|
goog.require('ol.style.Fill');
|
||||||
goog.require('ol.style.Stroke');
|
goog.require('ol.style.Stroke');
|
||||||
goog.require('ol.style.Style');
|
goog.require('ol.style.Style');
|
||||||
|
goog.require('ol.style.Text');
|
||||||
// Style for labels
|
|
||||||
function setStyle(context) {
|
|
||||||
context.font = '12px Calibri,sans-serif';
|
|
||||||
context.fillStyle = '#000';
|
|
||||||
context.strokeStyle = '#fff';
|
|
||||||
context.lineWidth = 3;
|
|
||||||
context.textBaseline = 'hanging';
|
|
||||||
context.textAlign = 'start';
|
|
||||||
}
|
|
||||||
|
|
||||||
// A separate canvas context for measuring label width and height.
|
|
||||||
var textMeasureContext = document.createElement('CANVAS').getContext('2d');
|
|
||||||
setStyle(textMeasureContext);
|
|
||||||
|
|
||||||
// The label height is approximated by the width of the text 'WI'.
|
|
||||||
var height = textMeasureContext.measureText('WI').width;
|
|
||||||
|
|
||||||
// A cache for reusing label images once they have been created.
|
|
||||||
var textCache = {};
|
|
||||||
|
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
target: 'map',
|
target: 'map',
|
||||||
@@ -38,30 +17,35 @@ var map = new ol.Map({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
var emptyFn = function() {};
|
|
||||||
var labelEngine = new labelgun['default'](emptyFn, emptyFn);
|
|
||||||
|
|
||||||
function createLabel(canvas, text, coord) {
|
|
||||||
var halfWidth = canvas.width / 2;
|
|
||||||
var halfHeight = canvas.height / 2;
|
|
||||||
var bounds = {
|
|
||||||
bottomLeft: [Math.round(coord[0] - halfWidth), Math.round(coord[1] - halfHeight)],
|
|
||||||
topRight: [Math.round(coord[0] + halfWidth), Math.round(coord[1] + halfHeight)]
|
|
||||||
};
|
|
||||||
labelEngine.ingestLabel(bounds, coord.toString(), 1, canvas, text, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For multi-polygons, we only label the widest polygon. This is done by sorting
|
|
||||||
// by extent width in descending order, and take the first from the array.
|
|
||||||
function sortByWidth(a, b) {
|
|
||||||
return ol.extent.getWidth(b.getExtent()) - ol.extent.getWidth(a.getExtent());
|
|
||||||
}
|
|
||||||
|
|
||||||
var labelStyle = new ol.style.Style({
|
var labelStyle = new ol.style.Style({
|
||||||
renderer: function(coords, state) {
|
geometry: function(feature) {
|
||||||
var text = state.feature.get('name');
|
var geometry = feature.getGeometry();
|
||||||
createLabel(textCache[text], text, coords);
|
if (geometry.getType() == 'MultiPolygon') {
|
||||||
}
|
// Only render label for the widest polygon of a multipolygon
|
||||||
|
var polygons = geometry.getPolygons();
|
||||||
|
var widest = 0;
|
||||||
|
for (var i = 0, ii = polygons.length; i < ii; ++i) {
|
||||||
|
var polygon = polygons[i];
|
||||||
|
var width = ol.extent.getWidth(polygon.getExtent());
|
||||||
|
if (width > widest) {
|
||||||
|
widest = width;
|
||||||
|
geometry = polygon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return geometry;
|
||||||
|
},
|
||||||
|
text: new ol.style.Text({
|
||||||
|
font: '12px Calibri,sans-serif',
|
||||||
|
exceedLength: true,
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: '#000'
|
||||||
|
}),
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: '#fff',
|
||||||
|
width: 3
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
var countryStyle = new ol.style.Style({
|
var countryStyle = new ol.style.Style({
|
||||||
fill: new ol.style.Fill({
|
fill: new ol.style.Fill({
|
||||||
@@ -72,54 +56,18 @@ var countryStyle = new ol.style.Style({
|
|||||||
width: 1
|
width: 1
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
var styleWithLabel = [countryStyle, labelStyle];
|
var style = [countryStyle, labelStyle];
|
||||||
var styleWithoutLabel = [countryStyle];
|
|
||||||
|
|
||||||
var pixelRatio; // This is set by the map's precompose listener
|
|
||||||
var vectorLayer = new ol.layer.Vector({
|
var vectorLayer = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({
|
source: new ol.source.Vector({
|
||||||
url: 'data/geojson/countries.geojson',
|
url: 'data/geojson/countries.geojson',
|
||||||
format: new ol.format.GeoJSON()
|
format: new ol.format.GeoJSON()
|
||||||
}),
|
}),
|
||||||
style: function(feature, resolution) {
|
style: function(feature) {
|
||||||
var text = feature.get('name');
|
labelStyle.getText().setText(feature.get('name'));
|
||||||
var width = textMeasureContext.measureText(text).width;
|
return style;
|
||||||
var geometry = feature.getGeometry();
|
},
|
||||||
if (geometry.getType() == 'MultiPolygon') {
|
declutter: true
|
||||||
geometry = geometry.getPolygons().sort(sortByWidth)[0];
|
|
||||||
}
|
|
||||||
var extentWidth = ol.extent.getWidth(geometry.getExtent());
|
|
||||||
if (extentWidth / resolution > width) {
|
|
||||||
// Only consider label when it fits its geometry's extent
|
|
||||||
if (!(text in textCache)) {
|
|
||||||
// Draw the label to its own canvas and cache it.
|
|
||||||
var canvas = textCache[text] = document.createElement('CANVAS');
|
|
||||||
canvas.width = width * pixelRatio;
|
|
||||||
canvas.height = height * pixelRatio;
|
|
||||||
var context = canvas.getContext('2d');
|
|
||||||
context.scale(pixelRatio, pixelRatio);
|
|
||||||
setStyle(context);
|
|
||||||
context.strokeText(text, 0, 0);
|
|
||||||
context.fillText(text, 0, 0);
|
|
||||||
}
|
|
||||||
labelStyle.setGeometry(geometry.getInteriorPoint());
|
|
||||||
return styleWithLabel;
|
|
||||||
} else {
|
|
||||||
return styleWithoutLabel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
vectorLayer.on('precompose', function(e) {
|
|
||||||
pixelRatio = e.frameState.pixelRatio;
|
|
||||||
labelEngine.destroy();
|
|
||||||
});
|
|
||||||
vectorLayer.on('postcompose', function(e) {
|
|
||||||
var labels = labelEngine.getShown();
|
|
||||||
for (var i = 0, ii = labels.length; i < ii; ++i) {
|
|
||||||
var label = labels[i];
|
|
||||||
// Draw label to the map canvas
|
|
||||||
e.context.drawImage(label.labelObject, label.minX, label.minY);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
map.addLayer(vectorLayer);
|
map.addLayer(vectorLayer);
|
||||||
|
|||||||
@@ -4250,6 +4250,7 @@ olx.layer.TileOptions.prototype.zIndex;
|
|||||||
* renderBuffer: (number|undefined),
|
* renderBuffer: (number|undefined),
|
||||||
* source: (ol.source.Vector|undefined),
|
* source: (ol.source.Vector|undefined),
|
||||||
* map: (ol.PluggableMap|undefined),
|
* map: (ol.PluggableMap|undefined),
|
||||||
|
* declutter: (boolean|undefined),
|
||||||
* style: (ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined),
|
* style: (ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined),
|
||||||
* updateWhileAnimating: (boolean|undefined),
|
* updateWhileAnimating: (boolean|undefined),
|
||||||
* updateWhileInteracting: (boolean|undefined),
|
* updateWhileInteracting: (boolean|undefined),
|
||||||
@@ -4332,6 +4333,16 @@ olx.layer.VectorOptions.prototype.renderBuffer;
|
|||||||
olx.layer.VectorOptions.prototype.source;
|
olx.layer.VectorOptions.prototype.source;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declutter images and text. Decluttering is applied to all image and text
|
||||||
|
* styles, and the priority is defined by the z-index of the style. Lower
|
||||||
|
* z-index means higher priority. Default is `false`.
|
||||||
|
* @type {boolean|undefined}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.layer.VectorOptions.prototype.declutter;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layer style. See {@link ol.style} for default style which will be used if
|
* Layer style. See {@link ol.style} for default style which will be used if
|
||||||
* this is not defined.
|
* this is not defined.
|
||||||
@@ -4389,6 +4400,7 @@ olx.layer.VectorOptions.prototype.zIndex;
|
|||||||
* renderMode: (ol.layer.VectorTileRenderType|string|undefined),
|
* renderMode: (ol.layer.VectorTileRenderType|string|undefined),
|
||||||
* renderOrder: (ol.RenderOrderFunction|undefined),
|
* renderOrder: (ol.RenderOrderFunction|undefined),
|
||||||
* source: (ol.source.VectorTile|undefined),
|
* source: (ol.source.VectorTile|undefined),
|
||||||
|
* declutter: (boolean|undefined),
|
||||||
* style: (ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined),
|
* style: (ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined),
|
||||||
* updateWhileAnimating: (boolean|undefined),
|
* updateWhileAnimating: (boolean|undefined),
|
||||||
* updateWhileInteracting: (boolean|undefined),
|
* updateWhileInteracting: (boolean|undefined),
|
||||||
@@ -4422,7 +4434,8 @@ olx.layer.VectorTileOptions.prototype.renderBuffer;
|
|||||||
* * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering
|
* * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering
|
||||||
* even during animations, but slower performance than the other options.
|
* even during animations, but slower performance than the other options.
|
||||||
*
|
*
|
||||||
* The default is `'hybrid'`.
|
* When `declutter` is set to `true`, `'hybrid'` will be used instead of
|
||||||
|
* `'image'`. The default is `'hybrid'`.
|
||||||
* @type {ol.layer.VectorTileRenderType|string|undefined}
|
* @type {ol.layer.VectorTileRenderType|string|undefined}
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
@@ -4498,6 +4511,17 @@ olx.layer.VectorTileOptions.prototype.preload;
|
|||||||
olx.layer.VectorTileOptions.prototype.source;
|
olx.layer.VectorTileOptions.prototype.source;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declutter images and text. Decluttering is applied to all image and text
|
||||||
|
* styles, and the priority is defined by the z-index of the style. Lower
|
||||||
|
* z-index means higher priority. When set to `true`, a `renderMode` of
|
||||||
|
* `'image'` will be overridden with `'hybrid'`. Default is `false`.
|
||||||
|
* @type {boolean|undefined}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.layer.VectorTileOptions.prototype.declutter;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layer style. See {@link ol.style} for default style which will be used if
|
* Layer style. See {@link ol.style} for default style which will be used if
|
||||||
* this is not defined.
|
* this is not defined.
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ ol.layer.Vector = function(opt_options) {
|
|||||||
delete baseOptions.updateWhileInteracting;
|
delete baseOptions.updateWhileInteracting;
|
||||||
ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
|
ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.declutter_ = options.declutter !== undefined ? options.declutter : false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @private
|
* @private
|
||||||
@@ -80,6 +86,22 @@ ol.layer.Vector = function(opt_options) {
|
|||||||
ol.inherits(ol.layer.Vector, ol.layer.Layer);
|
ol.inherits(ol.layer.Vector, ol.layer.Layer);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} Declutter.
|
||||||
|
*/
|
||||||
|
ol.layer.Vector.prototype.getDeclutter = function() {
|
||||||
|
return this.declutter_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} declutter Declutter.
|
||||||
|
*/
|
||||||
|
ol.layer.Vector.prototype.setDeclutter = function(declutter) {
|
||||||
|
this.declutter_ = declutter;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {number|undefined} Render buffer.
|
* @return {number|undefined} Render buffer.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -34,17 +34,23 @@ ol.layer.VectorTile = function(opt_options) {
|
|||||||
this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
|
this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
|
||||||
options.useInterimTilesOnError : true);
|
options.useInterimTilesOnError : true);
|
||||||
|
|
||||||
ol.asserts.assert(options.renderMode == undefined ||
|
var renderMode = options.renderMode;
|
||||||
options.renderMode == ol.layer.VectorTileRenderType.IMAGE ||
|
|
||||||
options.renderMode == ol.layer.VectorTileRenderType.HYBRID ||
|
ol.asserts.assert(renderMode == undefined ||
|
||||||
options.renderMode == ol.layer.VectorTileRenderType.VECTOR,
|
renderMode == ol.layer.VectorTileRenderType.IMAGE ||
|
||||||
|
renderMode == ol.layer.VectorTileRenderType.HYBRID ||
|
||||||
|
renderMode == ol.layer.VectorTileRenderType.VECTOR,
|
||||||
28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`
|
28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`
|
||||||
|
|
||||||
|
if (options.declutter && renderMode == ol.layer.VectorTileRenderType.IMAGE) {
|
||||||
|
renderMode = ol.layer.VectorTileRenderType.HYBRID;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {ol.layer.VectorTileRenderType|string}
|
* @type {ol.layer.VectorTileRenderType|string}
|
||||||
*/
|
*/
|
||||||
this.renderMode_ = options.renderMode || ol.layer.VectorTileRenderType.HYBRID;
|
this.renderMode_ = renderMode || ol.layer.VectorTileRenderType.HYBRID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The layer type.
|
* The layer type.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
goog.provide('ol.render.canvas');
|
goog.provide('ol.render.canvas');
|
||||||
|
|
||||||
|
|
||||||
|
goog.require('ol.transform');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const
|
* @const
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@@ -91,3 +94,41 @@ ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY)
|
|||||||
context.translate(-offsetX, -offsetY);
|
context.translate(-offsetX, -offsetY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ol.render.canvas.resetTransform_ = ol.transform.create();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
|
* @param {ol.Transform|null} transform Transform.
|
||||||
|
* @param {number} opacity Opacity.
|
||||||
|
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
|
||||||
|
* @param {number} originX Origin X.
|
||||||
|
* @param {number} originY Origin Y.
|
||||||
|
* @param {number} w Width.
|
||||||
|
* @param {number} h Height.
|
||||||
|
* @param {number} x X.
|
||||||
|
* @param {number} y Y.
|
||||||
|
* @param {number} scale Scale.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.drawImage = function(context,
|
||||||
|
transform, opacity, image, originX, originY, w, h, x, y, scale) {
|
||||||
|
var alpha;
|
||||||
|
if (opacity != 1) {
|
||||||
|
alpha = context.globalAlpha;
|
||||||
|
context.globalAlpha = alpha * opacity;
|
||||||
|
}
|
||||||
|
if (transform) {
|
||||||
|
context.setTransform.apply(context, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
|
||||||
|
|
||||||
|
if (alpha) {
|
||||||
|
context.globalAlpha = alpha;
|
||||||
|
}
|
||||||
|
if (transform) {
|
||||||
|
context.setTransform.apply(context, ol.render.canvas.resetTransform_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,10 +13,19 @@ goog.require('ol.render.canvas.Replay');
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree.
|
||||||
* @struct
|
* @struct
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps) {
|
ol.render.canvas.ImageReplay = function(
|
||||||
ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, pixelRatio, overlaps);
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
||||||
|
ol.render.canvas.Replay.call(this,
|
||||||
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {ol.DeclutterGroup}
|
||||||
|
*/
|
||||||
|
this.declutterGroup_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -130,7 +139,7 @@ ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, featu
|
|||||||
this.instructions.push([
|
this.instructions.push([
|
||||||
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
|
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
|
||||||
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
||||||
this.anchorX_, this.anchorY_, this.height_, this.opacity_,
|
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
|
||||||
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
||||||
this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_
|
this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_
|
||||||
]);
|
]);
|
||||||
@@ -138,7 +147,7 @@ ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, featu
|
|||||||
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
|
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
|
||||||
this.hitDetectionImage_,
|
this.hitDetectionImage_,
|
||||||
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
||||||
this.anchorX_, this.anchorY_, this.height_, this.opacity_,
|
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
|
||||||
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
||||||
this.scale_, this.snapToPixel_, this.width_
|
this.scale_, this.snapToPixel_, this.width_
|
||||||
]);
|
]);
|
||||||
@@ -162,7 +171,7 @@ ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeome
|
|||||||
this.instructions.push([
|
this.instructions.push([
|
||||||
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
|
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
|
||||||
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
||||||
this.anchorX_, this.anchorY_, this.height_, this.opacity_,
|
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
|
||||||
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
||||||
this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_
|
this.scale_ * this.pixelRatio, this.snapToPixel_, this.width_
|
||||||
]);
|
]);
|
||||||
@@ -170,7 +179,7 @@ ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeome
|
|||||||
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
|
ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
|
||||||
this.hitDetectionImage_,
|
this.hitDetectionImage_,
|
||||||
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
// Remaining arguments to DRAW_IMAGE are in alphabetical order
|
||||||
this.anchorX_, this.anchorY_, this.height_, this.opacity_,
|
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
|
||||||
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
|
||||||
this.scale_, this.snapToPixel_, this.width_
|
this.scale_, this.snapToPixel_, this.width_
|
||||||
]);
|
]);
|
||||||
@@ -203,7 +212,7 @@ ol.render.canvas.ImageReplay.prototype.finish = function() {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
|
ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle, declutterGroup) {
|
||||||
var anchor = imageStyle.getAnchor();
|
var anchor = imageStyle.getAnchor();
|
||||||
var size = imageStyle.getSize();
|
var size = imageStyle.getSize();
|
||||||
var hitDetectionImage = imageStyle.getHitDetectionImage(1);
|
var hitDetectionImage = imageStyle.getHitDetectionImage(1);
|
||||||
@@ -211,6 +220,7 @@ ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
|
|||||||
var origin = imageStyle.getOrigin();
|
var origin = imageStyle.getOrigin();
|
||||||
this.anchorX_ = anchor[0];
|
this.anchorX_ = anchor[0];
|
||||||
this.anchorY_ = anchor[1];
|
this.anchorY_ = anchor[1];
|
||||||
|
this.declutterGroup_ = /** @type {ol.DeclutterGroup} */ (declutterGroup);
|
||||||
this.hitDetectionImage_ = hitDetectionImage;
|
this.hitDetectionImage_ = hitDetectionImage;
|
||||||
this.image_ = image;
|
this.image_ = image;
|
||||||
this.height_ = size[1];
|
this.height_ = size[1];
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ goog.require('ol.render.canvas.Replay');
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree.
|
||||||
* @struct
|
* @struct
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps) {
|
ol.render.canvas.LineStringReplay = function(
|
||||||
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
||||||
ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, pixelRatio, overlaps);
|
ol.render.canvas.Replay.call(this,
|
||||||
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ goog.require('ol.render.canvas.Replay');
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree.
|
||||||
* @struct
|
* @struct
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps) {
|
ol.render.canvas.PolygonReplay = function(
|
||||||
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
||||||
ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, pixelRatio, overlaps);
|
ol.render.canvas.Replay.call(this,
|
||||||
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ goog.require('ol.geom.flat.transform');
|
|||||||
goog.require('ol.has');
|
goog.require('ol.has');
|
||||||
goog.require('ol.obj');
|
goog.require('ol.obj');
|
||||||
goog.require('ol.render.VectorContext');
|
goog.require('ol.render.VectorContext');
|
||||||
|
goog.require('ol.render.canvas');
|
||||||
goog.require('ol.render.canvas.Instruction');
|
goog.require('ol.render.canvas.Instruction');
|
||||||
goog.require('ol.transform');
|
goog.require('ol.transform');
|
||||||
|
|
||||||
@@ -24,11 +25,23 @@ goog.require('ol.transform');
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree.
|
||||||
* @struct
|
* @struct
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps) {
|
ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
||||||
ol.render.VectorContext.call(this);
|
ol.render.VectorContext.call(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {?}
|
||||||
|
*/
|
||||||
|
this.declutterTree = declutterTree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {ol.Extent}
|
||||||
|
*/
|
||||||
|
this.tmpExtent_ = ol.extent.createEmpty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -127,12 +140,6 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, pixelRatio,
|
|||||||
*/
|
*/
|
||||||
this.tmpLocalTransform_ = ol.transform.create();
|
this.tmpLocalTransform_ = ol.transform.create();
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {!ol.Transform}
|
|
||||||
*/
|
|
||||||
this.resetTransform_ = ol.transform.create();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {Array.<Array.<number>>}
|
* @type {Array.<Array.<number>>}
|
||||||
@@ -149,6 +156,7 @@ ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);
|
|||||||
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
|
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
|
||||||
* @param {number} anchorX Anchor X.
|
* @param {number} anchorX Anchor X.
|
||||||
* @param {number} anchorY Anchor Y.
|
* @param {number} anchorY Anchor Y.
|
||||||
|
* @param {ol.DeclutterGroup} declutterGroup Declutter group.
|
||||||
* @param {number} height Height.
|
* @param {number} height Height.
|
||||||
* @param {number} opacity Opacity.
|
* @param {number} opacity Opacity.
|
||||||
* @param {number} originX Origin X.
|
* @param {number} originX Origin X.
|
||||||
@@ -159,7 +167,7 @@ ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);
|
|||||||
* @param {number} width Width.
|
* @param {number} width Width.
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, anchorX, anchorY,
|
ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image, anchorX, anchorY,
|
||||||
height, opacity, originX, originY, rotation, scale, snapToPixel, width) {
|
declutterGroup, height, opacity, originX, originY, rotation, scale, snapToPixel, width) {
|
||||||
var localTransform = this.tmpLocalTransform_;
|
var localTransform = this.tmpLocalTransform_;
|
||||||
anchorX *= scale;
|
anchorX *= scale;
|
||||||
anchorY *= scale;
|
anchorY *= scale;
|
||||||
@@ -169,28 +177,37 @@ ol.render.canvas.Replay.prototype.replayImage_ = function(context, x, y, image,
|
|||||||
x = Math.round(x);
|
x = Math.round(x);
|
||||||
y = Math.round(y);
|
y = Math.round(y);
|
||||||
}
|
}
|
||||||
if (rotation !== 0) {
|
|
||||||
var centerX = x + anchorX;
|
|
||||||
var centerY = y + anchorY;
|
|
||||||
ol.transform.compose(localTransform,
|
|
||||||
centerX, centerY, 1, 1, rotation, -centerX, -centerY);
|
|
||||||
context.setTransform.apply(context, localTransform);
|
|
||||||
}
|
|
||||||
var alpha = context.globalAlpha;
|
|
||||||
if (opacity != 1) {
|
|
||||||
context.globalAlpha = alpha * opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
var w = (width + originX > image.width) ? image.width - originX : width;
|
var w = (width + originX > image.width) ? image.width - originX : width;
|
||||||
var h = (height + originY > image.height) ? image.height - originY : height;
|
var h = (height + originY > image.height) ? image.height - originY : height;
|
||||||
|
var box = this.tmpExtent_;
|
||||||
|
|
||||||
context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
|
var transform = null;
|
||||||
|
|
||||||
if (opacity != 1) {
|
|
||||||
context.globalAlpha = alpha;
|
|
||||||
}
|
|
||||||
if (rotation !== 0) {
|
if (rotation !== 0) {
|
||||||
context.setTransform.apply(context, this.resetTransform_);
|
var centerX = x + anchorX;
|
||||||
|
var centerY = y + anchorY;
|
||||||
|
transform = ol.transform.compose(localTransform,
|
||||||
|
centerX, centerY, 1, 1, rotation, -centerX, -centerY);
|
||||||
|
ol.extent.createOrUpdateEmpty(box);
|
||||||
|
ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x, y]));
|
||||||
|
ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x + w, y]));
|
||||||
|
ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x + w, y + h]));
|
||||||
|
ol.extent.extendCoordinate(box, ol.transform.apply(localTransform, [x, y + w]));
|
||||||
|
} else {
|
||||||
|
ol.extent.createOrUpdate(x, y, x + w * scale, y + h * scale, box);
|
||||||
|
}
|
||||||
|
var canvas = context.canvas;
|
||||||
|
var intersects = box[0] <= canvas.width && box[2] >= 0 && box[1] <= canvas.height && box[3] >= 0;
|
||||||
|
if (declutterGroup) {
|
||||||
|
if (!intersects && declutterGroup[4] == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ol.extent.extend(declutterGroup, box);
|
||||||
|
declutterGroup.push(intersects ?
|
||||||
|
[context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] :
|
||||||
|
null);
|
||||||
|
} else if (intersects) {
|
||||||
|
ol.render.canvas.drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -360,7 +377,37 @@ ol.render.canvas.Replay.prototype.fill_ = function(context, rotation) {
|
|||||||
}
|
}
|
||||||
context.fill();
|
context.fill();
|
||||||
if (this.fillOrigin_) {
|
if (this.fillOrigin_) {
|
||||||
context.setTransform.apply(context, this.resetTransform_);
|
context.setTransform.apply(context, ol.render.canvas.resetTransform_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ol.DeclutterGroup} declutterGroup Declutter group.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.Replay.prototype.renderDeclutter_ = function(declutterGroup) {
|
||||||
|
if (declutterGroup && declutterGroup.length > 5) {
|
||||||
|
var groupCount = declutterGroup[4];
|
||||||
|
if (groupCount == 1 || groupCount == declutterGroup.length - 5) {
|
||||||
|
/** @type {ol.RBushEntry} */
|
||||||
|
var box = {
|
||||||
|
minX: /** @type {number} */ (declutterGroup[0]),
|
||||||
|
minY: /** @type {number} */ (declutterGroup[1]),
|
||||||
|
maxX: /** @type {number} */ (declutterGroup[2]),
|
||||||
|
maxY: /** @type {number} */ (declutterGroup[3])
|
||||||
|
};
|
||||||
|
if (!this.declutterTree.collides(box)) {
|
||||||
|
this.declutterTree.insert(box);
|
||||||
|
var drawImage = ol.render.canvas.drawImage;
|
||||||
|
for (var j = 5, jj = declutterGroup.length; j < jj; ++j) {
|
||||||
|
if (declutterGroup[j]) {
|
||||||
|
drawImage.apply(undefined, /** @type {Array} */ (declutterGroup[j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declutterGroup.length = 5;
|
||||||
|
ol.extent.createOrUpdateEmpty(declutterGroup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -401,7 +448,7 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
var ii = instructions.length; // end of instructions
|
var ii = instructions.length; // end of instructions
|
||||||
var d = 0; // data index
|
var d = 0; // data index
|
||||||
var dd; // end of per-instruction data
|
var dd; // end of per-instruction data
|
||||||
var anchorX, anchorY, prevX, prevY, roundX, roundY;
|
var anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup;
|
||||||
var pendingFill = 0;
|
var pendingFill = 0;
|
||||||
var pendingStroke = 0;
|
var pendingStroke = 0;
|
||||||
var coordinateCache = this.coordinateCache_;
|
var coordinateCache = this.coordinateCache_;
|
||||||
@@ -497,24 +544,26 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
// Remaining arguments in DRAW_IMAGE are in alphabetical order
|
// Remaining arguments in DRAW_IMAGE are in alphabetical order
|
||||||
anchorX = /** @type {number} */ (instruction[4]);
|
anchorX = /** @type {number} */ (instruction[4]);
|
||||||
anchorY = /** @type {number} */ (instruction[5]);
|
anchorY = /** @type {number} */ (instruction[5]);
|
||||||
var height = /** @type {number} */ (instruction[6]);
|
declutterGroup = /** @type {ol.DeclutterGroup} */ (instruction[6]);
|
||||||
var opacity = /** @type {number} */ (instruction[7]);
|
var height = /** @type {number} */ (instruction[7]);
|
||||||
var originX = /** @type {number} */ (instruction[8]);
|
var opacity = /** @type {number} */ (instruction[8]);
|
||||||
var originY = /** @type {number} */ (instruction[9]);
|
var originX = /** @type {number} */ (instruction[9]);
|
||||||
var rotateWithView = /** @type {boolean} */ (instruction[10]);
|
var originY = /** @type {number} */ (instruction[10]);
|
||||||
var rotation = /** @type {number} */ (instruction[11]);
|
var rotateWithView = /** @type {boolean} */ (instruction[11]);
|
||||||
var scale = /** @type {number} */ (instruction[12]);
|
var rotation = /** @type {number} */ (instruction[12]);
|
||||||
var snapToPixel = /** @type {boolean} */ (instruction[13]);
|
var scale = /** @type {number} */ (instruction[13]);
|
||||||
var width = /** @type {number} */ (instruction[14]);
|
var snapToPixel = /** @type {boolean} */ (instruction[14]);
|
||||||
|
var width = /** @type {number} */ (instruction[15]);
|
||||||
|
|
||||||
if (rotateWithView) {
|
if (rotateWithView) {
|
||||||
rotation += viewRotation;
|
rotation += viewRotation;
|
||||||
}
|
}
|
||||||
for (; d < dd; d += 2) {
|
for (; d < dd; d += 2) {
|
||||||
this.replayImage_(context, pixelCoordinates[d], pixelCoordinates[d + 1],
|
this.replayImage_(context,
|
||||||
image, anchorX, anchorY, height, opacity, originX, originY,
|
pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
|
||||||
rotation, scale, snapToPixel, width);
|
declutterGroup, height, opacity, originX, originY, rotation, scale, snapToPixel, width);
|
||||||
}
|
}
|
||||||
|
this.renderDeclutter_(declutterGroup);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.DRAW_CHARS:
|
case ol.render.canvas.Instruction.DRAW_CHARS:
|
||||||
@@ -523,13 +572,14 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
var images = /** @type {Array.<HTMLCanvasElement>} */ (instruction[3]);
|
var images = /** @type {Array.<HTMLCanvasElement>} */ (instruction[3]);
|
||||||
// Remaining arguments in DRAW_CHARS are in alphabetical order
|
// Remaining arguments in DRAW_CHARS are in alphabetical order
|
||||||
var baseline = /** @type {number} */ (instruction[4]);
|
var baseline = /** @type {number} */ (instruction[4]);
|
||||||
var exceedLength = /** @type {number} */ (instruction[5]);
|
declutterGroup = /** @type {ol.DeclutterGroup} */ (instruction[5]);
|
||||||
var maxAngle = /** @type {number} */ (instruction[6]);
|
var exceedLength = /** @type {number} */ (instruction[6]);
|
||||||
var measure = /** @type {function(string):number} */ (instruction[7]);
|
var maxAngle = /** @type {number} */ (instruction[7]);
|
||||||
var offsetY = /** @type {number} */ (instruction[8]);
|
var measure = /** @type {function(string):number} */ (instruction[8]);
|
||||||
var text = /** @type {string} */ (instruction[9]);
|
var offsetY = /** @type {number} */ (instruction[9]);
|
||||||
var align = /** @type {number} */ (instruction[10]);
|
var text = /** @type {string} */ (instruction[10]);
|
||||||
var textScale = /** @type {number} */ (instruction[11]);
|
var align = /** @type {number} */ (instruction[11]);
|
||||||
|
var textScale = /** @type {number} */ (instruction[12]);
|
||||||
|
|
||||||
var pathLength = ol.geom.flat.length.lineString(pixelCoordinates, begin, end, 2);
|
var pathLength = ol.geom.flat.length.lineString(pixelCoordinates, begin, end, 2);
|
||||||
var textLength = measure(text);
|
var textLength = measure(text);
|
||||||
@@ -545,12 +595,12 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
var label = images[c];
|
var label = images[c];
|
||||||
anchorX = label.width / 2;
|
anchorX = label.width / 2;
|
||||||
anchorY = baseline * label.height + (0.5 - baseline) * (label.height - fillHeight) - offsetY;
|
anchorY = baseline * label.height + (0.5 - baseline) * (label.height - fillHeight) - offsetY;
|
||||||
this.replayImage_(context, char[0], char[1], label, anchorX, anchorY,
|
this.replayImage_(context, char[0], char[1], label,
|
||||||
label.height, 1, 0, 0, char[2], textScale, false, label.width);
|
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0, char[2], textScale, false, label.width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.renderDeclutter_(declutterGroup);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.END_GEOMETRY:
|
case ol.render.canvas.Instruction.END_GEOMETRY:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ goog.require('ol.extent');
|
|||||||
goog.require('ol.geom.flat.transform');
|
goog.require('ol.geom.flat.transform');
|
||||||
goog.require('ol.obj');
|
goog.require('ol.obj');
|
||||||
goog.require('ol.render.ReplayGroup');
|
goog.require('ol.render.ReplayGroup');
|
||||||
|
goog.require('ol.render.ReplayType');
|
||||||
goog.require('ol.render.canvas.Replay');
|
goog.require('ol.render.canvas.Replay');
|
||||||
goog.require('ol.render.canvas.ImageReplay');
|
goog.require('ol.render.canvas.ImageReplay');
|
||||||
goog.require('ol.render.canvas.LineStringReplay');
|
goog.require('ol.render.canvas.LineStringReplay');
|
||||||
@@ -24,13 +25,27 @@ goog.require('ol.transform');
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay group can have overlapping geometries.
|
* @param {boolean} overlaps The replay group can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree
|
||||||
|
* for declutter processing in postrender.
|
||||||
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
* @param {number=} opt_renderBuffer Optional rendering buffer.
|
||||||
* @struct
|
* @struct
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup = function(
|
ol.render.canvas.ReplayGroup = function(
|
||||||
tolerance, maxExtent, resolution, pixelRatio, overlaps, opt_renderBuffer) {
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree, opt_renderBuffer) {
|
||||||
ol.render.ReplayGroup.call(this);
|
ol.render.ReplayGroup.call(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declutter tree.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterTree_ = declutterTree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ol.DeclutterGroup}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterGroup_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -170,6 +185,71 @@ ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ol.render.canvas.ReplayGroup.replayDeclutter = function(declutterReplays, context, rotation) {
|
||||||
|
var zs = Object.keys(declutterReplays).map(Number).sort(ol.array.numberSafeCompareFunction);
|
||||||
|
for (var z = 0, zz = zs.length; z < zz; ++z) {
|
||||||
|
var replayData = declutterReplays[zs[z].toString()];
|
||||||
|
for (var i = 0, ii = replayData.length; i < ii;) {
|
||||||
|
var replay = replayData[i++];
|
||||||
|
var transform = replayData[i++];
|
||||||
|
replay.replay(context, transform, rotation, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ol.render.canvas.ReplayGroup.replayDeclutterHitDetection = function(
|
||||||
|
declutterReplays, context, rotation, featureCallback, hitExtent) {
|
||||||
|
var zs = Object.keys(declutterReplays).map(Number).sort(ol.array.numberSafeCompareFunction);
|
||||||
|
for (var z = 0, zz = zs.length; z < zz; ++z) {
|
||||||
|
var replayData = declutterReplays[zs[z].toString()];
|
||||||
|
for (var i = replayData.length - 1; i >= 0;) {
|
||||||
|
var transform = replayData[i--];
|
||||||
|
var replay = replayData[i--];
|
||||||
|
var result = replay.replayHitDetection(context, transform, rotation, {},
|
||||||
|
featureCallback, hitExtent);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} group Group with previous replay.
|
||||||
|
* @return {ol.DeclutterGroup} Declutter instruction group.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.ReplayGroup.prototype.addDeclutter = function(group) {
|
||||||
|
var declutter = null;
|
||||||
|
if (this.declutterTree_) {
|
||||||
|
if (group) {
|
||||||
|
declutter = this.declutterGroup_;
|
||||||
|
/** @type {number} */ (declutter[4])++;
|
||||||
|
} else {
|
||||||
|
declutter = this.declutterGroup_ = ol.extent.createEmpty();
|
||||||
|
declutter.push(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return declutter;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CanvasRenderingContext2D} context Context.
|
||||||
|
* @param {ol.Transform} transform Transform.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.ReplayGroup.prototype.clip = function(context, transform) {
|
||||||
|
var flatClipCoords = this.getClipCoords(transform);
|
||||||
|
context.beginPath();
|
||||||
|
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.clip();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array.<ol.render.ReplayType>} replays Replays.
|
* @param {Array.<ol.render.ReplayType>} replays Replays.
|
||||||
* @return {boolean} Has replays of the provided types.
|
* @return {boolean} Has replays of the provided types.
|
||||||
@@ -211,11 +291,13 @@ ol.render.canvas.ReplayGroup.prototype.finish = function() {
|
|||||||
* to skip.
|
* to skip.
|
||||||
* @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
|
* @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
|
||||||
* callback.
|
* callback.
|
||||||
|
* @param {Object.<string, ol.DeclutterGroup>} declutterReplays Declutter
|
||||||
|
* replays.
|
||||||
* @return {T|undefined} Callback result.
|
* @return {T|undefined} Callback result.
|
||||||
* @template T
|
* @template T
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
|
ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
|
||||||
coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback) {
|
coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback, declutterReplays) {
|
||||||
|
|
||||||
hitTolerance = Math.round(hitTolerance);
|
hitTolerance = Math.round(hitTolerance);
|
||||||
var contextSize = hitTolerance * 2 + 1;
|
var contextSize = hitTolerance * 2 + 1;
|
||||||
@@ -245,30 +327,36 @@ ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
|
|||||||
|
|
||||||
var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance);
|
var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance);
|
||||||
|
|
||||||
return this.replayHitDetection_(context, transform, rotation,
|
/**
|
||||||
skippedFeaturesHash,
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
/**
|
* @return {?} Callback result.
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
*/
|
||||||
* @return {?} Callback result.
|
function hitDetectionCallback(feature) {
|
||||||
*/
|
var imageData = context.getImageData(0, 0, contextSize, contextSize).data;
|
||||||
function(feature) {
|
for (var i = 0; i < contextSize; i++) {
|
||||||
var imageData = context.getImageData(0, 0, contextSize, contextSize).data;
|
for (var j = 0; j < contextSize; j++) {
|
||||||
for (var i = 0; i < contextSize; i++) {
|
if (mask[i][j]) {
|
||||||
for (var j = 0; j < contextSize; j++) {
|
if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
|
||||||
if (mask[i][j]) {
|
var result = callback(feature);
|
||||||
if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
|
if (result) {
|
||||||
var result = callback(feature);
|
return result;
|
||||||
if (result) {
|
} else {
|
||||||
return result;
|
context.clearRect(0, 0, contextSize, contextSize);
|
||||||
} else {
|
return undefined;
|
||||||
context.clearRect(0, 0, contextSize, contextSize);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, hitExtent);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = this.replayHitDetection_(context, transform, rotation,
|
||||||
|
skippedFeaturesHash, hitDetectionCallback, hitExtent, declutterReplays);
|
||||||
|
if (!result && declutterReplays) {
|
||||||
|
result = ol.render.canvas.ReplayGroup.replayDeclutterHitDetection(
|
||||||
|
declutterReplays, context, rotation, hitDetectionCallback, hitExtent);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -303,13 +391,21 @@ ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType)
|
|||||||
if (replay === undefined) {
|
if (replay === undefined) {
|
||||||
var Constructor = ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_[replayType];
|
var Constructor = ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_[replayType];
|
||||||
replay = new Constructor(this.tolerance_, this.maxExtent_,
|
replay = new Constructor(this.tolerance_, this.maxExtent_,
|
||||||
this.resolution_, this.pixelRatio_, this.overlaps_);
|
this.resolution_, this.pixelRatio_, this.overlaps_, this.declutterTree_);
|
||||||
replays[replayType] = replay;
|
replays[replayType] = replay;
|
||||||
}
|
}
|
||||||
return replay;
|
return replay;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Object.<string, Object.<ol.render.ReplayType, ol.render.canvas.Replay>>} Replays.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.ReplayGroup.prototype.getReplays = function() {
|
||||||
|
return this.replaysByZIndex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@@ -326,9 +422,11 @@ ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
|
|||||||
* to skip.
|
* to skip.
|
||||||
* @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types
|
* @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types
|
||||||
* to replay. Default is {@link ol.render.replay.ORDER}
|
* to replay. Default is {@link ol.render.replay.ORDER}
|
||||||
|
* @param {Object.<string, ol.DeclutterGroup>=} opt_declutterReplays Declutter
|
||||||
|
* replays.
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup.prototype.replay = function(context,
|
ol.render.canvas.ReplayGroup.prototype.replay = function(context,
|
||||||
transform, viewRotation, skippedFeaturesHash, opt_replayTypes) {
|
transform, viewRotation, skippedFeaturesHash, opt_replayTypes, opt_declutterReplays) {
|
||||||
|
|
||||||
/** @type {Array.<number>} */
|
/** @type {Array.<number>} */
|
||||||
var zs = Object.keys(this.replaysByZIndex_).map(Number);
|
var zs = Object.keys(this.replaysByZIndex_).map(Number);
|
||||||
@@ -336,23 +434,29 @@ ol.render.canvas.ReplayGroup.prototype.replay = function(context,
|
|||||||
|
|
||||||
// setup clipping so that the parts of over-simplified geometries are not
|
// setup clipping so that the parts of over-simplified geometries are not
|
||||||
// visible outside the current extent when panning
|
// visible outside the current extent when panning
|
||||||
var flatClipCoords = this.getClipCoords(transform);
|
|
||||||
context.save();
|
context.save();
|
||||||
context.beginPath();
|
this.clip(context, transform);
|
||||||
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.clip();
|
|
||||||
|
|
||||||
var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER;
|
var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER;
|
||||||
var i, ii, j, jj, replays, replay;
|
var i, ii, j, jj, replays, replay;
|
||||||
for (i = 0, ii = zs.length; i < ii; ++i) {
|
for (i = 0, ii = zs.length; i < ii; ++i) {
|
||||||
replays = this.replaysByZIndex_[zs[i].toString()];
|
var zIndexKey = zs[i].toString();
|
||||||
|
replays = this.replaysByZIndex_[zIndexKey];
|
||||||
for (j = 0, jj = replayTypes.length; j < jj; ++j) {
|
for (j = 0, jj = replayTypes.length; j < jj; ++j) {
|
||||||
replay = replays[replayTypes[j]];
|
var replayType = replayTypes[j];
|
||||||
|
replay = replays[replayType];
|
||||||
if (replay !== undefined) {
|
if (replay !== undefined) {
|
||||||
replay.replay(context, transform, viewRotation, skippedFeaturesHash);
|
if (opt_declutterReplays &&
|
||||||
|
(replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) {
|
||||||
|
var declutter = opt_declutterReplays[zIndexKey];
|
||||||
|
if (!declutter) {
|
||||||
|
opt_declutterReplays[zIndexKey] = [replay, transform.slice(0)];
|
||||||
|
} else {
|
||||||
|
declutter.push(replay, transform.slice(0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replay.replay(context, transform, viewRotation, skippedFeaturesHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,28 +476,40 @@ ol.render.canvas.ReplayGroup.prototype.replay = function(context,
|
|||||||
* Feature callback.
|
* Feature callback.
|
||||||
* @param {ol.Extent=} opt_hitExtent Only check features that intersect this
|
* @param {ol.Extent=} opt_hitExtent Only check features that intersect this
|
||||||
* extent.
|
* extent.
|
||||||
|
* @param {Object.<string, ol.DeclutterGroup>=} opt_declutterReplays Declutter
|
||||||
|
* replays.
|
||||||
* @return {T|undefined} Callback result.
|
* @return {T|undefined} Callback result.
|
||||||
* @template T
|
* @template T
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
|
ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
|
||||||
context, transform, viewRotation, skippedFeaturesHash,
|
context, transform, viewRotation, skippedFeaturesHash,
|
||||||
featureCallback, opt_hitExtent) {
|
featureCallback, opt_hitExtent, opt_declutterReplays) {
|
||||||
/** @type {Array.<number>} */
|
/** @type {Array.<number>} */
|
||||||
var zs = Object.keys(this.replaysByZIndex_).map(Number);
|
var zs = Object.keys(this.replaysByZIndex_).map(Number);
|
||||||
zs.sort(function(a, b) {
|
zs.sort(ol.array.numberSafeCompareFunction);
|
||||||
return b - a;
|
|
||||||
});
|
|
||||||
|
|
||||||
var i, ii, j, replays, replay, result;
|
var i, j, replays, replay, result;
|
||||||
for (i = 0, ii = zs.length; i < ii; ++i) {
|
for (i = zs.length - 1; i >= 0; --i) {
|
||||||
replays = this.replaysByZIndex_[zs[i].toString()];
|
var zIndexKey = zs[i].toString();
|
||||||
|
replays = this.replaysByZIndex_[zIndexKey];
|
||||||
for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) {
|
for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) {
|
||||||
replay = replays[ol.render.replay.ORDER[j]];
|
var replayType = ol.render.replay.ORDER[j];
|
||||||
|
replay = replays[replayType];
|
||||||
if (replay !== undefined) {
|
if (replay !== undefined) {
|
||||||
result = replay.replayHitDetection(context, transform, viewRotation,
|
if (opt_declutterReplays &&
|
||||||
skippedFeaturesHash, featureCallback, opt_hitExtent);
|
(replayType == ol.render.ReplayType.IMAGE || replayType == ol.render.ReplayType.TEXT)) {
|
||||||
if (result) {
|
var declutter = opt_declutterReplays[zIndexKey];
|
||||||
return result;
|
if (!declutter) {
|
||||||
|
opt_declutterReplays[zIndexKey] = [replay, transform.slice(0)];
|
||||||
|
} else {
|
||||||
|
declutter.push(replay, transform.slice(0));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = replay.replayHitDetection(context, transform, viewRotation,
|
||||||
|
skippedFeaturesHash, featureCallback, opt_hitExtent);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,7 +523,7 @@ ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
|
|||||||
* @private
|
* @private
|
||||||
* @type {Object.<ol.render.ReplayType,
|
* @type {Object.<ol.render.ReplayType,
|
||||||
* function(new: ol.render.canvas.Replay, number, ol.Extent,
|
* function(new: ol.render.canvas.Replay, number, ol.Extent,
|
||||||
* number, number, boolean)>}
|
* number, number, boolean, Array.<ol.DeclutterGroup>)>}
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
|
ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
|
||||||
'Circle': ol.render.canvas.PolygonReplay,
|
'Circle': ol.render.canvas.PolygonReplay,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ goog.provide('ol.render.canvas.TextReplay');
|
|||||||
goog.require('ol');
|
goog.require('ol');
|
||||||
goog.require('ol.colorlike');
|
goog.require('ol.colorlike');
|
||||||
goog.require('ol.dom');
|
goog.require('ol.dom');
|
||||||
|
goog.require('ol.extent');
|
||||||
goog.require('ol.geom.flat.straightchunk');
|
goog.require('ol.geom.flat.straightchunk');
|
||||||
goog.require('ol.geom.GeometryType');
|
goog.require('ol.geom.GeometryType');
|
||||||
goog.require('ol.has');
|
goog.require('ol.has');
|
||||||
@@ -22,11 +23,19 @@ goog.require('ol.style.TextPlacement');
|
|||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
* @param {boolean} overlaps The replay can have overlapping geometries.
|
* @param {boolean} overlaps The replay can have overlapping geometries.
|
||||||
|
* @param {?} declutterTree Declutter tree.
|
||||||
* @struct
|
* @struct
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, pixelRatio, overlaps) {
|
ol.render.canvas.TextReplay = function(
|
||||||
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree) {
|
||||||
|
ol.render.canvas.Replay.call(this,
|
||||||
|
tolerance, maxExtent, resolution, pixelRatio, overlaps, declutterTree);
|
||||||
|
|
||||||
ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, pixelRatio, overlaps);
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {ol.DeclutterGroup}
|
||||||
|
*/
|
||||||
|
this.declutterGroup_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -127,18 +136,22 @@ ol.render.canvas.TextReplay.labelCache_ = new ol.structs.LRUCache();
|
|||||||
*/
|
*/
|
||||||
ol.render.canvas.TextReplay.measureTextHeight = (function() {
|
ol.render.canvas.TextReplay.measureTextHeight = (function() {
|
||||||
var span;
|
var span;
|
||||||
return function(font, lines, widths) {
|
var heights = {};
|
||||||
if (!span) {
|
return function(font) {
|
||||||
span = document.createElement('span');
|
var height = heights[font];
|
||||||
span.textContent = 'M';
|
if (height == undefined) {
|
||||||
span.style.margin = span.style.padding = '0 !important';
|
if (!span) {
|
||||||
span.style.position = 'absolute !important';
|
span = document.createElement('span');
|
||||||
span.style.left = '-99999px !important';
|
span.textContent = 'M';
|
||||||
|
span.style.margin = span.style.padding = '0 !important';
|
||||||
|
span.style.position = 'absolute !important';
|
||||||
|
span.style.left = '-99999px !important';
|
||||||
|
}
|
||||||
|
span.style.font = font;
|
||||||
|
document.body.appendChild(span);
|
||||||
|
height = heights[font] = span.offsetHeight;
|
||||||
|
document.body.removeChild(span);
|
||||||
}
|
}
|
||||||
span.style.font = font;
|
|
||||||
document.body.appendChild(span);
|
|
||||||
var height = span.offsetHeight;
|
|
||||||
document.body.removeChild(span);
|
|
||||||
return height;
|
return height;
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@@ -207,6 +220,9 @@ ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
|
|||||||
var i, ii;
|
var i, ii;
|
||||||
|
|
||||||
if (this.textState_.placement === ol.style.TextPlacement.LINE) {
|
if (this.textState_.placement === ol.style.TextPlacement.LINE) {
|
||||||
|
if (!ol.extent.intersects(this.getBufferedMaxExtent(), geometry.getExtent())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var ends;
|
var ends;
|
||||||
flatCoordinates = geometry.getFlatCoordinates();
|
flatCoordinates = geometry.getFlatCoordinates();
|
||||||
stride = geometry.getStride();
|
stride = geometry.getStride();
|
||||||
@@ -236,9 +252,12 @@ ol.render.canvas.TextReplay.prototype.drawText = function(geometry, feature) {
|
|||||||
} else {
|
} else {
|
||||||
flatEnd = ends[o];
|
flatEnd = ends[o];
|
||||||
}
|
}
|
||||||
end = this.appendFlatCoordinates(flatCoordinates, flatOffset, flatEnd, stride, false, false);
|
for (i = flatOffset; i < flatEnd; i += stride) {
|
||||||
|
this.coordinates.push(flatCoordinates[i], flatCoordinates[i + 1]);
|
||||||
|
}
|
||||||
|
end = this.coordinates.length;
|
||||||
flatOffset = ends[o];
|
flatOffset = ends[o];
|
||||||
this.drawChars_(begin, end);
|
this.drawChars_(begin, end, this.declutterGroup_);
|
||||||
begin = end;
|
begin = end;
|
||||||
}
|
}
|
||||||
this.endGeometry(geometry, feature);
|
this.endGeometry(geometry, feature);
|
||||||
@@ -303,8 +322,6 @@ ol.render.canvas.TextReplay.prototype.getImage_ = function(text, fill, stroke) {
|
|||||||
var label;
|
var label;
|
||||||
var key = (stroke ? this.strokeKey_ : '') + this.textKey_ + text + (fill ? this.fillKey_ : '');
|
var key = (stroke ? this.strokeKey_ : '') + this.textKey_ + text + (fill ? this.fillKey_ : '');
|
||||||
|
|
||||||
var lines = text.split('\n');
|
|
||||||
var numLines = lines.length;
|
|
||||||
if (!ol.render.canvas.TextReplay.labelCache_.containsKey(key)) {
|
if (!ol.render.canvas.TextReplay.labelCache_.containsKey(key)) {
|
||||||
var strokeState = this.textStrokeState_;
|
var strokeState = this.textStrokeState_;
|
||||||
var fillState = this.textFillState_;
|
var fillState = this.textFillState_;
|
||||||
@@ -314,6 +331,8 @@ ol.render.canvas.TextReplay.prototype.getImage_ = function(text, fill, stroke) {
|
|||||||
var align = ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
|
var align = ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
|
||||||
var strokeWidth = stroke && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
var strokeWidth = stroke && strokeState.lineWidth ? strokeState.lineWidth : 0;
|
||||||
|
|
||||||
|
var lines = text.split('\n');
|
||||||
|
var numLines = lines.length;
|
||||||
var widths = [];
|
var widths = [];
|
||||||
var width = ol.render.canvas.TextReplay.measureTextWidths(textState.font, lines, widths);
|
var width = ol.render.canvas.TextReplay.measureTextWidths(textState.font, lines, widths);
|
||||||
var lineHeight = ol.render.canvas.TextReplay.measureTextHeight(textState.font);
|
var lineHeight = ol.render.canvas.TextReplay.measureTextHeight(textState.font);
|
||||||
@@ -332,7 +351,7 @@ ol.render.canvas.TextReplay.prototype.getImage_ = function(text, fill, stroke) {
|
|||||||
context.lineCap = strokeState.lineCap;
|
context.lineCap = strokeState.lineCap;
|
||||||
context.lineJoin = strokeState.lineJoin;
|
context.lineJoin = strokeState.lineJoin;
|
||||||
context.miterLimit = strokeState.miterLimit;
|
context.miterLimit = strokeState.miterLimit;
|
||||||
if (ol.has.CANVAS_LINE_DASH) {
|
if (ol.has.CANVAS_LINE_DASH && strokeState.lineDash.length) {
|
||||||
context.setLineDash(strokeState.lineDash);
|
context.setLineDash(strokeState.lineDash);
|
||||||
context.lineDashOffset = strokeState.lineDashOffset;
|
context.lineDashOffset = strokeState.lineDashOffset;
|
||||||
}
|
}
|
||||||
@@ -378,12 +397,12 @@ ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(label, begin, en
|
|||||||
var anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
|
var anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
|
||||||
this.instructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
|
this.instructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
|
||||||
label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
|
label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
|
||||||
label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
|
this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
|
||||||
1, true, label.width
|
1, true, label.width
|
||||||
]);
|
]);
|
||||||
this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
|
this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_IMAGE, begin, end,
|
||||||
label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
|
label, (anchorX - this.textOffsetX_) * pixelRatio, (anchorY - this.textOffsetY_) * pixelRatio,
|
||||||
label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
|
this.declutterGroup_, label.height, 1, 0, 0, this.textRotateWithView_, this.textRotation_,
|
||||||
1 / pixelRatio, true, label.width
|
1 / pixelRatio, true, label.width
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@@ -393,8 +412,9 @@ ol.render.canvas.TextReplay.prototype.drawTextImage_ = function(label, begin, en
|
|||||||
* @private
|
* @private
|
||||||
* @param {number} begin Begin.
|
* @param {number} begin Begin.
|
||||||
* @param {number} end End.
|
* @param {number} end End.
|
||||||
|
* @param {ol.DeclutterGroup} declutterGroup Declutter group.
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end) {
|
ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end, declutterGroup) {
|
||||||
var pixelRatio = this.pixelRatio;
|
var pixelRatio = this.pixelRatio;
|
||||||
var strokeState = this.textStrokeState_;
|
var strokeState = this.textStrokeState_;
|
||||||
var fill = !!this.textFillState_;
|
var fill = !!this.textFillState_;
|
||||||
@@ -423,13 +443,13 @@ ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end) {
|
|||||||
var align = ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
|
var align = ol.render.replay.TEXT_ALIGN[textState.textAlign || ol.render.canvas.defaultTextAlign];
|
||||||
var widths = {};
|
var widths = {};
|
||||||
this.instructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
|
this.instructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
|
||||||
begin, end, labels, baseline,
|
begin, end, labels, baseline, declutterGroup,
|
||||||
textState.exceedLength, textState.maxAngle,
|
textState.exceedLength, textState.maxAngle,
|
||||||
ol.render.canvas.TextReplay.getTextWidth.bind(widths, context, pixelRatio * this.textScale_),
|
ol.render.canvas.TextReplay.getTextWidth.bind(widths, context, pixelRatio * this.textScale_),
|
||||||
offsetY, this.text_, align, 1
|
offsetY, this.text_, align, 1
|
||||||
]);
|
]);
|
||||||
this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
|
this.hitDetectionInstructions.push([ol.render.canvas.Instruction.DRAW_CHARS,
|
||||||
begin, end, labels, baseline,
|
begin, end, labels, baseline, declutterGroup,
|
||||||
textState.exceedLength, textState.maxAngle,
|
textState.exceedLength, textState.maxAngle,
|
||||||
ol.render.canvas.TextReplay.getTextWidth.bind(widths, context, this.textScale_),
|
ol.render.canvas.TextReplay.getTextWidth.bind(widths, context, this.textScale_),
|
||||||
offsetY, this.text_, align, 1 / pixelRatio
|
offsetY, this.text_, align, 1 / pixelRatio
|
||||||
@@ -440,11 +460,12 @@ ol.render.canvas.TextReplay.prototype.drawChars_ = function(begin, end) {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
|
ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle, declutterGroup) {
|
||||||
var textState, fillState, strokeState;
|
var textState, fillState, strokeState;
|
||||||
if (!textStyle) {
|
if (!textStyle) {
|
||||||
this.text_ = '';
|
this.text_ = '';
|
||||||
} else {
|
} else {
|
||||||
|
this.declutterGroup_ = /** @type {ol.DeclutterGroup} */ (declutterGroup);
|
||||||
var textFillStyle = textStyle.getFill();
|
var textFillStyle = textStyle.getFill();
|
||||||
if (!textFillStyle) {
|
if (!textFillStyle) {
|
||||||
fillState = this.textFillState_ = null;
|
fillState = this.textFillState_ = null;
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
goog.provide('ol.render.Feature');
|
goog.provide('ol.render.Feature');
|
||||||
|
|
||||||
goog.require('ol');
|
goog.require('ol');
|
||||||
|
goog.require('ol.array');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
goog.require('ol.geom.GeometryType');
|
goog.require('ol.geom.GeometryType');
|
||||||
|
goog.require('ol.geom.flat.center');
|
||||||
|
goog.require('ol.geom.flat.interiorpoint');
|
||||||
|
goog.require('ol.geom.flat.interpolate');
|
||||||
goog.require('ol.geom.flat.transform');
|
goog.require('ol.geom.flat.transform');
|
||||||
goog.require('ol.transform');
|
goog.require('ol.transform');
|
||||||
|
|
||||||
@@ -45,6 +49,18 @@ ol.render.Feature = function(type, flatCoordinates, ends, properties, id) {
|
|||||||
*/
|
*/
|
||||||
this.flatCoordinates_ = flatCoordinates;
|
this.flatCoordinates_ = flatCoordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array.<number>}
|
||||||
|
*/
|
||||||
|
this.flatInteriorPoints_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Array.<number>}
|
||||||
|
*/
|
||||||
|
this.flatMidpoints_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {Array.<number>|Array.<Array.<number>>}
|
* @type {Array.<number>|Array.<Array.<number>>}
|
||||||
@@ -102,6 +118,66 @@ ol.render.Feature.prototype.getExtent = function() {
|
|||||||
return this.extent_;
|
return this.extent_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array.<number>} Flat interior points.
|
||||||
|
*/
|
||||||
|
ol.render.Feature.prototype.getFlatInteriorPoint = function() {
|
||||||
|
if (!this.flatInteriorPoints_) {
|
||||||
|
var flatCenter = ol.extent.getCenter(this.getExtent());
|
||||||
|
this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRings(
|
||||||
|
this.flatCoordinates_, 0, this.ends_, 2, flatCenter, 0);
|
||||||
|
}
|
||||||
|
return this.flatInteriorPoints_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array.<number>} Flat interior points.
|
||||||
|
*/
|
||||||
|
ol.render.Feature.prototype.getFlatInteriorPoints = function() {
|
||||||
|
if (!this.flatInteriorPoints_) {
|
||||||
|
var flatCenters = ol.geom.flat.center.linearRingss(
|
||||||
|
this.flatCoordinates_, 0, this.ends_, 2);
|
||||||
|
this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss(
|
||||||
|
this.flatCoordinates_, 0, this.ends_, 2, flatCenters);
|
||||||
|
}
|
||||||
|
return this.flatInteriorPoints_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array.<number>} Flat midpoint.
|
||||||
|
*/
|
||||||
|
ol.render.Feature.prototype.getFlatMidpoint = function() {
|
||||||
|
if (!this.flatMidpoints_) {
|
||||||
|
this.flatMidpoints_ = ol.geom.flat.interpolate.lineString(
|
||||||
|
this.flatCoordinates_, 0, this.flatCoordinates_.length, 2, 0.5);
|
||||||
|
}
|
||||||
|
return this.flatMidpoints_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array.<number>} Flat midpoints.
|
||||||
|
*/
|
||||||
|
ol.render.Feature.prototype.getFlatMidpoints = function() {
|
||||||
|
if (!this.flatMidpoints_) {
|
||||||
|
this.flatMidpoints_ = [];
|
||||||
|
var flatCoordinates = this.flatCoordinates_;
|
||||||
|
var offset = 0;
|
||||||
|
var ends = this.ends_;
|
||||||
|
for (var i = 0, ii = ends.length; i < ii; ++i) {
|
||||||
|
var end = ends[i];
|
||||||
|
var midpoint = ol.geom.flat.interpolate.lineString(
|
||||||
|
flatCoordinates, offset, end, 2, 0.5);
|
||||||
|
ol.array.extend(this.flatMidpoints_, midpoint);
|
||||||
|
offset = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.flatMidpoints_;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the feature identifier. This is a stable identifier for the feature and
|
* Get the feature identifier. This is a stable identifier for the feature and
|
||||||
* is set when reading data from a remote source.
|
* is set when reading data from a remote source.
|
||||||
|
|||||||
@@ -123,11 +123,13 @@ ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strok
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ol.style.Image} imageStyle Image style.
|
* @param {ol.style.Image} imageStyle Image style.
|
||||||
|
* @param {ol.DeclutterGroup=} opt_declutterGroup Declutter.
|
||||||
*/
|
*/
|
||||||
ol.render.VectorContext.prototype.setImageStyle = function(imageStyle) {};
|
ol.render.VectorContext.prototype.setImageStyle = function(imageStyle, opt_declutterGroup) {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ol.style.Text} textStyle Text style.
|
* @param {ol.style.Text} textStyle Text style.
|
||||||
|
* @param {ol.DeclutterGroup=} opt_declutterGroup Declutter.
|
||||||
*/
|
*/
|
||||||
ol.render.VectorContext.prototype.setTextStyle = function(textStyle) {};
|
ol.render.VectorContext.prototype.setTextStyle = function(textStyle, opt_declutterGroup) {};
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) {
|
|||||||
ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);
|
ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ol.style.Style} style Style.
|
||||||
|
* @param {boolean} group Group with previous replay.
|
||||||
|
*/
|
||||||
|
ol.render.webgl.ReplayGroup.prototype.addDeclutter = function(style, group) {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ol.webgl.Context} context WebGL context.
|
* @param {ol.webgl.Context} context WebGL context.
|
||||||
* @return {function()} Delete resources function.
|
* @return {function()} Delete resources function.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ goog.require('ol');
|
|||||||
goog.require('ol.LayerType');
|
goog.require('ol.LayerType');
|
||||||
goog.require('ol.ViewHint');
|
goog.require('ol.ViewHint');
|
||||||
goog.require('ol.dom');
|
goog.require('ol.dom');
|
||||||
|
goog.require('ol.ext.rbush');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
goog.require('ol.render.EventType');
|
goog.require('ol.render.EventType');
|
||||||
goog.require('ol.render.canvas');
|
goog.require('ol.render.canvas');
|
||||||
@@ -23,6 +24,13 @@ ol.renderer.canvas.VectorLayer = function(vectorLayer) {
|
|||||||
|
|
||||||
ol.renderer.canvas.Layer.call(this, vectorLayer);
|
ol.renderer.canvas.Layer.call(this, vectorLayer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declutter tree.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterTree_ = vectorLayer.getDeclutter() ?
|
||||||
|
ol.ext.rbush(9) : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -209,6 +217,9 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, lay
|
|||||||
if (clipped) {
|
if (clipped) {
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
if (this.declutterTree_) {
|
||||||
|
this.declutterTree_.clear();
|
||||||
|
}
|
||||||
this.postCompose(context, frameState, layerState, transform);
|
this.postCompose(context, frameState, layerState, transform);
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -223,10 +234,11 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(c
|
|||||||
} else {
|
} else {
|
||||||
var resolution = frameState.viewState.resolution;
|
var resolution = frameState.viewState.resolution;
|
||||||
var rotation = frameState.viewState.rotation;
|
var rotation = frameState.viewState.rotation;
|
||||||
var layer = this.getLayer();
|
var layer = /** @type {ol.layer.Vector} */ (this.getLayer());
|
||||||
/** @type {Object.<string, boolean>} */
|
/** @type {Object.<string, boolean>} */
|
||||||
var features = {};
|
var features = {};
|
||||||
return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
|
var declutterReplays = layer.getDeclutter() ? {} : null;
|
||||||
|
var result = this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
|
||||||
rotation, hitTolerance, {},
|
rotation, hitTolerance, {},
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
@@ -238,7 +250,11 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(c
|
|||||||
features[key] = true;
|
features[key] = true;
|
||||||
return callback.call(thisArg, feature, layer);
|
return callback.call(thisArg, feature, layer);
|
||||||
}
|
}
|
||||||
});
|
}, declutterReplays);
|
||||||
|
if (this.declutterTree_) {
|
||||||
|
this.declutterTree_.clear();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -317,7 +333,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, lay
|
|||||||
|
|
||||||
var replayGroup = new ol.render.canvas.ReplayGroup(
|
var replayGroup = new ol.render.canvas.ReplayGroup(
|
||||||
ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, resolution,
|
ol.renderer.vector.getTolerance(resolution, pixelRatio), extent, resolution,
|
||||||
pixelRatio, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer());
|
pixelRatio, vectorSource.getOverlaps(), this.declutterTree_, vectorLayer.getRenderBuffer());
|
||||||
vectorSource.loadFeatures(extent, resolution, projection);
|
vectorSource.loadFeatures(extent, resolution, projection);
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature} feature Feature.
|
* @param {ol.Feature} feature Feature.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ goog.require('ol');
|
|||||||
goog.require('ol.LayerType');
|
goog.require('ol.LayerType');
|
||||||
goog.require('ol.TileState');
|
goog.require('ol.TileState');
|
||||||
goog.require('ol.dom');
|
goog.require('ol.dom');
|
||||||
|
goog.require('ol.ext.rbush');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
goog.require('ol.layer.VectorTileRenderType');
|
goog.require('ol.layer.VectorTileRenderType');
|
||||||
goog.require('ol.proj');
|
goog.require('ol.proj');
|
||||||
@@ -33,6 +34,12 @@ ol.renderer.canvas.VectorTileLayer = function(layer) {
|
|||||||
|
|
||||||
ol.renderer.canvas.TileLayer.call(this, layer);
|
ol.renderer.canvas.TileLayer.call(this, layer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declutter tree.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterTree_ = layer.getDeclutter() ? ol.ext.rbush(9) : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -54,7 +61,6 @@ ol.renderer.canvas.VectorTileLayer = function(layer) {
|
|||||||
// Use lower resolution for pure vector rendering. Closest resolution otherwise.
|
// Use lower resolution for pure vector rendering. Closest resolution otherwise.
|
||||||
this.zDirection =
|
this.zDirection =
|
||||||
layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0;
|
layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
|
ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
|
||||||
|
|
||||||
@@ -150,6 +156,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
|
|||||||
var resolution = tileGrid.getResolution(tile.tileCoord[0]);
|
var resolution = tileGrid.getResolution(tile.tileCoord[0]);
|
||||||
var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
|
||||||
|
|
||||||
|
var zIndexKeys = {};
|
||||||
for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
||||||
var sourceTile = tile.getTile(tile.tileKeys[t]);
|
var sourceTile = tile.getTile(tile.tileKeys[t]);
|
||||||
if (sourceTile.getState() == ol.TileState.ERROR) {
|
if (sourceTile.getState() == ol.TileState.ERROR) {
|
||||||
@@ -159,6 +166,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
|
|||||||
var sourceTileCoord = sourceTile.tileCoord;
|
var sourceTileCoord = sourceTile.tileCoord;
|
||||||
var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
|
var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
|
||||||
var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent);
|
var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent);
|
||||||
|
var bufferedExtent = ol.extent.equals(sourceTileExtent, sharedExtent) ? null :
|
||||||
|
ol.extent.buffer(sharedExtent, layer.getRenderBuffer() * resolution);
|
||||||
var tileProjection = sourceTile.getProjection();
|
var tileProjection = sourceTile.getProjection();
|
||||||
var reproject = false;
|
var reproject = false;
|
||||||
if (!ol.proj.equivalent(projection, tileProjection)) {
|
if (!ol.proj.equivalent(projection, tileProjection)) {
|
||||||
@@ -166,8 +175,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
|
|||||||
sourceTile.setProjection(projection);
|
sourceTile.setProjection(projection);
|
||||||
}
|
}
|
||||||
replayState.dirty = false;
|
replayState.dirty = false;
|
||||||
var replayGroup = new ol.render.canvas.ReplayGroup(0, sharedExtent,
|
var replayGroup = new ol.render.canvas.ReplayGroup(0, sharedExtent, resolution,
|
||||||
resolution, pixelRatio, source.getOverlaps(), layer.getRenderBuffer());
|
pixelRatio, source.getOverlaps(), this.declutterTree_, layer.getRenderBuffer());
|
||||||
var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
|
var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
|
||||||
resolution, pixelRatio);
|
resolution, pixelRatio);
|
||||||
|
|
||||||
@@ -210,9 +219,14 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
|
|||||||
}
|
}
|
||||||
feature.getGeometry().transform(tileProjection, projection);
|
feature.getGeometry().transform(tileProjection, projection);
|
||||||
}
|
}
|
||||||
renderFeature.call(this, feature);
|
if (!bufferedExtent || ol.extent.intersects(bufferedExtent, feature.getExtent())) {
|
||||||
|
renderFeature.call(this, feature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
replayGroup.finish();
|
replayGroup.finish();
|
||||||
|
for (var r in replayGroup.getReplays()) {
|
||||||
|
zIndexKeys[r] = true;
|
||||||
|
}
|
||||||
sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), replayGroup);
|
sourceTile.setReplayGroup(layer, tile.tileCoord.toString(), replayGroup);
|
||||||
}
|
}
|
||||||
replayState.renderedRevision = revision;
|
replayState.renderedRevision = revision;
|
||||||
@@ -242,6 +256,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
|
|||||||
var rotation = frameState.viewState.rotation;
|
var rotation = frameState.viewState.rotation;
|
||||||
hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
|
hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
|
||||||
var layer = this.getLayer();
|
var layer = this.getLayer();
|
||||||
|
var declutterReplays = layer.getDeclutter() ? {} : null;
|
||||||
/** @type {Object.<string, boolean>} */
|
/** @type {Object.<string, boolean>} */
|
||||||
var features = {};
|
var features = {};
|
||||||
|
|
||||||
@@ -279,9 +294,12 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
|
|||||||
features[key] = true;
|
features[key] = true;
|
||||||
return callback.call(thisArg, feature, layer);
|
return callback.call(thisArg, feature, layer);
|
||||||
}
|
}
|
||||||
});
|
}, declutterReplays);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.declutterTree_) {
|
||||||
|
this.declutterTree_.clear();
|
||||||
|
}
|
||||||
return found;
|
return found;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -331,14 +349,16 @@ ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(
|
|||||||
*/
|
*/
|
||||||
ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) {
|
ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) {
|
||||||
var layer = this.getLayer();
|
var layer = this.getLayer();
|
||||||
|
var declutterReplays = layer.getDeclutter() ? {} : null;
|
||||||
var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
|
var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
|
||||||
var renderMode = layer.getRenderMode();
|
var renderMode = layer.getRenderMode();
|
||||||
var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
|
var replayTypes = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
|
||||||
var pixelRatio = frameState.pixelRatio;
|
var pixelRatio = frameState.pixelRatio;
|
||||||
var rotation = frameState.viewState.rotation;
|
var rotation = frameState.viewState.rotation;
|
||||||
var size = frameState.size;
|
var size = frameState.size;
|
||||||
var offsetX = Math.round(pixelRatio * size[0] / 2);
|
var offsetX = Math.round(pixelRatio * size[0] / 2);
|
||||||
var offsetY = Math.round(pixelRatio * size[1] / 2);
|
var offsetY = Math.round(pixelRatio * size[1] / 2);
|
||||||
|
ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
|
||||||
var tiles = this.renderedTiles;
|
var tiles = this.renderedTiles;
|
||||||
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
||||||
var clips = [];
|
var clips = [];
|
||||||
@@ -351,21 +371,23 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra
|
|||||||
var tileCoord = tile.tileCoord;
|
var tileCoord = tile.tileCoord;
|
||||||
var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
|
var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
|
||||||
tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
|
tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
|
||||||
|
var transform = undefined;
|
||||||
for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
||||||
var sourceTile = tile.getTile(tile.tileKeys[t]);
|
var sourceTile = tile.getTile(tile.tileKeys[t]);
|
||||||
if (sourceTile.getState() == ol.TileState.ERROR) {
|
if (sourceTile.getState() == ol.TileState.ERROR) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString());
|
var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString());
|
||||||
if (renderMode != ol.layer.VectorTileRenderType.VECTOR && !replayGroup.hasReplays(replays)) {
|
if (renderMode != ol.layer.VectorTileRenderType.VECTOR && !replayGroup.hasReplays(replayTypes)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!transform) {
|
||||||
|
transform = this.getTransform(frameState, worldOffset);
|
||||||
|
}
|
||||||
var currentZ = sourceTile.tileCoord[0];
|
var currentZ = sourceTile.tileCoord[0];
|
||||||
var transform = this.getTransform(frameState, worldOffset);
|
|
||||||
var currentClip = replayGroup.getClipCoords(transform);
|
var currentClip = replayGroup.getClipCoords(transform);
|
||||||
context.save();
|
context.save();
|
||||||
context.globalAlpha = layerState.opacity;
|
context.globalAlpha = layerState.opacity;
|
||||||
ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
|
|
||||||
// Create a clip mask for regions in this low resolution tile that are
|
// Create a clip mask for regions in this low resolution tile that are
|
||||||
// already filled by a higher resolution tile
|
// already filled by a higher resolution tile
|
||||||
for (var j = 0, jj = clips.length; j < jj; ++j) {
|
for (var j = 0, jj = clips.length; j < jj; ++j) {
|
||||||
@@ -385,12 +407,16 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra
|
|||||||
context.clip();
|
context.clip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replayGroup.replay(context, transform, rotation, {}, replays);
|
replayGroup.replay(context, transform, rotation, {}, replayTypes, declutterReplays);
|
||||||
context.restore();
|
context.restore();
|
||||||
clips.push(currentClip);
|
clips.push(currentClip);
|
||||||
zs.push(currentZ);
|
zs.push(currentZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (declutterReplays) {
|
||||||
|
ol.render.canvas.ReplayGroup.replayDeclutter(declutterReplays, context, rotation);
|
||||||
|
this.declutterTree_.clear();
|
||||||
|
}
|
||||||
ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
|
ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -458,7 +484,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
|
|||||||
ol.transform.scale(transform, pixelScale, -pixelScale);
|
ol.transform.scale(transform, pixelScale, -pixelScale);
|
||||||
ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
|
ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
|
||||||
var replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString());
|
var replayGroup = sourceTile.getReplayGroup(layer, tile.tileCoord.toString());
|
||||||
replayGroup.replay(context, transform, 0, {}, replays, true);
|
replayGroup.replay(context, transform, 0, {}, replays);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style
|
|||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
var textReplay = replayGroup.getReplay(
|
var textReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.TEXT);
|
style.getZIndex(), ol.render.ReplayType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle);
|
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -180,7 +180,7 @@ ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, s
|
|||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
var textReplay = replayGroup.getReplay(
|
var textReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.TEXT);
|
style.getZIndex(), ol.render.ReplayType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle);
|
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -205,7 +205,7 @@ ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geomet
|
|||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
var textReplay = replayGroup.getReplay(
|
var textReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.TEXT);
|
style.getZIndex(), ol.render.ReplayType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle);
|
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -231,7 +231,7 @@ ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry,
|
|||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
var textReplay = replayGroup.getReplay(
|
var textReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.TEXT);
|
style.getZIndex(), ol.render.ReplayType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle);
|
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -252,14 +252,14 @@ ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style,
|
|||||||
}
|
}
|
||||||
var imageReplay = replayGroup.getReplay(
|
var imageReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.IMAGE);
|
style.getZIndex(), ol.render.ReplayType.IMAGE);
|
||||||
imageReplay.setImageStyle(imageStyle);
|
imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false));
|
||||||
imageReplay.drawPoint(geometry, feature);
|
imageReplay.drawPoint(geometry, feature);
|
||||||
}
|
}
|
||||||
var textStyle = style.getText();
|
var textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
var textReplay = replayGroup.getReplay(
|
var textReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.TEXT);
|
style.getZIndex(), ol.render.ReplayType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle);
|
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -280,14 +280,14 @@ ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, s
|
|||||||
}
|
}
|
||||||
var imageReplay = replayGroup.getReplay(
|
var imageReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.IMAGE);
|
style.getZIndex(), ol.render.ReplayType.IMAGE);
|
||||||
imageReplay.setImageStyle(imageStyle);
|
imageReplay.setImageStyle(imageStyle, replayGroup.addDeclutter(false));
|
||||||
imageReplay.drawMultiPoint(geometry, feature);
|
imageReplay.drawMultiPoint(geometry, feature);
|
||||||
}
|
}
|
||||||
var textStyle = style.getText();
|
var textStyle = style.getText();
|
||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
var textReplay = replayGroup.getReplay(
|
var textReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.TEXT);
|
style.getZIndex(), ol.render.ReplayType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle);
|
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(!!imageStyle));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -313,7 +313,7 @@ ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, styl
|
|||||||
if (textStyle) {
|
if (textStyle) {
|
||||||
var textReplay = replayGroup.getReplay(
|
var textReplay = replayGroup.getReplay(
|
||||||
style.getZIndex(), ol.render.ReplayType.TEXT);
|
style.getZIndex(), ol.render.ReplayType.TEXT);
|
||||||
textReplay.setTextStyle(textStyle);
|
textReplay.setTextStyle(textStyle, replayGroup.addDeclutter(false));
|
||||||
textReplay.drawText(geometry, feature);
|
textReplay.drawText(geometry, feature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ goog.require('ol');
|
|||||||
goog.require('ol.dom');
|
goog.require('ol.dom');
|
||||||
goog.require('ol.events');
|
goog.require('ol.events');
|
||||||
goog.require('ol.events.EventType');
|
goog.require('ol.events.EventType');
|
||||||
|
goog.require('ol.ext.rbush');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
goog.require('ol.render.canvas.ReplayGroup');
|
goog.require('ol.render.canvas.ReplayGroup');
|
||||||
goog.require('ol.renderer.vector');
|
goog.require('ol.renderer.vector');
|
||||||
@@ -55,6 +56,12 @@ ol.source.ImageVector = function(options) {
|
|||||||
*/
|
*/
|
||||||
this.canvasSize_ = [0, 0];
|
this.canvasSize_ = [0, 0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declutter tree.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.declutterTree_ = ol.ext.rbush(9);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -113,7 +120,7 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resol
|
|||||||
|
|
||||||
var replayGroup = new ol.render.canvas.ReplayGroup(
|
var replayGroup = new ol.render.canvas.ReplayGroup(
|
||||||
ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
|
ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
|
||||||
resolution, pixelRatio, this.source_.getOverlaps(), this.renderBuffer_);
|
resolution, pixelRatio, this.source_.getOverlaps(), this.declutterTree_, this.renderBuffer_);
|
||||||
|
|
||||||
this.source_.loadFeatures(extent, resolution, projection);
|
this.source_.loadFeatures(extent, resolution, projection);
|
||||||
|
|
||||||
@@ -146,6 +153,7 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resol
|
|||||||
replayGroup.replay(this.canvasContext_, transform, 0, {});
|
replayGroup.replay(this.canvasContext_, transform, 0, {});
|
||||||
|
|
||||||
this.replayGroup_ = replayGroup;
|
this.replayGroup_ = replayGroup;
|
||||||
|
this.declutterTree_.clear();
|
||||||
|
|
||||||
return this.canvasContext_.canvas;
|
return this.canvasContext_.canvas;
|
||||||
};
|
};
|
||||||
@@ -161,7 +169,7 @@ ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
|
|||||||
} else {
|
} else {
|
||||||
/** @type {Object.<string, boolean>} */
|
/** @type {Object.<string, boolean>} */
|
||||||
var features = {};
|
var features = {};
|
||||||
return this.replayGroup_.forEachFeatureAtCoordinate(
|
var result = this.replayGroup_.forEachFeatureAtCoordinate(
|
||||||
coordinate, resolution, 0, hitTolerance, skippedFeatureUids,
|
coordinate, resolution, 0, hitTolerance, skippedFeatureUids,
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
@@ -173,7 +181,9 @@ ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
|
|||||||
features[key] = true;
|
features[key] = true;
|
||||||
return callback(feature);
|
return callback(feature);
|
||||||
}
|
}
|
||||||
});
|
}, null);
|
||||||
|
this.declutterTree_.clear();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,20 @@ ol.Coordinate;
|
|||||||
ol.CoordinateFormatType;
|
ol.CoordinateFormatType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for decluttered replay instructions that need to be rendered or
|
||||||
|
* omitted together, i.e. when styles render both an image and text, or for the
|
||||||
|
* characters that form text along lines. The basic elements of this array are
|
||||||
|
* `[minX, minY, maxX, maxY, count]`, where the first four entries are the
|
||||||
|
* rendered extent of the group in pixel space. `count` is the number of styles
|
||||||
|
* in the group, i.e. 2 when an image and a text are grouped, or 1 otherwise.
|
||||||
|
* In addition to these four elements, declutter instruction arrays (i.e. the
|
||||||
|
* arguments to @{link ol.render.canvas.drawImage} are appended to the array.
|
||||||
|
* @typedef {Array.<*>}
|
||||||
|
*/
|
||||||
|
ol.DeclutterGroup;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that takes a {@link ol.MapBrowserEvent} and two
|
* A function that takes a {@link ol.MapBrowserEvent} and two
|
||||||
* {@link ol.Pixel}s and returns a `{boolean}`. If the condition is met,
|
* {@link ol.Pixel}s and returns a `{boolean}`. If the condition is met,
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 968 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -6,12 +6,15 @@ goog.require('ol.View');
|
|||||||
goog.require('ol.format.GeoJSON');
|
goog.require('ol.format.GeoJSON');
|
||||||
goog.require('ol.geom.Circle');
|
goog.require('ol.geom.Circle');
|
||||||
goog.require('ol.geom.LineString');
|
goog.require('ol.geom.LineString');
|
||||||
|
goog.require('ol.geom.Point');
|
||||||
goog.require('ol.geom.Polygon');
|
goog.require('ol.geom.Polygon');
|
||||||
goog.require('ol.layer.Vector');
|
goog.require('ol.layer.Vector');
|
||||||
goog.require('ol.source.Vector');
|
goog.require('ol.source.Vector');
|
||||||
|
goog.require('ol.style.Circle');
|
||||||
goog.require('ol.style.Fill');
|
goog.require('ol.style.Fill');
|
||||||
goog.require('ol.style.Stroke');
|
goog.require('ol.style.Stroke');
|
||||||
goog.require('ol.style.Style');
|
goog.require('ol.style.Style');
|
||||||
|
goog.require('ol.style.Text');
|
||||||
|
|
||||||
|
|
||||||
describe('ol.rendering.layer.Vector', function() {
|
describe('ol.rendering.layer.Vector', function() {
|
||||||
@@ -461,4 +464,304 @@ describe('ol.rendering.layer.Vector', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('decluttering', function() {
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
source = new ol.source.Vector();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('declutters text', function(done) {
|
||||||
|
createMap('canvas');
|
||||||
|
var layer = new ol.layer.Vector({
|
||||||
|
source: source
|
||||||
|
});
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
var centerFeature = new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point(center),
|
||||||
|
text: 'center'
|
||||||
|
});
|
||||||
|
source.addFeature(centerFeature);
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] - 550, center[1]]),
|
||||||
|
text: 'west'
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] + 550, center[1]]),
|
||||||
|
text: 'east'
|
||||||
|
}));
|
||||||
|
|
||||||
|
layer.setDeclutter(true);
|
||||||
|
layer.setStyle(function(feature) {
|
||||||
|
return new ol.style.Style({
|
||||||
|
text: new ol.style.Text({
|
||||||
|
text: feature.get('text'),
|
||||||
|
font: '12px sans-serif'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('postrender', function() {
|
||||||
|
var hitDetected = map.getFeaturesAtPixel([42, 42]);
|
||||||
|
expect(hitDetected).to.have.length(1);
|
||||||
|
expect(hitDetected[0]).to.equal(centerFeature);
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter.png',
|
||||||
|
2.2, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('declutters text and respects z-index', function(done) {
|
||||||
|
createMap('canvas');
|
||||||
|
var layer = new ol.layer.Vector({
|
||||||
|
source: source
|
||||||
|
});
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point(center),
|
||||||
|
text: 'center',
|
||||||
|
zIndex: 2
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] - 550, center[1]]),
|
||||||
|
text: 'west',
|
||||||
|
zIndex: 3
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] + 550, center[1]]),
|
||||||
|
text: 'east',
|
||||||
|
zIndex: 1
|
||||||
|
}));
|
||||||
|
|
||||||
|
layer.setDeclutter(true);
|
||||||
|
layer.setStyle(function(feature) {
|
||||||
|
return new ol.style.Style({
|
||||||
|
zIndex: feature.get('zIndex'),
|
||||||
|
text: new ol.style.Text({
|
||||||
|
text: feature.get('text'),
|
||||||
|
font: '12px sans-serif'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('postrender', function() {
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-zindex.png',
|
||||||
|
3.9, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('declutters images', function(done) {
|
||||||
|
createMap('canvas');
|
||||||
|
var layer = new ol.layer.Vector({
|
||||||
|
source: source
|
||||||
|
});
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
var centerFeature = new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point(center)
|
||||||
|
});
|
||||||
|
source.addFeature(centerFeature);
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] - 550, center[1]])
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] + 550, center[1]])
|
||||||
|
}));
|
||||||
|
|
||||||
|
layer.setDeclutter(true);
|
||||||
|
layer.setStyle(function(feature) {
|
||||||
|
return new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 15,
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('postrender', function() {
|
||||||
|
var hitDetected = map.getFeaturesAtPixel([40, 40]);
|
||||||
|
expect(hitDetected).to.have.length(1);
|
||||||
|
expect(hitDetected[0]).to.equal(centerFeature);
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-image.png',
|
||||||
|
IMAGE_TOLERANCE, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('declutters images and respects z-index', function(done) {
|
||||||
|
createMap('canvas');
|
||||||
|
var layer = new ol.layer.Vector({
|
||||||
|
source: source
|
||||||
|
});
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point(center),
|
||||||
|
zIndex: 2
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] - 550, center[1]]),
|
||||||
|
zIndex: 3
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] + 550, center[1]]),
|
||||||
|
zIndex: 1
|
||||||
|
}));
|
||||||
|
|
||||||
|
layer.setDeclutter(true);
|
||||||
|
layer.setStyle(function(feature) {
|
||||||
|
return new ol.style.Style({
|
||||||
|
zIndex: feature.get('zIndex'),
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 15,
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('postrender', function() {
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-image-zindex.png',
|
||||||
|
IMAGE_TOLERANCE, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('declutters image & text groups', function(done) {
|
||||||
|
createMap('canvas');
|
||||||
|
var layer = new ol.layer.Vector({
|
||||||
|
source: source
|
||||||
|
});
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point(center),
|
||||||
|
text: 'center'
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] - 550, center[1]]),
|
||||||
|
text: 'west'
|
||||||
|
}));
|
||||||
|
source.addFeature(new ol.Feature({
|
||||||
|
geometry: new ol.geom.Point([center[0] + 550, center[1]]),
|
||||||
|
text: 'east'
|
||||||
|
}));
|
||||||
|
|
||||||
|
layer.setDeclutter(true);
|
||||||
|
layer.setStyle(function(feature) {
|
||||||
|
return new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 5,
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
text: new ol.style.Text({
|
||||||
|
text: feature.get('text'),
|
||||||
|
font: '12px sans-serif',
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
offsetY: -5
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('postrender', function() {
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-group.png',
|
||||||
|
2.2, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('declutters text along lines and images', function(done) {
|
||||||
|
createMap('canvas');
|
||||||
|
var layer = new ol.layer.Vector({
|
||||||
|
source: source
|
||||||
|
});
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
var point = new ol.Feature(new ol.geom.Point(center));
|
||||||
|
point.setStyle(new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 8,
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
var line = new ol.Feature(new ol.geom.LineString([
|
||||||
|
[center[0] - 650, center[1] - 200],
|
||||||
|
[center[0] + 650, center[1] - 200]
|
||||||
|
]));
|
||||||
|
line.setStyle(new ol.style.Style({
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: '#CCC',
|
||||||
|
width: 12
|
||||||
|
}),
|
||||||
|
text: new ol.style.Text({
|
||||||
|
placement: 'line',
|
||||||
|
text: 'east-west',
|
||||||
|
font: '12px sans-serif'
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
source.addFeature(point);
|
||||||
|
source.addFeature(line);
|
||||||
|
|
||||||
|
layer.setDeclutter(true);
|
||||||
|
|
||||||
|
map.once('postrender', function() {
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-line.png',
|
||||||
|
IMAGE_TOLERANCE, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('declutters text along lines and images with z-index', function(done) {
|
||||||
|
createMap('canvas');
|
||||||
|
var layer = new ol.layer.Vector({
|
||||||
|
source: source
|
||||||
|
});
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
var point = new ol.Feature(new ol.geom.Point(center));
|
||||||
|
point.setStyle(new ol.style.Style({
|
||||||
|
zIndex: 2,
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 8,
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: 'blue'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
var line = new ol.Feature(new ol.geom.LineString([
|
||||||
|
[center[0] - 650, center[1] - 200],
|
||||||
|
[center[0] + 650, center[1] - 200]
|
||||||
|
]));
|
||||||
|
line.setStyle(new ol.style.Style({
|
||||||
|
zIndex: 1,
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: '#CCC',
|
||||||
|
width: 12
|
||||||
|
}),
|
||||||
|
text: new ol.style.Text({
|
||||||
|
placement: 'line',
|
||||||
|
text: 'east-west',
|
||||||
|
font: '12px sans-serif'
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
source.addFeature(point);
|
||||||
|
source.addFeature(line);
|
||||||
|
|
||||||
|
layer.setDeclutter(true);
|
||||||
|
|
||||||
|
map.once('postrender', function() {
|
||||||
|
var hitDetected = map.getFeaturesAtPixel([35, 46]);
|
||||||
|
expect(hitDetected).to.have.length(1);
|
||||||
|
expect(hitDetected[0]).to.equal(line);
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vector-canvas-declutter-line-zindex.png',
|
||||||
|
4.1, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ goog.require('ol.format.MVT');
|
|||||||
goog.require('ol.layer.VectorTile');
|
goog.require('ol.layer.VectorTile');
|
||||||
goog.require('ol.obj');
|
goog.require('ol.obj');
|
||||||
goog.require('ol.source.VectorTile');
|
goog.require('ol.source.VectorTile');
|
||||||
|
goog.require('ol.style.Circle');
|
||||||
|
goog.require('ol.style.Fill');
|
||||||
|
goog.require('ol.style.Style');
|
||||||
|
goog.require('ol.style.Text');
|
||||||
goog.require('ol.tilegrid');
|
goog.require('ol.tilegrid');
|
||||||
|
|
||||||
|
|
||||||
@@ -13,10 +17,11 @@ describe('ol.rendering.layer.VectorTile', function() {
|
|||||||
|
|
||||||
var map;
|
var map;
|
||||||
|
|
||||||
function createMap(renderer, opt_pixelRatio) {
|
function createMap(renderer, opt_pixelRatio, opt_size) {
|
||||||
|
var size = opt_size || 50;
|
||||||
map = new ol.Map({
|
map = new ol.Map({
|
||||||
pixelRatio: opt_pixelRatio || 1,
|
pixelRatio: opt_pixelRatio || 1,
|
||||||
target: createMapDiv(50, 50),
|
target: createMapDiv(size, size),
|
||||||
renderer: renderer,
|
renderer: renderer,
|
||||||
view: new ol.View({
|
view: new ol.View({
|
||||||
center: [1825927.7316762917, 6143091.089223046],
|
center: [1825927.7316762917, 6143091.089223046],
|
||||||
@@ -104,6 +109,34 @@ describe('ol.rendering.layer.VectorTile', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('declutters text and images', function(done) {
|
||||||
|
createMap('canvas', 1, 100);
|
||||||
|
map.getView().setZoom(13.8);
|
||||||
|
var style = function(feature, resolution) {
|
||||||
|
var geom = feature.getGeometry();
|
||||||
|
if (geom.getType() == 'Point') {
|
||||||
|
return new ol.style.Style({
|
||||||
|
image: new ol.style.Circle({
|
||||||
|
radius: 7,
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: 'red'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
text: new ol.style.Text({
|
||||||
|
text: feature.get('name_en'),
|
||||||
|
font: '12px sans-serif',
|
||||||
|
textBaseline: 'bottom',
|
||||||
|
offsetY: -7
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
waitForTiles(source, {declutter: true, style: style}, function() {
|
||||||
|
expectResemble(map, 'rendering/ol/layer/expected/vectortile-canvas-declutter.png',
|
||||||
|
8.5, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
goog.require('ol.geom.LineString');
|
||||||
|
goog.require('ol.geom.MultiLineString');
|
||||||
|
goog.require('ol.geom.MultiPolygon');
|
||||||
|
goog.require('ol.geom.Polygon');
|
||||||
goog.require('ol.render.Feature');
|
goog.require('ol.render.Feature');
|
||||||
|
|
||||||
|
|
||||||
@@ -51,6 +53,51 @@ describe('ol.render.Feature', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getFlatInteriorPoint()', function() {
|
||||||
|
it('returns correct point and caches it', function() {
|
||||||
|
var polygon = new ol.geom.Polygon([[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]]);
|
||||||
|
var feature = new ol.render.Feature('Polygon', polygon.getOrientedFlatCoordinates(),
|
||||||
|
polygon.getEnds());
|
||||||
|
expect(feature.getFlatInteriorPoint()).to.eql([5, 5, 10]);
|
||||||
|
expect(feature.getFlatInteriorPoint()).to.be(feature.flatInteriorPoints_);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getFlatInteriorPoints()', function() {
|
||||||
|
it('returns correct points and caches them', function() {
|
||||||
|
var polygon = new ol.geom.MultiPolygon([
|
||||||
|
[[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]],
|
||||||
|
[[[10, 0], [10, 10], [20, 10], [20, 0], [10, 0]]]
|
||||||
|
]);
|
||||||
|
var feature = new ol.render.Feature('MultiPolygon', polygon.getOrientedFlatCoordinates(),
|
||||||
|
polygon.getEndss());
|
||||||
|
expect(feature.getFlatInteriorPoints()).to.eql([5, 5, 10, 15, 5, 10]);
|
||||||
|
expect(feature.getFlatInteriorPoints()).to.be(feature.flatInteriorPoints_);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getFlatMidpoint()', function() {
|
||||||
|
it('returns correct point', function() {
|
||||||
|
var line = new ol.geom.LineString([[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]]);
|
||||||
|
var feature = new ol.render.Feature('LineString', line.getFlatCoordinates());
|
||||||
|
expect(feature.getFlatMidpoint()).to.eql([10, 10]);
|
||||||
|
expect(feature.getFlatMidpoint()).to.eql(feature.flatMidpoints_);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getFlatMidpoints()', function() {
|
||||||
|
it('returns correct points and caches them', function() {
|
||||||
|
var line = new ol.geom.MultiLineString([
|
||||||
|
[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]],
|
||||||
|
[[10, 0], [10, 10], [20, 10], [20, 0], [10, 0]]
|
||||||
|
]);
|
||||||
|
var feature = new ol.render.Feature('MultiLineString', line.getFlatCoordinates(),
|
||||||
|
line.getEnds());
|
||||||
|
expect(feature.getFlatMidpoints()).to.eql([10, 10, 20, 10]);
|
||||||
|
expect(feature.getFlatMidpoints()).to.be(feature.flatMidpoints_);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#getGeometry()', function() {
|
describe('#getGeometry()', function() {
|
||||||
it('returns itself as geometry', function() {
|
it('returns itself as geometry', function() {
|
||||||
expect(renderFeature.getGeometry()).to.equal(renderFeature);
|
expect(renderFeature.getGeometry()).to.equal(renderFeature);
|
||||||
|
|||||||