Allow styles to configure a custom renderer
Two new examples show how custom renderers can be used to render text along paths, and to declutter labels using 3rd party libraries.
This commit is contained in:
20
examples/street-labels.html
Normal file
20
examples/street-labels.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: Street Labels
|
||||||
|
shortdesc: Render street names with a custom render.
|
||||||
|
docs: >
|
||||||
|
Example showing the use of a custom renderer to render text along a path. [Labelgun](https://github.com/Geovation/labelgun) is used to avoid label collisions, and [linelabel](https://github.com/naturalatlas/linelabel) makes sure that labels are placed on suitable street segments. The data is fetched from OSM using the [Overpass API](https://overpass-api.de).
|
||||||
|
tags: "vector, label, collision detection, labelgun, linelabel, overpass"
|
||||||
|
resources:
|
||||||
|
- 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
|
||||||
|
cloak:
|
||||||
|
As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5: Your Bing Maps Key from http://www.bingmapsportal.com/ here
|
||||||
|
---
|
||||||
|
<!-- Wrap https://npmjs.com/package/linelabel -->
|
||||||
|
<script>var module = {};</script>
|
||||||
|
<script src="https://unpkg.com/linelabel@0.1.1"></script>
|
||||||
|
<script>var linelabel = module.exports;</script>
|
||||||
|
|
||||||
|
<div id="map" class="map"></div>
|
||||||
177
examples/street-labels.js
Normal file
177
examples/street-labels.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
// NOCOMPILE
|
||||||
|
goog.require('ol.Map');
|
||||||
|
goog.require('ol.View');
|
||||||
|
goog.require('ol.extent');
|
||||||
|
goog.require('ol.format.OSMXML');
|
||||||
|
goog.require('ol.geom.LineString');
|
||||||
|
goog.require('ol.layer.Tile');
|
||||||
|
goog.require('ol.layer.Vector');
|
||||||
|
goog.require('ol.source.BingMaps');
|
||||||
|
goog.require('ol.source.Vector');
|
||||||
|
goog.require('ol.style.Style');
|
||||||
|
|
||||||
|
/* global labelgun */
|
||||||
|
var labelEngine = new labelgun['default'](function() {}, function() {});
|
||||||
|
|
||||||
|
function segmentSort(a, b) {
|
||||||
|
return a.length - b.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dist2D(p1, p2) {
|
||||||
|
var dx = p2[0] - p1[0];
|
||||||
|
var dy = p2[1] - p1[1];
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified from https://github.com/Viglino/ol3-ext/blob/7d17eef5720970fd36798ebc889ea44d9f04b059/style/settextpathstyle.js
|
||||||
|
function textPath(ctx, text, path, fid, pixelRatio) {
|
||||||
|
var di, dpos = 0;
|
||||||
|
var pos = 1;
|
||||||
|
var letterPadding = ctx.measureText(' ').width * pixelRatio * 0.25;
|
||||||
|
var d = 0;
|
||||||
|
for (var i = 1; i < path.length; ++i) {
|
||||||
|
d += dist2D(path[i - 1], path[i]);
|
||||||
|
}
|
||||||
|
var nbspace = text.split(' ').length - 1;
|
||||||
|
var start = (d - ctx.measureText(text).width * pixelRatio - (text.length + nbspace) * letterPadding) / 2;
|
||||||
|
var extent = ol.extent.createEmpty();
|
||||||
|
var letters = [];
|
||||||
|
for (var t = 0; t < text.length; t++) {
|
||||||
|
var letter = text[t];
|
||||||
|
var wl = ctx.measureText(letter).width * pixelRatio;
|
||||||
|
var dl = start + wl / 2;
|
||||||
|
if (!di || dpos + di < dl) {
|
||||||
|
for (; pos < path.length;) {
|
||||||
|
di = dist2D(path[pos - 1], path[pos]);
|
||||||
|
if (dpos + di > dl) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += 1;
|
||||||
|
if (pos >= path.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dpos += di;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var x, y, a, dt = dl - dpos;
|
||||||
|
if (pos >= path.length) {
|
||||||
|
pos = path.length - 1;
|
||||||
|
}
|
||||||
|
a = Math.atan2(path[pos][1] - path[pos - 1][1], path[pos][0] - path[pos - 1][0]);
|
||||||
|
if (!dt) {
|
||||||
|
x = path[pos - 1][0];
|
||||||
|
y = path[pos - 1][1];
|
||||||
|
} else {
|
||||||
|
x = path[pos - 1][0] + (path[pos][0] - path[pos - 1][0]) * dt / di;
|
||||||
|
y = path[pos - 1][1] + (path[pos][1] - path[pos - 1][1]) * dt / di;
|
||||||
|
}
|
||||||
|
ol.extent.extendCoordinate(extent, [x, y]);
|
||||||
|
letters.push([x, y, a, letter]);
|
||||||
|
start += wl + letterPadding * (letter == ' ' ? 2 : 1);
|
||||||
|
}
|
||||||
|
ol.extent.buffer(extent, 5 * pixelRatio, extent);
|
||||||
|
var bounds = {
|
||||||
|
bottomLeft: ol.extent.getBottomLeft(extent),
|
||||||
|
topRight: ol.extent.getTopRight(extent)
|
||||||
|
};
|
||||||
|
labelEngine.ingestLabel(bounds, fid, 1, letters, text, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var style = new ol.style.Style({
|
||||||
|
geometry: function(feature) {
|
||||||
|
// Use the longest, straight enough segment of the geometry
|
||||||
|
var coords = feature.getGeometry().getCoordinates();
|
||||||
|
/* global linelabel */
|
||||||
|
var segment = linelabel(coords, Math.PI / 8).sort(segmentSort)[0];
|
||||||
|
return new ol.geom.LineString(coords.slice(segment.beginIndex, segment.endIndex));
|
||||||
|
},
|
||||||
|
renderer: function(coords, geometry, feature, state) {
|
||||||
|
var context = state.context;
|
||||||
|
var pixelRatio = state.pixelRatio;
|
||||||
|
var text = feature.get('name');
|
||||||
|
if (text) {
|
||||||
|
// Only consider label when the segment is long enough
|
||||||
|
var labelLength = context.measureText(text).width * pixelRatio;
|
||||||
|
var pathLength = 0;
|
||||||
|
for (var i = 1, ii = coords.length; i < ii; ++i) {
|
||||||
|
pathLength += dist2D(coords[i - 1], coords[i]);
|
||||||
|
if (pathLength >= labelLength) {
|
||||||
|
if (coords[0][0] > coords[coords.length - 1][0]) {
|
||||||
|
// Attempt to make text upright
|
||||||
|
coords.reverse();
|
||||||
|
}
|
||||||
|
textPath(context, text, coords, feature.getId(), pixelRatio);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var rasterLayer = new ol.layer.Tile({
|
||||||
|
source: new ol.source.BingMaps({
|
||||||
|
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5',
|
||||||
|
imagerySet: 'Aerial'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
var source = new ol.source.Vector();
|
||||||
|
// Request streets from OSM, using the Overpass API
|
||||||
|
var client = new XMLHttpRequest();
|
||||||
|
client.open('POST', 'https://overpass-api.de/api/interpreter');
|
||||||
|
client.addEventListener('load', function() {
|
||||||
|
var features = new ol.format.OSMXML().readFeatures(client.responseText, {
|
||||||
|
featureProjection: 'EPSG:3857'
|
||||||
|
});
|
||||||
|
source.addFeatures(features);
|
||||||
|
});
|
||||||
|
client.send('(way["highway"](48.19642,16.32580,48.22050,16.41986));(._;>;);out meta;');
|
||||||
|
|
||||||
|
var vectorLayer = new ol.layer.Vector({
|
||||||
|
source: source,
|
||||||
|
style: function(feature) {
|
||||||
|
if (feature.getGeometry().getType() == 'LineString') {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var extent = [1817379, 6139595, 1827851, 6143616];
|
||||||
|
var map = new ol.Map({
|
||||||
|
layers: [rasterLayer, vectorLayer],
|
||||||
|
target: 'map',
|
||||||
|
view: new ol.View({
|
||||||
|
extent: extent,
|
||||||
|
center: ol.extent.getCenter(extent),
|
||||||
|
zoom: 17,
|
||||||
|
minZoom: 14
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
vectorLayer.on('precompose', function() {
|
||||||
|
labelEngine.destroy();
|
||||||
|
});
|
||||||
|
vectorLayer.on('postcompose', function(e) {
|
||||||
|
var context = e.context;
|
||||||
|
var pixelRatio = e.frameState.pixelRatio;
|
||||||
|
context.save();
|
||||||
|
context.font = 'normal 11px "Open Sans", "Arial Unicode MS"';
|
||||||
|
context.fillStyle = 'white';
|
||||||
|
context.textBaseline = 'middle';
|
||||||
|
context.textAlign = 'center';
|
||||||
|
var labels = labelEngine.getShown();
|
||||||
|
for (var i = 0, ii = labels.length; i < ii; ++i) {
|
||||||
|
// Render label letter by letter
|
||||||
|
var letters = labels[i].labelObject;
|
||||||
|
for (var j = 0, jj = letters.length; j < jj; ++j) {
|
||||||
|
var labelData = letters[j];
|
||||||
|
context.save();
|
||||||
|
context.translate(labelData[0], labelData[1]);
|
||||||
|
context.rotate(labelData[2]);
|
||||||
|
context.scale(pixelRatio, pixelRatio);
|
||||||
|
context.fillText(labelData[3], 0, 0);
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.restore();
|
||||||
|
});
|
||||||
14
examples/vector-label-decluttering.html
Normal file
14
examples/vector-label-decluttering.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: Vector Label Decluttering
|
||||||
|
shortdesc: Label decluttering with a custom renderer.
|
||||||
|
resources:
|
||||||
|
- 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: >
|
||||||
|
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.
|
||||||
|
tags: "vector, renderer, labelgun, label"
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<div id="info"> </div>
|
||||||
130
examples/vector-label-decluttering.js
Normal file
130
examples/vector-label-decluttering.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
// NOCOMPILE
|
||||||
|
goog.require('ol.Map');
|
||||||
|
goog.require('ol.View');
|
||||||
|
goog.require('ol.extent');
|
||||||
|
goog.require('ol.format.GeoJSON');
|
||||||
|
goog.require('ol.geom.Point');
|
||||||
|
goog.require('ol.layer.Vector');
|
||||||
|
goog.require('ol.source.Vector');
|
||||||
|
goog.require('ol.style.Fill');
|
||||||
|
goog.require('ol.style.Stroke');
|
||||||
|
goog.require('ol.style.Style');
|
||||||
|
|
||||||
|
// 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({
|
||||||
|
target: 'map',
|
||||||
|
view: new ol.View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
/*global labelgun*/
|
||||||
|
var labelEngine = new labelgun['default'](function() {}, function() {});
|
||||||
|
|
||||||
|
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 resolution;
|
||||||
|
var styles = [
|
||||||
|
new ol.style.Style({
|
||||||
|
fill: new ol.style.Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 0.6)'
|
||||||
|
}),
|
||||||
|
stroke: new ol.style.Stroke({
|
||||||
|
color: '#319FD3',
|
||||||
|
width: 1
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
new ol.style.Style({
|
||||||
|
renderer: function(coord, geometry, feature, state) {
|
||||||
|
var pixelRatio = state.pixelRatio;
|
||||||
|
var text = feature.get('name');
|
||||||
|
var canvas = textCache[text];
|
||||||
|
if (!canvas) {
|
||||||
|
// Draw the label to its own canvas and cache it.
|
||||||
|
var width = textMeasureContext.measureText(text).width;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
var extentWidth = geometry.getCoordinates()[2] / resolution * pixelRatio;
|
||||||
|
if (extentWidth > canvas.width) {
|
||||||
|
// Only consider labels not wider than their country's bounding box
|
||||||
|
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());
|
||||||
|
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) {
|
||||||
|
resolution = e.frameState.viewState.resolution;
|
||||||
|
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);
|
||||||
@@ -4400,6 +4400,48 @@ olx.layer.VectorTileOptions.prototype.visible;
|
|||||||
olx.render;
|
olx.render;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{context: CanvasRenderingContext2D,
|
||||||
|
* pixelRatio: number,
|
||||||
|
* resolution: number,
|
||||||
|
* rotation: number}}
|
||||||
|
*/
|
||||||
|
olx.render.State;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canvas context that the layer is being rendered to.
|
||||||
|
* @type {CanvasRenderingContext2D}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.render.State.prototype.context;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pixel ratio used by the layer renderer.
|
||||||
|
* @type {number}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.render.State.prototype.pixelRatio;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolution that the render batch was created and optiized for. This is
|
||||||
|
* not the view's resolution that is being rendered.
|
||||||
|
* @type {number}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.render.State.prototype.resolution;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotation of the rendered layer in radians.
|
||||||
|
* @type {number}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.render.State.prototype.rotation;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{size: (ol.Size|undefined),
|
* @typedef {{size: (ol.Size|undefined),
|
||||||
* pixelRatio: (number|undefined)}}
|
* pixelRatio: (number|undefined)}}
|
||||||
@@ -7621,6 +7663,7 @@ olx.style.TextOptions.prototype.stroke;
|
|||||||
* @typedef {{geometry: (undefined|string|ol.geom.Geometry|ol.StyleGeometryFunction),
|
* @typedef {{geometry: (undefined|string|ol.geom.Geometry|ol.StyleGeometryFunction),
|
||||||
* fill: (ol.style.Fill|undefined),
|
* fill: (ol.style.Fill|undefined),
|
||||||
* image: (ol.style.Image|undefined),
|
* image: (ol.style.Image|undefined),
|
||||||
|
* renderer: (ol.StyleRenderFunction|undefined),
|
||||||
* stroke: (ol.style.Stroke|undefined),
|
* stroke: (ol.style.Stroke|undefined),
|
||||||
* text: (ol.style.Text|undefined),
|
* text: (ol.style.Text|undefined),
|
||||||
* zIndex: (number|undefined)}}
|
* zIndex: (number|undefined)}}
|
||||||
@@ -7653,6 +7696,16 @@ olx.style.StyleOptions.prototype.fill;
|
|||||||
olx.style.StyleOptions.prototype.image;
|
olx.style.StyleOptions.prototype.image;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom renderer. When configured, `fill`, `stroke` and `image` will be
|
||||||
|
* ignored, and the provided function will be called with each render frame for
|
||||||
|
* each geometry.
|
||||||
|
*
|
||||||
|
* @type {ol.StyleRenderFunction|undefined}
|
||||||
|
*/
|
||||||
|
olx.style.StyleOptions.prototype.renderer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stroke style.
|
* Stroke style.
|
||||||
* @type {ol.style.Stroke|undefined}
|
* @type {ol.style.Stroke|undefined}
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ 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);
|
||||||
|
|
||||||
@@ -141,6 +147,18 @@ 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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ ol.render.canvas.Instruction = {
|
|||||||
BEGIN_PATH: 1,
|
BEGIN_PATH: 1,
|
||||||
CIRCLE: 2,
|
CIRCLE: 2,
|
||||||
CLOSE_PATH: 3,
|
CLOSE_PATH: 3,
|
||||||
DRAW_IMAGE: 4,
|
CUSTOM: 4,
|
||||||
DRAW_TEXT: 5,
|
DRAW_IMAGE: 5,
|
||||||
END_GEOMETRY: 6,
|
DRAW_TEXT: 6,
|
||||||
FILL: 7,
|
END_GEOMETRY: 7,
|
||||||
MOVE_TO_LINE_TO: 8,
|
FILL: 8,
|
||||||
SET_FILL_STYLE: 9,
|
MOVE_TO_LINE_TO: 9,
|
||||||
SET_STROKE_STYLE: 10,
|
SET_FILL_STYLE: 10,
|
||||||
SET_TEXT_STYLE: 11,
|
SET_STROKE_STYLE: 11,
|
||||||
STROKE: 12
|
SET_TEXT_STYLE: 12,
|
||||||
|
STROKE: 13
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, o
|
|||||||
* currentLineJoin: (string|undefined),
|
* currentLineJoin: (string|undefined),
|
||||||
* currentLineWidth: (number|undefined),
|
* currentLineWidth: (number|undefined),
|
||||||
* currentMiterLimit: (number|undefined),
|
* currentMiterLimit: (number|undefined),
|
||||||
* lastStroke: number,
|
* lastStroke: (number|undefined),
|
||||||
* strokeStyle: (ol.ColorLike|undefined),
|
* strokeStyle: (ol.ColorLike|undefined),
|
||||||
* lineCap: (string|undefined),
|
* lineCap: (string|undefined),
|
||||||
* lineDash: Array.<number>,
|
* lineDash: Array.<number>,
|
||||||
@@ -54,7 +54,7 @@ ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, o
|
|||||||
currentLineJoin: undefined,
|
currentLineJoin: undefined,
|
||||||
currentLineWidth: undefined,
|
currentLineWidth: undefined,
|
||||||
currentMiterLimit: undefined,
|
currentMiterLimit: undefined,
|
||||||
lastStroke: 0,
|
lastStroke: undefined,
|
||||||
strokeStyle: undefined,
|
strokeStyle: undefined,
|
||||||
lineCap: undefined,
|
lineCap: undefined,
|
||||||
lineDash: null,
|
lineDash: null,
|
||||||
@@ -122,10 +122,11 @@ ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() {
|
|||||||
state.currentLineJoin != lineJoin ||
|
state.currentLineJoin != lineJoin ||
|
||||||
state.currentLineWidth != lineWidth ||
|
state.currentLineWidth != lineWidth ||
|
||||||
state.currentMiterLimit != miterLimit) {
|
state.currentMiterLimit != miterLimit) {
|
||||||
if (state.lastStroke != this.coordinates.length) {
|
if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) {
|
||||||
this.instructions.push([ol.render.canvas.Instruction.STROKE]);
|
this.instructions.push([ol.render.canvas.Instruction.STROKE]);
|
||||||
state.lastStroke = this.coordinates.length;
|
state.lastStroke = this.coordinates.length;
|
||||||
}
|
}
|
||||||
|
state.lastStroke = 0;
|
||||||
this.instructions.push([
|
this.instructions.push([
|
||||||
ol.render.canvas.Instruction.SET_STROKE_STYLE,
|
ol.render.canvas.Instruction.SET_STROKE_STYLE,
|
||||||
strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash, lineDashOffset, true, 1
|
strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash, lineDashOffset, true, 1
|
||||||
@@ -208,7 +209,7 @@ ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multi
|
|||||||
*/
|
*/
|
||||||
ol.render.canvas.LineStringReplay.prototype.finish = function() {
|
ol.render.canvas.LineStringReplay.prototype.finish = function() {
|
||||||
var state = this.state_;
|
var state = this.state_;
|
||||||
if (state.lastStroke != this.coordinates.length) {
|
if (state.lastStroke != undefined && state.lastStroke != this.coordinates.length) {
|
||||||
this.instructions.push([ol.render.canvas.Instruction.STROKE]);
|
this.instructions.push([ol.render.canvas.Instruction.STROKE]);
|
||||||
}
|
}
|
||||||
this.reverseHitDetectionInstructions();
|
this.reverseHitDetectionInstructions();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ goog.require('ol');
|
|||||||
goog.require('ol.array');
|
goog.require('ol.array');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
goog.require('ol.extent.Relationship');
|
goog.require('ol.extent.Relationship');
|
||||||
|
goog.require('ol.geom.GeometryType');
|
||||||
|
goog.require('ol.geom.flat.inflate');
|
||||||
goog.require('ol.geom.flat.transform');
|
goog.require('ol.geom.flat.transform');
|
||||||
goog.require('ol.has');
|
goog.require('ol.has');
|
||||||
goog.require('ol.obj');
|
goog.require('ol.obj');
|
||||||
@@ -174,6 +176,75 @@ ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinat
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array.<number>} flatCoordinates Flat coordinates.
|
||||||
|
* @param {number} offset Offset.
|
||||||
|
* @param {Array.<number>} ends Ends.
|
||||||
|
* @param {number} stride Stride.
|
||||||
|
* @param {Array.<number>} replayEnds Replay ends.
|
||||||
|
* @return {number} Offset.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.Replay.prototype.drawCustomCoordinates_ = function(flatCoordinates, offset, ends, stride, replayEnds) {
|
||||||
|
for (var i = 0, ii = ends.length; i < ii; ++i) {
|
||||||
|
var end = ends[i];
|
||||||
|
var replayEnd = this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false, false);
|
||||||
|
replayEnds.push(replayEnd);
|
||||||
|
offset = end;
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.Replay.prototype.drawCustom = function(geometry, feature, renderer) {
|
||||||
|
this.beginGeometry(geometry, feature);
|
||||||
|
var type = geometry.getType();
|
||||||
|
var stride = geometry.getStride();
|
||||||
|
var replayBegin = this.coordinates.length;
|
||||||
|
var flatCoordinates, replayEnd, replayEnds, replayEndss;
|
||||||
|
var offset;
|
||||||
|
if (type == ol.geom.GeometryType.MULTI_POLYGON) {
|
||||||
|
geometry = /** @type {ol.geom.MultiPolygon} */ (geometry);
|
||||||
|
flatCoordinates = geometry.getOrientedFlatCoordinates();
|
||||||
|
replayEndss = [];
|
||||||
|
var endss = geometry.getEndss();
|
||||||
|
offset = 0;
|
||||||
|
for (var i = 0, ii = endss.length; i < ii; ++i) {
|
||||||
|
var myEnds = [];
|
||||||
|
offset = this.drawCustomCoordinates_(flatCoordinates, offset, endss[i], stride, myEnds);
|
||||||
|
replayEndss.push(myEnds);
|
||||||
|
}
|
||||||
|
this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
|
||||||
|
replayBegin, replayEndss, geometry, renderer, ol.geom.flat.inflate.coordinatesss]);
|
||||||
|
} else if (type == ol.geom.GeometryType.POLYGON || type == ol.geom.GeometryType.MULTI_LINE_STRING) {
|
||||||
|
replayEnds = [];
|
||||||
|
flatCoordinates = (type == ol.geom.GeometryType.POLYGON) ?
|
||||||
|
/** @type {ol.geom.Polygon} */ (geometry).getOrientedFlatCoordinates() :
|
||||||
|
geometry.getFlatCoordinates();
|
||||||
|
offset = this.drawCustomCoordinates_(flatCoordinates, 0,
|
||||||
|
/** @type {ol.geom.Polygon|ol.geom.MultiLineString} */ (geometry).getEnds(),
|
||||||
|
stride, replayEnds);
|
||||||
|
this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
|
||||||
|
replayBegin, replayEnds, geometry, renderer, ol.geom.flat.inflate.coordinatess]);
|
||||||
|
} else if (type == ol.geom.GeometryType.LINE_STRING || type == ol.geom.GeometryType.MULTI_POINT) {
|
||||||
|
flatCoordinates = geometry.getFlatCoordinates();
|
||||||
|
replayEnd = this.appendFlatCoordinates(
|
||||||
|
flatCoordinates, 0, flatCoordinates.length, stride, false, false);
|
||||||
|
this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
|
||||||
|
replayBegin, replayEnd, geometry, renderer, ol.geom.flat.inflate.coordinates]);
|
||||||
|
} else if (type == ol.geom.GeometryType.POINT) {
|
||||||
|
flatCoordinates = geometry.getFlatCoordinates();
|
||||||
|
this.coordinates.push(flatCoordinates[0], flatCoordinates[1]);
|
||||||
|
replayEnd = this.coordinates.length;
|
||||||
|
this.instructions.push([ol.render.canvas.Instruction.CUSTOM,
|
||||||
|
replayBegin, replayEnd, geometry, renderer]);
|
||||||
|
}
|
||||||
|
this.endGeometry(geometry, feature);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
|
* @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
|
||||||
@@ -249,6 +320,17 @@ 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {olx.render.State}
|
||||||
|
*/
|
||||||
|
var replayState = {
|
||||||
|
context: context,
|
||||||
|
pixelRatio: pixelRatio,
|
||||||
|
resolution: this.resolution,
|
||||||
|
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.
|
||||||
var batchSize =
|
var batchSize =
|
||||||
@@ -303,6 +385,21 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
context.closePath();
|
context.closePath();
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
|
case ol.render.canvas.Instruction.CUSTOM:
|
||||||
|
d = /** @type {number} */ (instruction[1]);
|
||||||
|
dd = instruction[2];
|
||||||
|
var geometry = /** @type {ol.geom.SimpleGeometry} */ (instruction[3]);
|
||||||
|
var renderer = instruction[4];
|
||||||
|
var coords;
|
||||||
|
if (instruction.length == 6) {
|
||||||
|
var fn = instruction[5];
|
||||||
|
coords = fn(pixelCoordinates, d, dd, 2, geometry.getRenderCoordinates());
|
||||||
|
} else {
|
||||||
|
coords = pixelCoordinates.slice(d, dd);
|
||||||
|
}
|
||||||
|
renderer(coords, geometry, feature, replayState);
|
||||||
|
++i;
|
||||||
|
break;
|
||||||
case ol.render.canvas.Instruction.DRAW_IMAGE:
|
case ol.render.canvas.Instruction.DRAW_IMAGE:
|
||||||
d = /** @type {number} */ (instruction[1]);
|
d = /** @type {number} */ (instruction[1]);
|
||||||
dd = /** @type {number} */ (instruction[2]);
|
dd = /** @type {number} */ (instruction[2]);
|
||||||
|
|||||||
@@ -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.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');
|
||||||
goog.require('ol.render.canvas.PolygonReplay');
|
goog.require('ol.render.canvas.PolygonReplay');
|
||||||
@@ -161,6 +162,24 @@ ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) {
|
|||||||
return arr;
|
return arr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array.<ol.render.ReplayType>} replays Replays.
|
||||||
|
* @return {boolean} Has replays of the provided types.
|
||||||
|
*/
|
||||||
|
ol.render.canvas.ReplayGroup.prototype.hasReplays = function(replays) {
|
||||||
|
for (var zIndex in this.replaysByZIndex_) {
|
||||||
|
var candidates = this.replaysByZIndex_[zIndex];
|
||||||
|
for (var i = 0, ii = replays.length; i < ii; ++i) {
|
||||||
|
if (replays[i] in candidates) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME empty description for jsdoc
|
* FIXME empty description for jsdoc
|
||||||
*/
|
*/
|
||||||
@@ -387,6 +406,7 @@ ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
|
|||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
|
ol.render.canvas.ReplayGroup.BATCH_CONSTRUCTORS_ = {
|
||||||
'Circle': ol.render.canvas.PolygonReplay,
|
'Circle': ol.render.canvas.PolygonReplay,
|
||||||
|
'Default': ol.render.canvas.Replay,
|
||||||
'Image': ol.render.canvas.ImageReplay,
|
'Image': ol.render.canvas.ImageReplay,
|
||||||
'LineString': ol.render.canvas.LineStringReplay,
|
'LineString': ol.render.canvas.LineStringReplay,
|
||||||
'Polygon': ol.render.canvas.PolygonReplay,
|
'Polygon': ol.render.canvas.PolygonReplay,
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ 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>>}
|
||||||
@@ -111,6 +117,18 @@ 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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ ol.render.replay.ORDER = [
|
|||||||
ol.render.ReplayType.CIRCLE,
|
ol.render.ReplayType.CIRCLE,
|
||||||
ol.render.ReplayType.LINE_STRING,
|
ol.render.ReplayType.LINE_STRING,
|
||||||
ol.render.ReplayType.IMAGE,
|
ol.render.ReplayType.IMAGE,
|
||||||
ol.render.ReplayType.TEXT
|
ol.render.ReplayType.TEXT,
|
||||||
|
ol.render.ReplayType.DEFAULT
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ goog.provide('ol.render.ReplayType');
|
|||||||
*/
|
*/
|
||||||
ol.render.ReplayType = {
|
ol.render.ReplayType = {
|
||||||
CIRCLE: 'Circle',
|
CIRCLE: 'Circle',
|
||||||
|
DEFAULT: 'Default',
|
||||||
IMAGE: 'Image',
|
IMAGE: 'Image',
|
||||||
LINE_STRING: 'LineString',
|
LINE_STRING: 'LineString',
|
||||||
POLYGON: 'Polygon',
|
POLYGON: 'Polygon',
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ ol.render.VectorContext = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a geometry with a custom renderer.
|
||||||
|
*
|
||||||
|
* @param {ol.geom.SimpleGeometry} geometry Geometry.
|
||||||
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
|
* @param {Function} renderer Renderer.
|
||||||
|
*/
|
||||||
|
ol.render.VectorContext.prototype.drawCustom = function(geometry, feature, renderer) {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a geometry.
|
* Render a geometry.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
|
|||||||
* @type {!Object.<string, Array.<ol.render.ReplayType>>}
|
* @type {!Object.<string, Array.<ol.render.ReplayType>>}
|
||||||
*/
|
*/
|
||||||
ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = {
|
ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = {
|
||||||
'image': ol.render.replay.ORDER,
|
'image': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.CIRCLE,
|
||||||
|
ol.render.ReplayType.LINE_STRING, ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
|
||||||
'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
|
'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,7 +72,8 @@ ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS = {
|
|||||||
* @type {!Object.<string, Array.<ol.render.ReplayType>>}
|
* @type {!Object.<string, Array.<ol.render.ReplayType>>}
|
||||||
*/
|
*/
|
||||||
ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = {
|
ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS = {
|
||||||
'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
|
'image': [ol.render.ReplayType.DEFAULT],
|
||||||
|
'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT, ol.render.ReplayType.DEFAULT],
|
||||||
'vector': ol.render.replay.ORDER
|
'vector': ol.render.replay.ORDER
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -332,61 +334,62 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra
|
|||||||
var source = layer.getSource();
|
var source = layer.getSource();
|
||||||
var renderMode = layer.getRenderMode();
|
var renderMode = layer.getRenderMode();
|
||||||
var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
|
var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
|
||||||
if (replays) {
|
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);
|
var tiles = this.renderedTiles;
|
||||||
var tiles = this.renderedTiles;
|
var tilePixelRatio = layer.getSource().getTilePixelRatio();
|
||||||
var tilePixelRatio = layer.getSource().getTilePixelRatio();
|
var sourceTileGrid = source.getTileGrid();
|
||||||
var sourceTileGrid = source.getTileGrid();
|
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
||||||
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
var clips = [];
|
||||||
var clips = [];
|
var zs = [];
|
||||||
var zs = [];
|
for (var i = tiles.length - 1; i >= 0; --i) {
|
||||||
for (var i = tiles.length - 1; i >= 0; --i) {
|
var tile = /** @type {ol.VectorImageTile} */ (tiles[i]);
|
||||||
var tile = /** @type {ol.VectorImageTile} */ (tiles[i]);
|
if (tile.getState() == ol.TileState.ABORT) {
|
||||||
if (tile.getState() == ol.TileState.ABORT) {
|
continue;
|
||||||
|
}
|
||||||
|
var tileCoord = tile.tileCoord;
|
||||||
|
var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
|
||||||
|
tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
|
||||||
|
for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
||||||
|
var sourceTile = tile.getTile(tile.tileKeys[t]);
|
||||||
|
var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString());
|
||||||
|
if (renderMode != ol.layer.VectorTileRenderType.VECTOR && !replayGroup.hasReplays(replays)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var tileCoord = tile.tileCoord;
|
var currentZ = sourceTile.tileCoord[0];
|
||||||
var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
|
var sourceResolution = sourceTileGrid.getResolution(currentZ);
|
||||||
tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
|
var transform = this.getReplayTransform_(sourceTile, frameState);
|
||||||
for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) {
|
ol.transform.translate(transform, worldOffset * tilePixelRatio / sourceResolution, 0);
|
||||||
var sourceTile = tile.getTile(tile.tileKeys[t]);
|
var currentClip = replayGroup.getClipCoords(transform);
|
||||||
var currentZ = sourceTile.tileCoord[0];
|
context.save();
|
||||||
var sourceResolution = sourceTileGrid.getResolution(currentZ);
|
context.globalAlpha = layerState.opacity;
|
||||||
var transform = this.getReplayTransform_(sourceTile, frameState);
|
ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
|
||||||
ol.transform.translate(transform, worldOffset * tilePixelRatio / sourceResolution, 0);
|
// Create a clip mask for regions in this low resolution tile that are
|
||||||
var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString());
|
// already filled by a higher resolution tile
|
||||||
var currentClip = replayGroup.getClipCoords(transform);
|
for (var j = 0, jj = clips.length; j < jj; ++j) {
|
||||||
context.save();
|
var clip = clips[j];
|
||||||
context.globalAlpha = layerState.opacity;
|
if (currentZ < zs[j]) {
|
||||||
ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
|
context.beginPath();
|
||||||
// Create a clip mask for regions in this low resolution tile that are
|
// counter-clockwise (outer ring) for current tile
|
||||||
// already filled by a higher resolution tile
|
context.moveTo(currentClip[0], currentClip[1]);
|
||||||
for (var j = 0, jj = clips.length; j < jj; ++j) {
|
context.lineTo(currentClip[2], currentClip[3]);
|
||||||
var clip = clips[j];
|
context.lineTo(currentClip[4], currentClip[5]);
|
||||||
if (currentZ < zs[j]) {
|
context.lineTo(currentClip[6], currentClip[7]);
|
||||||
context.beginPath();
|
// clockwise (inner ring) for higher resolution tile
|
||||||
// counter-clockwise (outer ring) for current tile
|
context.moveTo(clip[6], clip[7]);
|
||||||
context.moveTo(currentClip[0], currentClip[1]);
|
context.lineTo(clip[4], clip[5]);
|
||||||
context.lineTo(currentClip[2], currentClip[3]);
|
context.lineTo(clip[2], clip[3]);
|
||||||
context.lineTo(currentClip[4], currentClip[5]);
|
context.lineTo(clip[0], clip[1]);
|
||||||
context.lineTo(currentClip[6], currentClip[7]);
|
context.clip();
|
||||||
// clockwise (inner ring) for higher resolution tile
|
|
||||||
context.moveTo(clip[6], clip[7]);
|
|
||||||
context.lineTo(clip[4], clip[5]);
|
|
||||||
context.lineTo(clip[2], clip[3]);
|
|
||||||
context.lineTo(clip[0], clip[1]);
|
|
||||||
context.clip();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays);
|
|
||||||
context.restore();
|
|
||||||
clips.push(currentClip);
|
|
||||||
zs.push(currentZ);
|
|
||||||
}
|
}
|
||||||
|
replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays);
|
||||||
|
context.restore();
|
||||||
|
clips.push(currentClip);
|
||||||
|
zs.push(currentZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
|
ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
|
||||||
@@ -466,7 +469,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
|
|||||||
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, pixelRatio, transform, 0, {}, replays);
|
replayGroup.replay(context, pixelRatio, transform, 0, {}, replays, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ goog.provide('ol.renderer.vector');
|
|||||||
|
|
||||||
goog.require('ol');
|
goog.require('ol');
|
||||||
goog.require('ol.ImageState');
|
goog.require('ol.ImageState');
|
||||||
|
goog.require('ol.geom.GeometryType');
|
||||||
goog.require('ol.render.ReplayType');
|
goog.require('ol.render.ReplayType');
|
||||||
|
|
||||||
|
|
||||||
@@ -111,9 +112,34 @@ ol.renderer.vector.renderFeature_ = function(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
|
var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
|
||||||
var geometryRenderer =
|
var renderer = style.getRenderer();
|
||||||
ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
|
if (renderer) {
|
||||||
geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
|
ol.renderer.vector.renderGeometry_(replayGroup, simplifiedGeometry, style, feature);
|
||||||
|
} else {
|
||||||
|
var geometryRenderer =
|
||||||
|
ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
|
||||||
|
geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ol.render.ReplayGroup} replayGroup Replay group.
|
||||||
|
* @param {ol.geom.Geometry} geometry Geometry.
|
||||||
|
* @param {ol.style.Style} style Style.
|
||||||
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.renderer.vector.renderGeometry_ = function(replayGroup, geometry, style, feature) {
|
||||||
|
if (geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
|
||||||
|
var geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
|
||||||
|
for (var i = 0, ii = geometries.length; i < ii; ++i) {
|
||||||
|
ol.renderer.vector.renderGeometry_(replayGroup, geometries[i], style, feature);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var replay = replayGroup.getReplay(style.getZIndex(), ol.render.ReplayType.DEFAULT);
|
||||||
|
replay.drawCustom(/** @type {ol.geom.SimpleGeometry} */ (geometry), feature, style.getRenderer());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ ol.style.Style = function(opt_options) {
|
|||||||
*/
|
*/
|
||||||
this.image_ = options.image !== undefined ? options.image : null;
|
this.image_ = options.image !== undefined ? options.image : null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {ol.StyleRenderFunction|null}
|
||||||
|
*/
|
||||||
|
this.renderer_ = options.renderer !== undefined ? options.renderer : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {ol.style.Stroke}
|
* @type {ol.style.Stroke}
|
||||||
@@ -92,6 +98,28 @@ ol.style.Style.prototype.clone = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the custom renderer function that was configured with
|
||||||
|
* {@link #setRenderer} or the `renderer` constructor option.
|
||||||
|
* @return {ol.StyleRenderFunction|null} Custom renderer function.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
ol.style.Style.prototype.getRenderer = function() {
|
||||||
|
return this.renderer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a custom renderer function for this style. When set, `fill`, `stroke`
|
||||||
|
* and `image` optins of the style will be ignored.
|
||||||
|
* @param {ol.StyleRenderFunction|null} renderer Custom renderer function.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
ol.style.Style.prototype.setRenderer = function(renderer) {
|
||||||
|
this.renderer_ = renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the geometry to be rendered.
|
* Get the geometry to be rendered.
|
||||||
* @return {string|ol.geom.Geometry|ol.StyleGeometryFunction}
|
* @return {string|ol.geom.Geometry|ol.StyleGeometryFunction}
|
||||||
|
|||||||
@@ -626,6 +626,19 @@ ol.StyleFunction;
|
|||||||
ol.StyleGeometryFunction;
|
ol.StyleGeometryFunction;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom renderer function. Takes 4 arguments:
|
||||||
|
*
|
||||||
|
* 1. The pixel coordinates of the geometry in GeoJSON notation.
|
||||||
|
* 2. The original {@link ol.geom.SimpleGeometry}.
|
||||||
|
* 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)}
|
||||||
|
*/
|
||||||
|
ol.StyleRenderFunction;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{opacity: number,
|
* @typedef {{opacity: number,
|
||||||
* rotateWithView: boolean,
|
* rotateWithView: boolean,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ var markupRegEx = /([^\/^\.]*)\.html$/;
|
|||||||
var cleanupJSRegEx = /.*(\/\/ NOCOMPILE|goog\.require\(.*\);)[\r\n]*/g;
|
var cleanupJSRegEx = /.*(\/\/ NOCOMPILE|goog\.require\(.*\);)[\r\n]*/g;
|
||||||
var requiresRegEx = /.*goog\.require\('(ol\.\S*)'\);/g;
|
var requiresRegEx = /.*goog\.require\('(ol\.\S*)'\);/g;
|
||||||
var isCssRegEx = /\.css$/;
|
var isCssRegEx = /\.css$/;
|
||||||
var isJsRegEx = /\.js$/;
|
var isJsRegEx = /\.js(\?.*)?$/;
|
||||||
|
|
||||||
var srcDir = path.join(__dirname, '..', 'examples');
|
var srcDir = path.join(__dirname, '..', 'examples');
|
||||||
var destDir = path.join(__dirname, '..', 'build', 'examples');
|
var destDir = path.join(__dirname, '..', 'build', 'examples');
|
||||||
|
|||||||
@@ -205,6 +205,53 @@ describe('ol.render.canvas.ReplayGroup', function() {
|
|||||||
expect(style2.getStroke().getLineDashOffset()).to.be(2);
|
expect(style2.getStroke().getLineDashOffset()).to.be(2);
|
||||||
expect(lineDashOffset).to.be(4);
|
expect(lineDashOffset).to.be(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('calls the renderer function configured for the style', function() {
|
||||||
|
var spy = sinon.spy();
|
||||||
|
var style = new ol.style.Style({
|
||||||
|
renderer: spy
|
||||||
|
});
|
||||||
|
var point = new ol.Feature(new ol.geom.Point([45, 90]));
|
||||||
|
var multipoint = new ol.Feature(new ol.geom.MultiPoint(
|
||||||
|
[[45, 90], [90, 45]]));
|
||||||
|
var linestring = new ol.Feature(new ol.geom.LineString(
|
||||||
|
[[45, 90], [45, 45], [90, 45]]));
|
||||||
|
var multilinestring = new ol.Feature(new ol.geom.MultiLineString(
|
||||||
|
[linestring.getGeometry().getCoordinates(), linestring.getGeometry().getCoordinates()]));
|
||||||
|
var polygon = feature1;
|
||||||
|
var multipolygon = new ol.Feature(new ol.geom.MultiPolygon(
|
||||||
|
[polygon.getGeometry().getCoordinates(), polygon.getGeometry().getCoordinates()]));
|
||||||
|
var geometrycollection = new ol.Feature(new ol.geom.GeometryCollection(
|
||||||
|
[point.getGeometry(), linestring.getGeometry(), polygon.getGeometry()]));
|
||||||
|
replay = new ol.render.canvas.ReplayGroup(1, [-180, -90, 180, 90], 1, true);
|
||||||
|
ol.renderer.vector.renderFeature(replay, point, style, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, multipoint, style, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, linestring, style, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, multilinestring, style, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, polygon, style, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, multipolygon, style, 1);
|
||||||
|
ol.renderer.vector.renderFeature(replay, geometrycollection, style, 1);
|
||||||
|
ol.transform.scale(transform, 0.1, 0.1);
|
||||||
|
replay.replay(context, 1, transform, 0, {});
|
||||||
|
expect(spy.callCount).to.be(9);
|
||||||
|
expect(spy.firstCall.args.length).to.be(4);
|
||||||
|
expect(spy.firstCall.args[1]).to.be(point.getGeometry());
|
||||||
|
expect(spy.firstCall.args[2]).to.be(point);
|
||||||
|
expect(spy.firstCall.args[3].context).to.be(context);
|
||||||
|
expect(spy.firstCall.args[3].pixelRatio).to.be(1);
|
||||||
|
expect(spy.firstCall.args[3].rotation).to.be(0);
|
||||||
|
expect(spy.firstCall.args[3].resolution).to.be(1);
|
||||||
|
expect(spy.getCall(0).args[0]).to.eql([4.5, 9]);
|
||||||
|
expect(spy.getCall(1).args[0][0]).to.eql([4.5, 9]);
|
||||||
|
expect(spy.getCall(2).args[0][0]).to.eql([4.5, 9]);
|
||||||
|
expect(spy.getCall(3).args[0][0][0]).to.eql([4.5, 9]);
|
||||||
|
expect(spy.getCall(4).args[0][0][0]).to.eql([-9, -4.5]);
|
||||||
|
expect(spy.getCall(5).args[0][0][0][0]).to.eql([-9, -4.5]);
|
||||||
|
expect(spy.getCall(6).args[2]).to.be(geometrycollection);
|
||||||
|
expect(spy.getCall(6).args[1].getCoordinates()).to.eql([45, 90]);
|
||||||
|
expect(spy.getCall(7).args[1].getCoordinates()[0]).to.eql([45, 90]);
|
||||||
|
expect(spy.getCall(8).args[1].getCoordinates()[0][0]).to.eql([-90, -45]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,6 +121,18 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
spy2.restore();
|
spy2.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders replays with custom renderers as direct replays', function() {
|
||||||
|
layer.renderMode_ = 'image';
|
||||||
|
layer.setStyle(new ol.style.Style({
|
||||||
|
renderer: function() {}
|
||||||
|
}));
|
||||||
|
var spy = sinon.spy(ol.renderer.canvas.VectorTileLayer.prototype,
|
||||||
|
'getReplayTransform_');
|
||||||
|
map.renderSync();
|
||||||
|
expect(spy.callCount).to.be(1);
|
||||||
|
spy.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('gives precedence to feature styles over layer styles', function() {
|
it('gives precedence to feature styles over layer styles', function() {
|
||||||
var spy = sinon.spy(map.getRenderer().getLayerRenderer(layer),
|
var spy = sinon.spy(map.getRenderer().getLayerRenderer(layer),
|
||||||
'renderFeature');
|
'renderFeature');
|
||||||
|
|||||||
Reference in New Issue
Block a user