Merge pull request #7079 from ahocevar/optimize-custom-renderer
Optimize custom renderer code, examples and API
This commit is contained in:
+15
-16
@@ -25,22 +25,21 @@ function collectDrawData(letter, x, y, angle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var style = new ol.style.Style({
|
var style = new ol.style.Style({
|
||||||
renderer: function(coords, geometry, feature) {
|
renderer: function(coords, context) {
|
||||||
|
var feature = context.feature;
|
||||||
var text = feature.get('name');
|
var text = feature.get('name');
|
||||||
if (text) {
|
// Only create label when geometry has a long and straight segment
|
||||||
// Only create label when geometry has a long and straight segment
|
var path = labelSegment(coords, Math.PI / 8, measureText(text));
|
||||||
var path = labelSegment(coords, Math.PI / 8, measureText(text));
|
if (path) {
|
||||||
if (path) {
|
extent = ol.extent.createEmpty();
|
||||||
extent = ol.extent.createEmpty();
|
letters = [];
|
||||||
letters = [];
|
textPath(text, path, measureText, collectDrawData);
|
||||||
textPath(text, path, measureText, collectDrawData);
|
ol.extent.buffer(extent, 5 * pixelRatio, extent);
|
||||||
ol.extent.buffer(extent, 5 * pixelRatio, extent);
|
var bounds = {
|
||||||
var bounds = {
|
bottomLeft: ol.extent.getBottomLeft(extent),
|
||||||
bottomLeft: ol.extent.getBottomLeft(extent),
|
topRight: ol.extent.getTopRight(extent)
|
||||||
topRight: ol.extent.getTopRight(extent)
|
};
|
||||||
};
|
labelEngine.ingestLabel(bounds, feature.getId(), 1, letters, text, false);
|
||||||
labelEngine.ingestLabel(bounds, feature.getId(), 1, letters, text, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -69,7 +68,7 @@ fetch('https://overpass-api.de/api/interpreter', {
|
|||||||
var vectorLayer = new ol.layer.Vector({
|
var vectorLayer = new ol.layer.Vector({
|
||||||
source: source,
|
source: source,
|
||||||
style: function(feature) {
|
style: function(feature) {
|
||||||
if (feature.getGeometry().getType() == 'LineString') {
|
if (feature.getGeometry().getType() == 'LineString' && feature.get('text')) {
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ goog.require('ol.Map');
|
|||||||
goog.require('ol.View');
|
goog.require('ol.View');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
goog.require('ol.format.GeoJSON');
|
goog.require('ol.format.GeoJSON');
|
||||||
goog.require('ol.geom.Point');
|
|
||||||
goog.require('ol.layer.Vector');
|
goog.require('ol.layer.Vector');
|
||||||
goog.require('ol.source.Vector');
|
goog.require('ol.source.Vector');
|
||||||
goog.require('ol.style.Fill');
|
goog.require('ol.style.Fill');
|
||||||
@@ -58,26 +57,43 @@ function sortByWidth(a, b) {
|
|||||||
return ol.extent.getWidth(b.getExtent()) - ol.extent.getWidth(a.getExtent());
|
return ol.extent.getWidth(b.getExtent()) - ol.extent.getWidth(a.getExtent());
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolution; // This is set by the map's precompose listener
|
var labelStyle = new ol.style.Style({
|
||||||
var styles = [
|
renderer: function(coords, state) {
|
||||||
new ol.style.Style({
|
var text = state.feature.get('name');
|
||||||
fill: new ol.style.Fill({
|
createLabel(textCache[text], text, coords);
|
||||||
color: 'rgba(255, 255, 255, 0.6)'
|
}
|
||||||
}),
|
});
|
||||||
stroke: new ol.style.Stroke({
|
var countryStyle = new ol.style.Style({
|
||||||
color: '#319FD3',
|
fill: new ol.style.Fill({
|
||||||
width: 1
|
color: 'rgba(255, 255, 255, 0.6)'
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
new ol.style.Style({
|
stroke: new ol.style.Stroke({
|
||||||
renderer: function(coord, geometry, feature, state) {
|
color: '#319FD3',
|
||||||
var pixelRatio = state.pixelRatio;
|
width: 1
|
||||||
var text = feature.get('name');
|
})
|
||||||
var canvas = textCache[text];
|
});
|
||||||
if (!canvas) {
|
var styleWithLabel = [countryStyle, labelStyle];
|
||||||
|
var styleWithoutLabel = [countryStyle];
|
||||||
|
|
||||||
|
var pixelRatio; // This is set by the map's precompose listener
|
||||||
|
var vectorLayer = new ol.layer.Vector({
|
||||||
|
source: new ol.source.Vector({
|
||||||
|
url: 'data/geojson/countries.geojson',
|
||||||
|
format: new ol.format.GeoJSON()
|
||||||
|
}),
|
||||||
|
style: function(feature, resolution) {
|
||||||
|
var text = feature.get('name');
|
||||||
|
var width = textMeasureContext.measureText(text).width;
|
||||||
|
var geometry = feature.getGeometry();
|
||||||
|
if (geometry.getType() == 'MultiPolygon') {
|
||||||
|
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.
|
// Draw the label to its own canvas and cache it.
|
||||||
var width = textMeasureContext.measureText(text).width;
|
var canvas = textCache[text] = document.createElement('CANVAS');
|
||||||
canvas = textCache[text] = document.createElement('CANVAS');
|
|
||||||
canvas.width = width * pixelRatio;
|
canvas.width = width * pixelRatio;
|
||||||
canvas.height = height * pixelRatio;
|
canvas.height = height * pixelRatio;
|
||||||
var context = canvas.getContext('2d');
|
var context = canvas.getContext('2d');
|
||||||
@@ -86,38 +102,15 @@ var styles = [
|
|||||||
context.strokeText(text, 0, 0);
|
context.strokeText(text, 0, 0);
|
||||||
context.fillText(text, 0, 0);
|
context.fillText(text, 0, 0);
|
||||||
}
|
}
|
||||||
// The 3rd value of the coordinate is the measure of the extent width
|
labelStyle.setGeometry(geometry.getInteriorPoint());
|
||||||
var extentWidth = geometry.getCoordinates()[2] / resolution * pixelRatio;
|
return styleWithLabel;
|
||||||
if (extentWidth > canvas.width) {
|
} else {
|
||||||
// Only consider labels not wider than their country's bounding box
|
return styleWithoutLabel;
|
||||||
createLabel(canvas, text, coord);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Geometry function to determine label positions
|
|
||||||
geometry: function(feature) {
|
|
||||||
var geometry = feature.getGeometry();
|
|
||||||
if (geometry.getType() == 'MultiPolygon') {
|
|
||||||
var geometries = geometry.getPolygons();
|
|
||||||
geometry = geometries.sort(sortByWidth)[0];
|
|
||||||
}
|
|
||||||
var coordinates = geometry.getInteriorPoint().getCoordinates();
|
|
||||||
var extentWidth = ol.extent.getWidth(geometry.getExtent());
|
|
||||||
// We are using the extentWidth as measure value of the geometry
|
|
||||||
coordinates.push(extentWidth);
|
|
||||||
return new ol.geom.Point(coordinates, 'XYM');
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
];
|
|
||||||
|
|
||||||
var vectorLayer = new ol.layer.Vector({
|
|
||||||
source: new ol.source.Vector({
|
|
||||||
url: 'data/geojson/countries.geojson',
|
|
||||||
format: new ol.format.GeoJSON()
|
|
||||||
}),
|
|
||||||
style: styles
|
|
||||||
});
|
});
|
||||||
vectorLayer.on('precompose', function(e) {
|
vectorLayer.on('precompose', function(e) {
|
||||||
resolution = e.frameState.viewState.resolution;
|
pixelRatio = e.frameState.pixelRatio;
|
||||||
labelEngine.destroy();
|
labelEngine.destroy();
|
||||||
});
|
});
|
||||||
vectorLayer.on('postcompose', function(e) {
|
vectorLayer.on('postcompose', function(e) {
|
||||||
|
|||||||
@@ -4402,6 +4402,8 @@ olx.render;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{context: CanvasRenderingContext2D,
|
* @typedef {{context: CanvasRenderingContext2D,
|
||||||
|
* feature: (ol.Feature|ol.render.Feature),
|
||||||
|
* geometry: ol.geom.SimpleGeometry,
|
||||||
* pixelRatio: number,
|
* pixelRatio: number,
|
||||||
* resolution: number,
|
* resolution: number,
|
||||||
* rotation: number}}
|
* rotation: number}}
|
||||||
|
|||||||
@@ -41,12 +41,6 @@ ol.geom.SimpleGeometry = function() {
|
|||||||
*/
|
*/
|
||||||
this.flatCoordinates = null;
|
this.flatCoordinates = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {Array.<number>|Array.<Array.<number>>|Array.<Array.<Array.<number>>>}
|
|
||||||
*/
|
|
||||||
this.renderCoordinates_ = null;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
|
ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);
|
||||||
|
|
||||||
@@ -147,18 +141,6 @@ ol.geom.SimpleGeometry.prototype.getLayout = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {Array.<number>|Array.<Array.<number>>|Array.<Array.<Array.<number>>>}
|
|
||||||
* Render coordinates.
|
|
||||||
*/
|
|
||||||
ol.geom.SimpleGeometry.prototype.getRenderCoordinates = function() {
|
|
||||||
if (!this.renderCoordinates_) {
|
|
||||||
this.renderCoordinates_ = [];
|
|
||||||
}
|
|
||||||
return this.renderCoordinates_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -88,6 +88,12 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) {
|
|||||||
*/
|
*/
|
||||||
this.coordinates = [];
|
this.coordinates = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Object.<number,ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>>}
|
||||||
|
*/
|
||||||
|
this.coordinateCache_ = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {!ol.Transform}
|
* @type {!ol.Transform}
|
||||||
@@ -320,16 +326,14 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
var prevX, prevY, roundX, roundY;
|
var prevX, prevY, roundX, roundY;
|
||||||
var pendingFill = 0;
|
var pendingFill = 0;
|
||||||
var pendingStroke = 0;
|
var pendingStroke = 0;
|
||||||
|
var coordinateCache = this.coordinateCache_;
|
||||||
|
|
||||||
/**
|
var state = /** @type {olx.render.State} */ ({
|
||||||
* @type {olx.render.State}
|
|
||||||
*/
|
|
||||||
var state = {
|
|
||||||
context: context,
|
context: context,
|
||||||
pixelRatio: pixelRatio,
|
pixelRatio: pixelRatio,
|
||||||
resolution: this.resolution,
|
resolution: this.resolution,
|
||||||
rotation: viewRotation
|
rotation: viewRotation
|
||||||
};
|
});
|
||||||
|
|
||||||
// When the batch size gets too big, performance decreases. 200 is a good
|
// When the batch size gets too big, performance decreases. 200 is a good
|
||||||
// balance between batch size and number of fill/stroke instructions.
|
// balance between batch size and number of fill/stroke instructions.
|
||||||
@@ -338,7 +342,7 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
while (i < ii) {
|
while (i < ii) {
|
||||||
var instruction = instructions[i];
|
var instruction = instructions[i];
|
||||||
var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
|
var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
|
||||||
var feature, fill, stroke, text, x, y;
|
var /** @type {ol.Feature|ol.render.Feature} */ feature, fill, stroke, text, x, y;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
|
case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
|
||||||
feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
|
feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
|
||||||
@@ -390,14 +394,21 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
dd = instruction[2];
|
dd = instruction[2];
|
||||||
var geometry = /** @type {ol.geom.SimpleGeometry} */ (instruction[3]);
|
var geometry = /** @type {ol.geom.SimpleGeometry} */ (instruction[3]);
|
||||||
var renderer = instruction[4];
|
var renderer = instruction[4];
|
||||||
var coords;
|
var fn = instruction.length == 6 ? instruction[5] : undefined;
|
||||||
if (instruction.length == 6) {
|
state.geometry = geometry;
|
||||||
var fn = instruction[5];
|
state.feature = feature;
|
||||||
coords = fn(pixelCoordinates, d, dd, 2, geometry.getRenderCoordinates());
|
if (!(i in coordinateCache)) {
|
||||||
} else {
|
coordinateCache[i] = [];
|
||||||
coords = pixelCoordinates.slice(d, dd);
|
|
||||||
}
|
}
|
||||||
renderer(coords, geometry, feature, state);
|
var coords = coordinateCache[i];
|
||||||
|
if (fn) {
|
||||||
|
fn(pixelCoordinates, d, dd, 2, coords);
|
||||||
|
} else {
|
||||||
|
coords[0] = pixelCoordinates[d];
|
||||||
|
coords[1] = pixelCoordinates[d + 1];
|
||||||
|
coords.length = 2;
|
||||||
|
}
|
||||||
|
renderer(coords, state);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.DRAW_IMAGE:
|
case ol.render.canvas.Instruction.DRAW_IMAGE:
|
||||||
|
|||||||
@@ -43,12 +43,6 @@ ol.render.Feature = function(type, flatCoordinates, ends, properties, id) {
|
|||||||
*/
|
*/
|
||||||
this.flatCoordinates_ = flatCoordinates;
|
this.flatCoordinates_ = flatCoordinates;
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {Array.<number>|Array.<Array.<number>>|Array.<Array.<Array.<number>>>}
|
|
||||||
*/
|
|
||||||
this.renderCoordinates_ = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {Array.<number>|Array.<Array.<number>>}
|
* @type {Array.<number>|Array.<Array.<number>>}
|
||||||
@@ -117,18 +111,6 @@ ol.render.Feature.prototype.getOrientedFlatCoordinates = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {Array.<number>|Array.<Array.<number>>|Array.<Array.<Array.<number>>>}
|
|
||||||
* Render coordinates.
|
|
||||||
*/
|
|
||||||
ol.render.Feature.prototype.getRenderCoordinates = function() {
|
|
||||||
if (!this.renderCoordinates_) {
|
|
||||||
this.renderCoordinates_ = [];
|
|
||||||
}
|
|
||||||
return this.renderCoordinates_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Array.<number>} Flat coordinates.
|
* @return {Array.<number>} Flat coordinates.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+3
-5
@@ -627,14 +627,12 @@ ol.StyleGeometryFunction;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom renderer function. Takes 4 arguments:
|
* Custom renderer function. Takes two arguments:
|
||||||
*
|
*
|
||||||
* 1. The pixel coordinates of the geometry in GeoJSON notation.
|
* 1. The pixel coordinates of the geometry in GeoJSON notation.
|
||||||
* 2. The original {@link ol.geom.SimpleGeometry}.
|
* 2. The {@link olx.render.State} of the layer renderer.
|
||||||
* 3. The underlying {@link ol.Feature} or {@link ol.render.Feature}.
|
|
||||||
* 4. The {@link olx.render.State} of the layer renderer.
|
|
||||||
*
|
*
|
||||||
* @typedef {function(Array,ol.geom.SimpleGeometry,(ol.Feature|ol.render.Feature),olx.render.State)}
|
* @typedef {function((ol.Coordinate|Array<ol.Coordinate>|Array.<Array.<ol.Coordinate>>),olx.render.State)}
|
||||||
*/
|
*/
|
||||||
ol.StyleRenderFunction;
|
ol.StyleRenderFunction;
|
||||||
|
|
||||||
|
|||||||
@@ -207,9 +207,19 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('calls the renderer function configured for the style', function() {
|
it('calls the renderer function configured for the style', function() {
|
||||||
var spy = sinon.spy();
|
var calls = [];
|
||||||
var style = new ol.style.Style({
|
var style = new ol.style.Style({
|
||||||
renderer: spy
|
renderer: function(coords, state) {
|
||||||
|
calls.push({
|
||||||
|
coords: coords,
|
||||||
|
geometry: state.geometry,
|
||||||
|
feature: state.feature,
|
||||||
|
context: state.context,
|
||||||
|
pixelRatio: state.pixelRatio,
|
||||||
|
rotation: state.rotation,
|
||||||
|
resolution: state.resolution
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
var point = new ol.Feature(new ol.geom.Point([45, 90]));
|
var point = new ol.Feature(new ol.geom.Point([45, 90]));
|
||||||
var multipoint = new ol.Feature(new ol.geom.MultiPoint(
|
var multipoint = new ol.Feature(new ol.geom.MultiPoint(
|
||||||
@@ -233,24 +243,28 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
ol.renderer.vector.renderFeature(replay, geometrycollection, style, 1);
|
ol.renderer.vector.renderFeature(replay, geometrycollection, style, 1);
|
||||||
ol.transform.scale(transform, 0.1, 0.1);
|
ol.transform.scale(transform, 0.1, 0.1);
|
||||||
replay.replay(context, 1, transform, 0, {});
|
replay.replay(context, 1, transform, 0, {});
|
||||||
expect(spy.callCount).to.be(9);
|
expect(calls.length).to.be(9);
|
||||||
expect(spy.firstCall.args.length).to.be(4);
|
expect(calls[0].geometry).to.be(point.getGeometry());
|
||||||
expect(spy.firstCall.args[1]).to.be(point.getGeometry());
|
expect(calls[0].feature).to.be(point);
|
||||||
expect(spy.firstCall.args[2]).to.be(point);
|
expect(calls[0].context).to.be(context);
|
||||||
expect(spy.firstCall.args[3].context).to.be(context);
|
expect(calls[0].pixelRatio).to.be(1);
|
||||||
expect(spy.firstCall.args[3].pixelRatio).to.be(1);
|
expect(calls[0].rotation).to.be(0);
|
||||||
expect(spy.firstCall.args[3].rotation).to.be(0);
|
expect(calls[0].resolution).to.be(1);
|
||||||
expect(spy.firstCall.args[3].resolution).to.be(1);
|
expect(calls[0].coords).to.eql([4.5, 9]);
|
||||||
expect(spy.getCall(0).args[0]).to.eql([4.5, 9]);
|
expect(calls[1].feature).to.be(multipoint);
|
||||||
expect(spy.getCall(1).args[0][0]).to.eql([4.5, 9]);
|
expect(calls[1].coords[0]).to.eql([4.5, 9]);
|
||||||
expect(spy.getCall(2).args[0][0]).to.eql([4.5, 9]);
|
expect(calls[2].feature).to.be(linestring);
|
||||||
expect(spy.getCall(3).args[0][0][0]).to.eql([4.5, 9]);
|
expect(calls[2].coords[0]).to.eql([4.5, 9]);
|
||||||
expect(spy.getCall(4).args[0][0][0]).to.eql([-9, -4.5]);
|
expect(calls[3].feature).to.be(multilinestring);
|
||||||
expect(spy.getCall(5).args[0][0][0][0]).to.eql([-9, -4.5]);
|
expect(calls[3].coords[0][0]).to.eql([4.5, 9]);
|
||||||
expect(spy.getCall(6).args[2]).to.be(geometrycollection);
|
expect(calls[4].feature).to.be(polygon);
|
||||||
expect(spy.getCall(6).args[1].getCoordinates()).to.eql([45, 90]);
|
expect(calls[4].coords[0][0]).to.eql([-9, -4.5]);
|
||||||
expect(spy.getCall(7).args[1].getCoordinates()[0]).to.eql([45, 90]);
|
expect(calls[5].feature).to.be(multipolygon);
|
||||||
expect(spy.getCall(8).args[1].getCoordinates()[0][0]).to.eql([-90, -45]);
|
expect(calls[5].coords[0][0][0]).to.eql([-9, -4.5]);
|
||||||
|
expect(calls[6].feature).to.be(geometrycollection);
|
||||||
|
expect(calls[6].geometry.getCoordinates()).to.eql([45, 90]);
|
||||||
|
expect(calls[7].geometry.getCoordinates()[0]).to.eql([45, 90]);
|
||||||
|
expect(calls[8].geometry.getCoordinates()[0][0]).to.eql([-90, -45]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user