Compare commits

..

19 Commits

Author SHA1 Message Date
ahocevar
a8b5dfa981 6.0.0-beta.15 2019-08-20 11:18:27 +02:00
Andreas Hocevar
42f1a7ed30 Merge pull request #9873 from ahocevar/another-vectortile-load-fix
Only check extent when a url tile coordinate is available
2019-08-20 11:08:04 +02:00
Andreas Hocevar
f3d6456876 Merge pull request #9876 from openlayers/greenkeeper/yargs-14.0.0
Update yargs to the latest version 🚀
2019-08-20 08:36:49 +02:00
greenkeeper[bot]
2ca13de4de chore(package): update lockfile package-lock.json 2019-08-19 23:21:53 +00:00
greenkeeper[bot]
87ae93ca2d chore(package): update yargs to version 14.0.0 2019-08-19 23:21:44 +00:00
ahocevar
5da32dbc5b More tests 2019-08-19 11:39:39 +02:00
ahocevar
6be2818f14 Handle empty url tile coord (no wrapx) 2019-08-19 11:01:03 +02:00
Andreas Hocevar
b602a6b33a Merge pull request #9871 from ahocevar/tile-load-key-change
Properly handle tile source key change
2019-08-19 08:08:31 +02:00
Andreas Hocevar
20a81ceb2f Merge pull request #9870 from ahocevar/declutter-multi-geometries
Declutter multi geometries per geometry instead of per feature
2019-08-19 08:06:39 +02:00
Andreas Hocevar
3d6cf24c26 Merge pull request #9872 from ahocevar/wrapx-extent
Check extent for wrapped tile coordinate
2019-08-18 18:10:24 +02:00
ahocevar
6a741d0504 Check extent for wrapped tile coordinate 2019-08-18 16:21:46 +02:00
ahocevar
aa55cce3ba Add rendering test for decluttered multipolygons 2019-08-18 15:43:41 +02:00
ahocevar
a5fbbef970 Always go through source tile change logic 2019-08-18 11:44:20 +02:00
ahocevar
25c8d93eba Fix test 2019-08-18 11:44:20 +02:00
Tim Schaub
eb294c78d1 Failing test 2019-08-18 08:58:59 +02:00
ahocevar
bd3f35eef0 Declutter multi geometries per geometry instead of per feature 2019-08-17 23:55:20 +02:00
Tim Schaub
f839b34594 Merge pull request #9869 from MoonE/master
Only get squared tolerance once per render
2019-08-17 07:23:22 -06:00
Tim Schaub
99462d3b53 Only get squared tolerance once per render 2019-08-17 07:10:10 -06:00
Maximilian Krög
200392785d Squared tolerance does not change for each style. 2019-08-17 11:15:06 +02:00
16 changed files with 337 additions and 170 deletions

View File

@@ -5,7 +5,7 @@ shortdesc: Label decluttering with a custom renderer.
resources:
- https://cdn.polyfill.io/v2/polyfill.min.js?features=Set"
docs: >
Decluttering is used to avoid overlapping labels with `overflow: true` set on the text style. For MultiPolygon geometries, only the widest polygon is selected in a custom `geometry` function.
Decluttering is used to avoid overlapping labels. The `overflow: true` setting on the text style makes it so labels that do not fit within the bounds of a polygon are also included.
tags: "vector, decluttering, labels"
---
<div id="map" class="map"></div>

View File

@@ -1,6 +1,5 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {getWidth} from '../src/ol/extent.js';
import GeoJSON from '../src/ol/format/GeoJSON.js';
import VectorLayer from '../src/ol/layer/Vector.js';
import VectorSource from '../src/ol/source/Vector.js';
@@ -15,23 +14,6 @@ const map = new Map({
});
const labelStyle = new Style({
geometry: function(feature) {
let geometry = feature.getGeometry();
if (geometry.getType() == 'MultiPolygon') {
// Only render label for the widest polygon of a multipolygon
const polygons = geometry.getPolygons();
let widest = 0;
for (let i = 0, ii = polygons.length; i < ii; ++i) {
const polygon = polygons[i];
const width = getWidth(polygon.getExtent());
if (width > widest) {
widest = width;
geometry = polygon;
}
}
}
return geometry;
},
text: new Text({
font: '12px Calibri,sans-serif',
overflow: true,

95
package-lock.json generated
View File

@@ -7293,6 +7293,12 @@
"integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
"dev": true
},
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
@@ -7316,6 +7322,26 @@
"path-is-absolute": "^1.0.0"
}
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
},
"supports-color": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
@@ -7324,6 +7350,25 @@
"requires": {
"has-flag": "^3.0.0"
}
},
"yargs": {
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
"integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==",
"dev": true,
"requires": {
"cliui": "^4.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"os-locale": "^3.1.0",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.0.0"
}
}
}
},
@@ -11068,22 +11113,22 @@
"dev": true
},
"yargs": {
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
"integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==",
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.0.0.tgz",
"integrity": "sha512-ssa5JuRjMeZEUjg7bEL99AwpitxU/zWGAGpdj0di41pOEmJti8NR6kyUIJBkR78DTYNPZOU08luUo0GTHuB+ow==",
"dev": true,
"requires": {
"cliui": "^4.0.0",
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"os-locale": "^3.1.0",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.0.0"
"yargs-parser": "^13.1.1"
},
"dependencies": {
"ansi-regex": {
@@ -11092,6 +11137,23 @@
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
}
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
@@ -11111,6 +11173,27 @@
"requires": {
"ansi-regex": "^4.1.0"
}
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}
},
"yargs-parser": {
"version": "13.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
"integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "ol",
"version": "6.0.0-beta.14",
"version": "6.0.0-beta.15",
"description": "OpenLayers mapping library",
"keywords": [
"map",
@@ -101,7 +101,7 @@
"webpack-cli": "^3.3.2",
"webpack-dev-middleware": "^3.6.2",
"webpack-dev-server": "^3.3.1",
"yargs": "^13.2.2"
"yargs": "^14.0.0"
},
"eslintConfig": {
"extends": "openlayers",

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,55 @@
import Map from '../../../src/ol/Map.js';
import View from '../../../src/ol/View.js';
import GeoJSON from '../../../src/ol/format/GeoJSON.js';
import VectorLayer from '../../../src/ol/layer/Vector.js';
import VectorSource from '../../../src/ol/source/Vector.js';
import {Fill, Stroke, Style, Text} from '../../../src/ol/style.js';
const map = new Map({
target: 'map',
view: new View({
center: [-17465028, 2331736],
zoom: 5
})
});
const labelStyle = new Style({
text: new Text({
font: '16px Ubuntu',
overflow: true,
fill: new Fill({
color: '#000'
}),
stroke: new Stroke({
color: '#fff',
width: 3
})
})
});
const countryStyle = new Style({
fill: new Fill({
color: 'rgba(255, 255, 255, 0.6)'
}),
stroke: new Stroke({
color: '#319FD3',
width: 1
})
});
const style = [countryStyle, labelStyle];
const vectorLayer = new VectorLayer({
source: new VectorSource({
url: '/data/countries.json',
format: new GeoJSON()
}),
style: function(feature) {
labelStyle.getText().setText(feature.get('name'));
return style;
},
declutter: true
});
map.addLayer(vectorLayer);
render({tolerance: 0.007});

View File

@@ -106,9 +106,9 @@ class VectorContext {
/**
* @param {import("../style/Text.js").default} textStyle Text style.
* @param {import("./canvas.js").DeclutterGroup=} opt_declutterGroup Declutter.
* @param {import("./canvas.js").DeclutterGroups=} opt_declutterGroups Declutter.
*/
setTextStyle(textStyle, opt_declutterGroup) {}
setTextStyle(textStyle, opt_declutterGroups) {}
}
export default VectorContext;

View File

@@ -62,7 +62,6 @@ import LabelCache from './canvas/LabelCache.js';
* @property {Array<number>} [padding]
*/
/**
* 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
@@ -76,6 +75,12 @@ import LabelCache from './canvas/LabelCache.js';
*/
/**
* Declutter groups for support of multi geometries.
* @typedef {Array<DeclutterGroup>} DeclutterGroups
*/
/**
* @const
* @type {string}

View File

@@ -40,10 +40,10 @@ class BuilderGroup {
this.declutter_ = declutter;
/**
* @type {import("../canvas.js").DeclutterGroup}
* @type {import("../canvas.js").DeclutterGroups}
* @private
*/
this.declutterGroup_ = null;
this.declutterGroups_ = null;
/**
* @private
@@ -78,17 +78,17 @@ class BuilderGroup {
/**
* @param {boolean} group Group with previous builder.
* @return {Array<*>} The resulting instruction group.
* @return {import("../canvas").DeclutterGroups} The resulting instruction groups.
*/
addDeclutter(group) {
let declutter = null;
if (this.declutter_) {
if (group) {
declutter = this.declutterGroup_;
/** @type {number} */ (declutter[4])++;
declutter = this.declutterGroups_;
/** @type {number} */ (declutter[0][4])++;
} else {
declutter = this.declutterGroup_ = createEmpty();
declutter.push(1);
declutter = this.declutterGroups_ = [createEmpty()];
declutter[0].push(1);
}
}
return declutter;

View File

@@ -535,7 +535,7 @@ class Executor extends Disposable {
const ii = instructions.length; // end of instructions
let d = 0; // data index
let dd; // end of per-instruction data
let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, image, text, textKey;
let anchorX, anchorY, prevX, prevY, roundX, roundY, declutterGroup, declutterGroups, image, text, textKey;
let strokeKey, fillKey;
let pendingFill = 0;
let pendingStroke = 0;
@@ -633,7 +633,7 @@ class Executor extends Disposable {
// Remaining arguments in DRAW_IMAGE are in alphabetical order
anchorX = /** @type {number} */ (instruction[4]);
anchorY = /** @type {number} */ (instruction[5]);
declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[6]);
declutterGroups = featureCallback ? null : instruction[6];
let height = /** @type {number} */ (instruction[7]);
const opacity = /** @type {number} */ (instruction[8]);
const originX = /** @type {number} */ (instruction[9]);
@@ -643,7 +643,6 @@ class Executor extends Disposable {
const scale = /** @type {number} */ (instruction[13]);
let width = /** @type {number} */ (instruction[14]);
if (!image && instruction.length >= 19) {
// create label images
text = /** @type {string} */ (instruction[18]);
@@ -679,25 +678,41 @@ class Executor extends Disposable {
rotation += viewRotation;
}
let widthIndex = 0;
let declutterGroupIndex = 0;
for (; d < dd; d += 2) {
if (geometryWidths && geometryWidths[widthIndex++] < width / this.pixelRatio) {
continue;
}
if (declutterGroups) {
const index = Math.floor(declutterGroupIndex);
if (declutterGroups.length < index + 1) {
declutterGroup = createEmpty();
declutterGroup.push(declutterGroups[0][4]);
declutterGroups.push(declutterGroup);
}
declutterGroup = declutterGroups[index];
}
this.replayImage_(context,
pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
declutterGroup, height, opacity, originX, originY, rotation, scale,
snapToPixel, width, padding,
backgroundFill ? /** @type {Array<*>} */ (lastFillInstruction) : null,
backgroundStroke ? /** @type {Array<*>} */ (lastStrokeInstruction) : null);
if (declutterGroup) {
if (declutterGroupIndex === Math.floor(declutterGroupIndex)) {
this.declutterItems.push(this, declutterGroup, feature);
}
declutterGroupIndex += 1 / declutterGroup[4];
}
}
this.declutterItems.push(this, declutterGroup, feature);
++i;
break;
case CanvasInstruction.DRAW_CHARS:
const begin = /** @type {number} */ (instruction[1]);
const end = /** @type {number} */ (instruction[2]);
const baseline = /** @type {number} */ (instruction[3]);
declutterGroup = featureCallback ? null : /** @type {import("../canvas.js").DeclutterGroup} */ (instruction[4]);
declutterGroup = featureCallback ? null : instruction[4];
const overflow = /** @type {number} */ (instruction[5]);
fillKey = /** @type {string} */ (instruction[6]);
const maxAngle = /** @type {number} */ (instruction[7]);

View File

@@ -16,9 +16,9 @@ class CanvasImageBuilder extends CanvasBuilder {
/**
* @private
* @type {import("../canvas.js").DeclutterGroup}
* @type {import("../canvas.js").DeclutterGroups}
*/
this.declutterGroup_ = null;
this.declutterGroups_ = null;
/**
* @private
@@ -121,14 +121,14 @@ class CanvasImageBuilder extends CanvasBuilder {
this.instructions.push([
CanvasInstruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
this.anchorX_, this.anchorY_, this.declutterGroups_, this.height_, this.opacity_,
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
this.scale_ * this.pixelRatio, this.width_
]);
this.hitDetectionInstructions.push([
CanvasInstruction.DRAW_IMAGE, myBegin, myEnd, this.hitDetectionImage_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
this.anchorX_, this.anchorY_, this.declutterGroups_, this.height_, this.opacity_,
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
this.scale_, this.width_
]);
@@ -151,14 +151,14 @@ class CanvasImageBuilder extends CanvasBuilder {
this.instructions.push([
CanvasInstruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
this.anchorX_, this.anchorY_, this.declutterGroups_, this.height_, this.opacity_,
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
this.scale_ * this.pixelRatio, this.width_
]);
this.hitDetectionInstructions.push([
CanvasInstruction.DRAW_IMAGE, myBegin, myEnd, this.hitDetectionImage_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorY_, this.declutterGroup_, this.height_, this.opacity_,
this.anchorX_, this.anchorY_, this.declutterGroups_, this.height_, this.opacity_,
this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
this.scale_, this.width_
]);
@@ -189,7 +189,7 @@ class CanvasImageBuilder extends CanvasBuilder {
/**
* @inheritDoc
*/
setImageStyle(imageStyle, declutterGroup) {
setImageStyle(imageStyle, declutterGroups) {
const anchor = imageStyle.getAnchor();
const size = imageStyle.getSize();
const hitDetectionImage = imageStyle.getHitDetectionImage(1);
@@ -197,7 +197,7 @@ class CanvasImageBuilder extends CanvasBuilder {
const origin = imageStyle.getOrigin();
this.anchorX_ = anchor[0];
this.anchorY_ = anchor[1];
this.declutterGroup_ = /** @type {import("../canvas.js").DeclutterGroup} */ (declutterGroup);
this.declutterGroups_ = /** @type {import("../canvas.js").DeclutterGroups} */ (declutterGroups);
this.hitDetectionImage_ = hitDetectionImage;
this.image_ = image;
this.height_ = size[1];

View File

@@ -40,9 +40,9 @@ class CanvasTextBuilder extends CanvasBuilder {
/**
* @private
* @type {import("../canvas.js").DeclutterGroup}
* @type {import("../canvas.js").DeclutterGroups}
*/
this.declutterGroup_;
this.declutterGroups_;
/**
* @private
@@ -201,7 +201,10 @@ class CanvasTextBuilder extends CanvasBuilder {
}
end = this.coordinates.length;
flatOffset = ends[o];
this.drawChars_(begin, end, this.declutterGroup_);
const declutterGroup = this.declutterGroups_ ?
(o === 0 ? this.declutterGroups_[0] : [].concat(this.declutterGroups_[0])) :
null;
this.drawChars_(begin, end, declutterGroup);
begin = end;
}
this.endGeometry(feature);
@@ -274,7 +277,7 @@ class CanvasTextBuilder extends CanvasBuilder {
// render time.
const pixelRatio = this.pixelRatio;
this.instructions.push([CanvasInstruction.DRAW_IMAGE, begin, end,
null, NaN, NaN, this.declutterGroup_, NaN, 1, 0, 0,
null, NaN, NaN, this.declutterGroups_, NaN, 1, 0, 0,
this.textRotateWithView_, this.textRotation_, 1, NaN,
textState.padding == defaultPadding ?
defaultPadding : textState.padding.map(function(p) {
@@ -285,7 +288,7 @@ class CanvasTextBuilder extends CanvasBuilder {
this.textOffsetX_, this.textOffsetY_, geometryWidths
]);
this.hitDetectionInstructions.push([CanvasInstruction.DRAW_IMAGE, begin, end,
null, NaN, NaN, this.declutterGroup_, NaN, 1, 0, 0,
null, NaN, NaN, this.declutterGroups_, NaN, 1, 0, 0,
this.textRotateWithView_, this.textRotation_, 1 / this.pixelRatio, NaN,
textState.padding,
!!textState.backgroundFill, !!textState.backgroundStroke,
@@ -379,12 +382,12 @@ class CanvasTextBuilder extends CanvasBuilder {
/**
* @inheritDoc
*/
setTextStyle(textStyle, declutterGroup) {
setTextStyle(textStyle, declutterGroups) {
let textState, fillState, strokeState;
if (!textStyle) {
this.text_ = '';
} else {
this.declutterGroup_ = /** @type {import("../canvas.js").DeclutterGroup} */ (declutterGroup);
this.declutterGroups_ = /** @type {import("../canvas.js").DeclutterGroups} */ (declutterGroups);
const textFillStyle = textStyle.getFill();
if (!textFillStyle) {

View File

@@ -299,6 +299,8 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
vectorSource.loadFeatures(extent, resolution, projection);
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);
/**
* @param {import("../../Feature.js").default} feature Feature.
* @this {CanvasVectorLayerRenderer}
@@ -310,11 +312,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
styles = styleFunction(feature, resolution);
}
if (styles) {
const dirty = this.renderFeature(
feature, resolution, pixelRatio, styles, replayGroup);
const dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup);
this.dirty_ = this.dirty_ || dirty;
}
}.bind(this);
if (vectorLayerRenderOrder) {
/** @type {Array<import("../../Feature.js").default>} */
const features = [];
@@ -350,13 +352,12 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
/**
* @param {import("../../Feature.js").default} feature Feature.
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @param {number} squaredTolerance Squared render tolerance.
* @param {import("../../style/Style.js").default|Array<import("../../style/Style.js").default>} styles The style or array of styles.
* @param {import("../../render/canvas/BuilderGroup.js").default} builderGroup Builder group.
* @return {boolean} `true` if an image is loading.
*/
renderFeature(feature, resolution, pixelRatio, styles, builderGroup) {
renderFeature(feature, squaredTolerance, styles, builderGroup) {
if (!styles) {
return false;
}
@@ -364,14 +365,12 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
if (Array.isArray(styles)) {
for (let i = 0, ii = styles.length; i < ii; ++i) {
loading = renderFeature(
builderGroup, feature, styles[i],
getSquaredRenderTolerance(resolution, pixelRatio),
builderGroup, feature, styles[i], squaredTolerance,
this.handleStyleImageChange_, this) || loading;
}
} else {
loading = renderFeature(
builderGroup, feature, styles,
getSquaredRenderTolerance(resolution, pixelRatio),
builderGroup, feature, styles, squaredTolerance,
this.handleStyleImageChange_, this);
}
return loading;

View File

@@ -200,74 +200,78 @@ class VectorTile extends UrlTile {
const minZoom = sourceTileGrid.getMinZoom();
const previousSourceTiles = this.sourceTilesByTileKey_[tile.getKey()];
let sourceTiles, covered, empty, loadedZ;
if (previousSourceTiles && previousSourceTiles.length > 0 && previousSourceTiles[0].tileCoord[0] === sourceZ) {
return previousSourceTiles;
}
const sourceTiles = [];
let loadedZ = sourceZ + 1;
let covered, empty;
do {
--loadedZ;
sourceTiles = previousSourceTiles;
covered = true;
empty = true;
sourceTileGrid.forEachTileCoord(extent, loadedZ, function(sourceTileCoord) {
const coordKey = getKey(sourceTileCoord);
let sourceTile;
if (coordKey in this.sourceTileByCoordKey_) {
sourceTile = this.sourceTileByCoordKey_[coordKey];
const state = sourceTile.getState();
if (state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
empty = empty && state === TileState.EMPTY;
sourceTiles.push(sourceTile);
empty = false;
loadedZ = sourceZ;
} else {
sourceTiles = [];
loadedZ = sourceZ + 1;
do {
--loadedZ;
covered = true;
empty = true;
sourceTileGrid.forEachTileCoord(extent, loadedZ, function(sourceTileCoord) {
const coordKey = getKey(sourceTileCoord);
let sourceTile;
if (coordKey in this.sourceTileByCoordKey_) {
sourceTile = this.sourceTileByCoordKey_[coordKey];
const state = sourceTile.getState();
if (state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
empty = empty && state === TileState.EMPTY;
sourceTiles.push(sourceTile);
return;
}
} else if (loadedZ === sourceZ) {
const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
if (tileUrl !== undefined) {
sourceTile = new this.tileClass(sourceTileCoord, TileState.IDLE, tileUrl,
this.format_, this.tileLoadFunction);
sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
sourceTile.projection = projection;
sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
this.sourceTileByCoordKey_[coordKey] = sourceTile;
empty = false;
listen(sourceTile, EventType.CHANGE, this.handleTileChange, this);
sourceTile.load();
}
} else {
empty = false;
}
covered = false;
if (!sourceTile) {
return;
}
} else if (loadedZ === sourceZ) {
const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
if (tileUrl !== undefined) {
sourceTile = new this.tileClass(sourceTileCoord, TileState.IDLE, tileUrl,
this.format_, this.tileLoadFunction);
sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
sourceTile.projection = projection;
sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
this.sourceTileByCoordKey_[coordKey] = sourceTile;
empty = false;
listen(sourceTile, EventType.CHANGE, this.handleTileChange, this);
sourceTile.load();
if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
tile.loadingSourceTiles++;
const key = listen(sourceTile, EventType.CHANGE, function() {
const state = sourceTile.getState();
const sourceTileKey = sourceTile.getKey();
if (state === TileState.LOADED || state === TileState.ERROR) {
if (state === TileState.LOADED) {
unlistenByKey(key);
tile.loadingSourceTiles--;
delete tile.errorSourceTileKeys[sourceTileKey];
} else if (state === TileState.ERROR) {
tile.errorSourceTileKeys[sourceTileKey] = true;
}
if (tile.loadingSourceTiles - Object.keys(tile.errorSourceTileKeys).length === 0) {
tile.hifi = true;
tile.sourceZ = sourceZ;
tile.setState(isEmpty(tile.errorSourceTileKeys) ? TileState.LOADED : TileState.ERROR);
}
}
});
}
} else {
empty = false;
}.bind(this));
if (!covered) {
sourceTiles.length = 0;
}
covered = false;
if (!sourceTile) {
return;
}
if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
tile.loadingSourceTiles++;
const key = listen(sourceTile, EventType.CHANGE, function() {
const state = sourceTile.getState();
const sourceTileKey = sourceTile.getKey();
if (state === TileState.LOADED || state === TileState.ERROR) {
if (state === TileState.LOADED) {
unlistenByKey(key);
tile.loadingSourceTiles--;
delete tile.errorSourceTileKeys[sourceTileKey];
} else if (state === TileState.ERROR) {
tile.errorSourceTileKeys[sourceTileKey] = true;
}
if (tile.loadingSourceTiles - Object.keys(tile.errorSourceTileKeys).length === 0) {
tile.hifi = true;
tile.sourceZ = sourceZ;
tile.setState(isEmpty(tile.errorSourceTileKeys) ? TileState.LOADED : TileState.ERROR);
}
}
});
}
}.bind(this));
if (!covered) {
sourceTiles.length = 0;
}
} while (!covered && loadedZ > minZoom);
} while (!covered && loadedZ > minZoom);
}
if (!empty && tile.getState() === TileState.IDLE) {
tile.setState(TileState.LOADING);
}
@@ -328,18 +332,17 @@ class VectorTile extends UrlTile {
}
}
const tileCoord = [z, x, y];
let urlTileCoord = this.getTileCoordForTileUrlFunction(tileCoord, projection);
const sourceExtent = this.getTileGrid().getExtent();
let tileInExtent = true;
if (sourceExtent) {
if (urlTileCoord && sourceExtent) {
const tileGrid = this.getTileGridForProjection(projection);
const tileExtent = tileGrid.getTileCoordExtent(tileCoord);
const tileExtent = tileGrid.getTileCoordExtent(urlTileCoord);
// make extent 1 pixel smaller so we don't load tiles for < 0.5 pixel render space
bufferExtent(tileExtent, -1 / tileGrid.getResolution(z), tileExtent);
tileInExtent = intersects(sourceExtent, tileExtent);
if (!intersects(sourceExtent, tileExtent)) {
urlTileCoord = null;
}
}
const urlTileCoord = tileInExtent ?
this.getTileCoordForTileUrlFunction(tileCoord, projection) :
null;
const newTile = new VectorRenderTile(
tileCoord,
urlTileCoord !== null ? TileState.IDLE : TileState.EMPTY,

View File

@@ -75,11 +75,10 @@ describe('ol.renderer.canvas.VectorLayer', function() {
style: layerStyle
});
map.addLayer(layer);
const spy = sinon.spy(layer.getRenderer(),
'renderFeature');
const spy = sinon.spy(layer.getRenderer(), 'renderFeature');
map.renderSync();
expect(spy.getCall(0).args[3]).to.be(layerStyle);
expect(spy.getCall(1).args[3]).to.be(featureStyle);
expect(spy.getCall(0).args[2]).to.be(layerStyle);
expect(spy.getCall(1).args[2]).to.be(featureStyle);
document.body.removeChild(target);
});

View File

@@ -11,16 +11,17 @@ import {createXYZ} from '../../../../src/ol/tilegrid.js';
import TileGrid from '../../../../src/ol/tilegrid/TileGrid.js';
import {listen, unlistenByKey} from '../../../../src/ol/events.js';
import TileState from '../../../../src/ol/TileState.js';
import {getCenter} from '../../../../src/ol/extent.js';
describe('ol.source.VectorTile', function() {
const format = new MVT();
const source = new VectorTileSource({
format: format,
url: 'spec/ol/data/{z}-{x}-{y}.vector.pbf'
let format, source;
beforeEach(function() {
format = new MVT();
source = new VectorTileSource({
format: format,
url: 'spec/ol/data/{z}-{x}-{y}.vector.pbf'
});
});
let tile;
describe('constructor', function() {
it('sets the format on the instance', function() {
@@ -45,15 +46,9 @@ describe('ol.source.VectorTile', function() {
describe('#getTile()', function() {
it('creates a tile with the correct tile class', function() {
tile = source.getTile(0, 0, 0, 1, getProjection('EPSG:3857'));
const tile = source.getTile(0, 0, 0, 1, getProjection('EPSG:3857'));
expect(tile).to.be.a(VectorRenderTile);
});
it('sets the correct tileCoord on the created tile', function() {
expect(tile.getTileCoord()).to.eql([0, 0, 0]);
});
it('fetches tile from cache when requested again', function() {
expect(source.getTile(0, 0, 0, 1, getProjection('EPSG:3857')))
.to.equal(tile);
});
@@ -100,6 +95,20 @@ describe('ol.source.VectorTile', function() {
expect(tile.getState()).to.be(TileState.EMPTY);
});
it('creates empty tiles outside the world extent when wrapX === false', function() {
const source = new VectorTileSource({
wrapX: false
});
const tile = source.getTile(0, -1, 0, 1, source.getProjection());
expect(tile.getState()).to.be(TileState.EMPTY);
});
it('creates non-empty tiles outside the world extent when wrapX === true', function() {
const source = new VectorTileSource({});
const tile = source.getTile(0, -1, 0, 1, source.getProjection());
expect(tile.getState()).to.be(TileState.IDLE);
});
it('creates new tile when source key changes', function() {
source.setKey('key1');
const tile1 = source.getTile(0, 0, 0, 1, getProjection('EPSG:3857'));
@@ -122,7 +131,7 @@ describe('ol.source.VectorTile', function() {
describe('Tile load events', function() {
it('triggers tileloadstart and tileloadend with ol.VectorTile', function(done) {
tile = source.getTile(14, 8938, 5680, 1, getProjection('EPSG:3857'));
const tile = source.getTile(14, 8938, 5680, 1, getProjection('EPSG:3857'));
let started = false;
source.on('tileloadstart', function() {
started = true;
@@ -209,45 +218,59 @@ describe('ol.source.VectorTile', function() {
target.style.width = '100px';
target.style.height = '100px';
document.body.appendChild(target);
const extent = [1824704.739223726, 6141868.096770482, 1827150.7241288517, 6144314.081675608];
let url = 'spec/ol/data/14-8938-5680.vector.pbf?';
const urls = [
'spec/ol/data/14-8938-5680.vector.pbf?num=0&coord={z},{x},{y}',
'spec/ol/data/14-8938-5680.vector.pbf?num=1&coord={z},{x},{y}',
'spec/ol/data/14-8938-5680.vector.pbf?num=2&coord={z},{x},{y}',
'spec/ol/data/14-8938-5680.vector.pbf?num=3&coord={z},{x},{y}'
];
const source = new VectorTileSource({
format: new MVT(),
url: url,
minZoom: 14,
maxZoom: 14
url: urls[0]
});
const map = new Map({
target: target,
layers: [
new VectorTileLayer({
extent: extent,
source: source
})
],
view: new View({
center: getCenter(extent),
zoom: 14
center: [0, 0],
zoom: 0
})
});
map.renderSync();
const limit = 3;
const max = urls.length + 3;
let count = 0;
source.on('tileloadend', function() {
setTimeout(function() {
let tile = source.getTile(0, 0, 0, 1, map.getView().getProjection());
tile.addEventListener('change', function onTileChange(e) {
if (e.target.getState() !== TileState.LOADED && !e.target.hifi) {
return;
}
e.target.removeEventListener('change', onTileChange);
map.once('rendercomplete', function() {
expect(map.tileQueue_.getTilesLoading()).to.be(0);
++count;
if (count === limit) {
if (count === max) {
document.body.removeChild(target);
map.dispose();
done();
return;
}
url = url + count;
source.setUrl(url);
const queue = map.tileQueue_;
expect(queue.getTilesLoading()).to.be(0);
}, 100);
source.setUrl(urls[count % urls.length]);
tile = source.getTile(0, 0, 0, 1, map.getView().getProjection());
tile.addEventListener('change', onTileChange);
});
});
});