Merge pull request #10393 from ahocevar/vectortile-getfeaturesinextent
getFeaturesInExtent function for ol/source/VectorTile
This commit is contained in:
@@ -105,7 +105,9 @@ class VectorTile extends Tile {
|
|||||||
if (this.state == TileState.IDLE) {
|
if (this.state == TileState.IDLE) {
|
||||||
this.setState(TileState.LOADING);
|
this.setState(TileState.LOADING);
|
||||||
this.tileLoadFunction_(this, this.url_);
|
this.tileLoadFunction_(this, this.url_);
|
||||||
this.loader_(this.extent, this.resolution, this.projection);
|
if (this.loader_) {
|
||||||
|
this.loader_(this.extent, this.resolution, this.projection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import VectorRenderTile from '../VectorRenderTile.js';
|
|||||||
import Tile from '../VectorTile.js';
|
import Tile from '../VectorTile.js';
|
||||||
import {toSize} from '../size.js';
|
import {toSize} from '../size.js';
|
||||||
import UrlTile from './UrlTile.js';
|
import UrlTile from './UrlTile.js';
|
||||||
import {getKeyZXY} from '../tilecoord.js';
|
import {getKeyZXY, fromKey} from '../tilecoord.js';
|
||||||
import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.js';
|
import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.js';
|
||||||
import {buffer as bufferExtent, getIntersection, intersects} from '../extent.js';
|
import {buffer as bufferExtent, getIntersection, intersects} from '../extent.js';
|
||||||
import EventType from '../events/EventType.js';
|
import EventType from '../events/EventType.js';
|
||||||
@@ -52,6 +52,17 @@ import {listen, unlistenByKey} from '../events.js';
|
|||||||
* });
|
* });
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
|
* If you do not need extent, resolution and projection to get the features for a tile (e.g.
|
||||||
|
* for GeoJSON tiles), your `tileLoadFunction` does not need a `setLoader()` call. Only make sure
|
||||||
|
* to call `setFeatures()` on the tile:
|
||||||
|
* ```js
|
||||||
|
* const format = new GeoJSON({featureProjection: map.getView().getProjection()});
|
||||||
|
* async function tileLoadFunction(tile, url) {
|
||||||
|
* const response = await fetch(url);
|
||||||
|
* const data = await response.json();
|
||||||
|
* tile.setFeatures(format.readFeatures(data));
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
* @property {import("../Tile.js").UrlFunction} [tileUrlFunction] Optional function to get tile URL given a tile coordinate and the projection.
|
* @property {import("../Tile.js").UrlFunction} [tileUrlFunction] Optional function to get tile URL given a tile coordinate and the projection.
|
||||||
* @property {string} [url] URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders.
|
* @property {string} [url] URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders.
|
||||||
* A `{?-?}` template pattern, for example `subdomain{a-f}.domain.com`, may be
|
* A `{?-?}` template pattern, for example `subdomain{a-f}.domain.com`, may be
|
||||||
@@ -159,6 +170,51 @@ class VectorTile extends UrlTile {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get features whose bounding box intersects the provided extent. Only features for cached
|
||||||
|
* tiles for the last rendered zoom level are available in the source. So this method is only
|
||||||
|
* suitable for requesting tiles for extents that are currently rendered.
|
||||||
|
*
|
||||||
|
* Features are returned in random tile order and as they are included in the tiles. This means
|
||||||
|
* they can be clipped, duplicated across tiles, and simplified to the render resolution.
|
||||||
|
*
|
||||||
|
* @param {import("../extent.js").Extent} extent Extent.
|
||||||
|
* @return {Array<import("../Feature.js").FeatureLike>} Features.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
getFeaturesInExtent(extent) {
|
||||||
|
const features = [];
|
||||||
|
const tileCache = this.tileCache;
|
||||||
|
if (tileCache.getCount() === 0) {
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
const z = fromKey(tileCache.peekFirstKey())[0];
|
||||||
|
const tileGrid = this.tileGrid;
|
||||||
|
tileCache.forEach(function(tile) {
|
||||||
|
if (tile.tileCoord[0] !== z || tile.getState() !== TileState.LOADED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sourceTiles = tile.getSourceTiles();
|
||||||
|
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
|
||||||
|
const sourceTile = sourceTiles[i];
|
||||||
|
const tileCoord = sourceTile.tileCoord;
|
||||||
|
if (intersects(extent, tileGrid.getTileCoordExtent(tileCoord))) {
|
||||||
|
const tileFeatures = sourceTile.getFeatures();
|
||||||
|
if (tileFeatures) {
|
||||||
|
for (let j = 0, jj = tileFeatures.length; j < jj; ++j) {
|
||||||
|
const candidate = tileFeatures[j];
|
||||||
|
const geometry = candidate.getGeometry();
|
||||||
|
if (geometry.intersectsExtent(extent)) {
|
||||||
|
features.push(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {boolean} The source can have overlapping geometries.
|
* @return {boolean} The source can have overlapping geometries.
|
||||||
*/
|
*/
|
||||||
@@ -232,7 +288,7 @@ class VectorTile extends UrlTile {
|
|||||||
sourceTile.load();
|
sourceTile.load();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
covered = false;
|
covered = covered && sourceTile && sourceTile.getState() === TileState.LOADED;
|
||||||
if (!sourceTile) {
|
if (!sourceTile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {listen, unlistenByKey} from '../../../../src/ol/events.js';
|
|||||||
import TileState from '../../../../src/ol/TileState.js';
|
import TileState from '../../../../src/ol/TileState.js';
|
||||||
import {getCenter} from '../../../../src/ol/extent.js';
|
import {getCenter} from '../../../../src/ol/extent.js';
|
||||||
import {unByKey} from '../../../../src/ol/Observable.js';
|
import {unByKey} from '../../../../src/ol/Observable.js';
|
||||||
|
import Feature from '../../../../src/ol/Feature.js';
|
||||||
|
import {fromExtent} from '../../../../src/ol/geom/Polygon.js';
|
||||||
|
|
||||||
describe('ol.source.VectorTile', function() {
|
describe('ol.source.VectorTile', function() {
|
||||||
|
|
||||||
@@ -354,4 +356,68 @@ describe('ol.source.VectorTile', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getFeatuersInExtent', function() {
|
||||||
|
|
||||||
|
let map, source, target;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
source = new VectorTileSource({
|
||||||
|
maxZoom: 15,
|
||||||
|
tileSize: 256,
|
||||||
|
url: '{z}/{x}/{y}',
|
||||||
|
tileLoadFunction: function(tile) {
|
||||||
|
const extent = source.getTileGrid().getTileCoordExtent(tile.tileCoord);
|
||||||
|
const feature = new Feature(fromExtent(extent));
|
||||||
|
feature.set('z', tile.tileCoord[0]);
|
||||||
|
tile.setFeatures([feature]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
target = document.createElement('div');
|
||||||
|
target.style.width = '100px';
|
||||||
|
target.style.height = '100px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [
|
||||||
|
new VectorTileLayer({
|
||||||
|
source: source
|
||||||
|
})
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
map.setTarget(null);
|
||||||
|
document.body.removeChild(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty array when no tiles are in the cache', function() {
|
||||||
|
source.tileCache.clear();
|
||||||
|
const extent = map.getView().calculateExtent(map.getSize());
|
||||||
|
expect(source.getFeaturesInExtent(extent).length).to.be(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns features in extent for the last rendered z', function(done) {
|
||||||
|
map.getView().setZoom(15);
|
||||||
|
map.once('rendercomplete', function() {
|
||||||
|
const extent = map.getView().calculateExtent(map.getSize());
|
||||||
|
const features = source.getFeaturesInExtent(extent);
|
||||||
|
expect(features.length).to.be(4);
|
||||||
|
expect(features[0].get('z')).to.be(15);
|
||||||
|
map.getView().setZoom(0);
|
||||||
|
map.once('rendercomplete', function() {
|
||||||
|
const extent = map.getView().calculateExtent(map.getSize());
|
||||||
|
const features = source.getFeaturesInExtent(extent);
|
||||||
|
expect(features.length).to.be(1);
|
||||||
|
expect(features[0].get('z')).to.be(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user