Merge pull request #12731 from ahocevar/user-resolution
Handle resolution and tile loadingstrategy with user projection
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: WFS with geographic coordinates
|
||||||
|
shortdesc: Example of using WFS with a Tile strategy.
|
||||||
|
docs: >
|
||||||
|
This example loads new features from GeoServer WFS with a tile based loading strategy.
|
||||||
|
Calling the <code>useGeographic</code> function in the <code>'ol/proj'</code> module
|
||||||
|
makes it so the map view uses geographic coordinates (even if the view projection is
|
||||||
|
not geographic).
|
||||||
|
tags: "geographic, vector, WFS, tile, strategy, loading, server, maptiler"
|
||||||
|
cloak:
|
||||||
|
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||||
|
value: Get your own API key at https://www.maptiler.com/cloud/
|
||||||
|
experimental: true
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import VectorSource from '../src/ol/source/Vector.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import XYZ from '../src/ol/source/XYZ.js';
|
||||||
|
import {Stroke, Style} from '../src/ol/style.js';
|
||||||
|
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
|
||||||
|
import {createXYZ} from '../src/ol/tilegrid.js';
|
||||||
|
import {tile} from '../src/ol/loadingstrategy.js';
|
||||||
|
import {useGeographic} from '../src/ol/proj.js';
|
||||||
|
|
||||||
|
useGeographic();
|
||||||
|
|
||||||
|
const vectorSource = new VectorSource({
|
||||||
|
format: new GeoJSON(),
|
||||||
|
url: function (extent) {
|
||||||
|
return (
|
||||||
|
'https://ahocevar.com/geoserver/wfs?service=WFS&' +
|
||||||
|
'version=1.1.0&request=GetFeature&typename=osm:water_areas&' +
|
||||||
|
'outputFormat=application/json&srsname=EPSG:4326&' +
|
||||||
|
'bbox=' +
|
||||||
|
extent.join(',') +
|
||||||
|
',EPSG:4326'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
strategy: tile(createXYZ({tileSize: 512})),
|
||||||
|
});
|
||||||
|
|
||||||
|
const vector = new VectorLayer({
|
||||||
|
source: vectorSource,
|
||||||
|
style: new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'rgba(0, 0, 255, 1.0)',
|
||||||
|
width: 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
|
||||||
|
const attributions =
|
||||||
|
'<a href="https://www.maptiler.com/copyright/" target="_blank">© MapTiler</a> ' +
|
||||||
|
'<a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap contributors</a>';
|
||||||
|
|
||||||
|
const raster = new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
attributions: attributions,
|
||||||
|
url: 'https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=' + key,
|
||||||
|
maxZoom: 20,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [raster, vector],
|
||||||
|
target: document.getElementById('map'),
|
||||||
|
view: new View({
|
||||||
|
center: [-80.0298, 43.4578],
|
||||||
|
maxZoom: 19,
|
||||||
|
zoom: 12,
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
* @module ol/loadingstrategy
|
* @module ol/loadingstrategy
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {fromUserExtent, fromUserResolution, toUserExtent} from './proj.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strategy function for loading all features with a single request.
|
* Strategy function for loading all features with a single request.
|
||||||
* @param {import("./extent.js").Extent} extent Extent.
|
* @param {import("./extent.js").Extent} extent Extent.
|
||||||
@@ -28,7 +30,7 @@ export function bbox(extent, resolution) {
|
|||||||
/**
|
/**
|
||||||
* Creates a strategy function for loading features based on a tile grid.
|
* Creates a strategy function for loading features based on a tile grid.
|
||||||
* @param {import("./tilegrid/TileGrid.js").default} tileGrid Tile grid.
|
* @param {import("./tilegrid/TileGrid.js").default} tileGrid Tile grid.
|
||||||
* @return {function(import("./extent.js").Extent, number): Array<import("./extent.js").Extent>} Loading strategy.
|
* @return {function(import("./extent.js").Extent, number, import("./proj.js").Projection): Array<import("./extent.js").Extent>} Loading strategy.
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
export function tile(tileGrid) {
|
export function tile(tileGrid) {
|
||||||
@@ -36,11 +38,17 @@ export function tile(tileGrid) {
|
|||||||
/**
|
/**
|
||||||
* @param {import("./extent.js").Extent} extent Extent.
|
* @param {import("./extent.js").Extent} extent Extent.
|
||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
|
* @param {import("./proj.js").Projection} projection Projection.
|
||||||
* @return {Array<import("./extent.js").Extent>} Extents.
|
* @return {Array<import("./extent.js").Extent>} Extents.
|
||||||
*/
|
*/
|
||||||
function (extent, resolution) {
|
function (extent, resolution, projection) {
|
||||||
const z = tileGrid.getZForResolution(resolution);
|
const z = tileGrid.getZForResolution(
|
||||||
const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
|
fromUserResolution(resolution, projection)
|
||||||
|
);
|
||||||
|
const tileRange = tileGrid.getTileRangeForExtentAndZ(
|
||||||
|
fromUserExtent(extent, projection),
|
||||||
|
z
|
||||||
|
);
|
||||||
/** @type {Array<import("./extent.js").Extent>} */
|
/** @type {Array<import("./extent.js").Extent>} */
|
||||||
const extents = [];
|
const extents = [];
|
||||||
/** @type {import("./tilecoord.js").TileCoord} */
|
/** @type {import("./tilecoord.js").TileCoord} */
|
||||||
@@ -55,7 +63,9 @@ export function tile(tileGrid) {
|
|||||||
tileCoord[2] <= tileRange.maxY;
|
tileCoord[2] <= tileRange.maxY;
|
||||||
++tileCoord[2]
|
++tileCoord[2]
|
||||||
) {
|
) {
|
||||||
extents.push(tileGrid.getTileCoordExtent(tileCoord));
|
extents.push(
|
||||||
|
toUserExtent(tileGrid.getTileCoordExtent(tileCoord), projection)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return extents;
|
return extents;
|
||||||
|
|||||||
@@ -631,6 +631,44 @@ export function fromUserExtent(extent, destProjection) {
|
|||||||
return transformExtent(extent, userProjection, destProjection);
|
return transformExtent(extent, userProjection, destProjection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the resolution in user projection units per pixel. If no user projection
|
||||||
|
* is set, or source or user projection are missing units, the original resolution
|
||||||
|
* is returned.
|
||||||
|
* @param {number} resolution Resolution in input projection units per pixel.
|
||||||
|
* @param {ProjectionLike} sourceProjection The input projection.
|
||||||
|
* @return {number} Resolution in user projection units per pixel.
|
||||||
|
*/
|
||||||
|
export function toUserResolution(resolution, sourceProjection) {
|
||||||
|
if (!userProjection) {
|
||||||
|
return resolution;
|
||||||
|
}
|
||||||
|
const sourceUnits = get(sourceProjection).getUnits();
|
||||||
|
const userUnits = userProjection.getUnits();
|
||||||
|
return sourceUnits && userUnits
|
||||||
|
? (resolution * METERS_PER_UNIT[sourceUnits]) / METERS_PER_UNIT[userUnits]
|
||||||
|
: resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the resolution in user projection units per pixel. If no user projection
|
||||||
|
* is set, or source or user projection are missing units, the original resolution
|
||||||
|
* is returned.
|
||||||
|
* @param {number} resolution Resolution in user projection units per pixel.
|
||||||
|
* @param {ProjectionLike} destProjection The destination projection.
|
||||||
|
* @return {number} Resolution in destination projection units per pixel.
|
||||||
|
*/
|
||||||
|
export function fromUserResolution(resolution, destProjection) {
|
||||||
|
if (!userProjection) {
|
||||||
|
return resolution;
|
||||||
|
}
|
||||||
|
const sourceUnits = get(destProjection).getUnits();
|
||||||
|
const userUnits = userProjection.getUnits();
|
||||||
|
return sourceUnits && userUnits
|
||||||
|
? (resolution * METERS_PER_UNIT[userUnits]) / METERS_PER_UNIT[sourceUnits]
|
||||||
|
: resolution;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a safe coordinate transform function from a coordinate transform function.
|
* Creates a safe coordinate transform function from a coordinate transform function.
|
||||||
* "Safe" means that it can handle wrapping of x-coordinates for global projections,
|
* "Safe" means that it can handle wrapping of x-coordinates for global projections,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
getTransformFromProjections,
|
getTransformFromProjections,
|
||||||
getUserProjection,
|
getUserProjection,
|
||||||
toUserExtent,
|
toUserExtent,
|
||||||
|
toUserResolution,
|
||||||
} from '../../proj.js';
|
} from '../../proj.js';
|
||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import {wrapX as wrapCoordinateX} from '../../coordinate.js';
|
import {wrapX as wrapCoordinateX} from '../../coordinate.js';
|
||||||
@@ -642,9 +643,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
|||||||
let userTransform;
|
let userTransform;
|
||||||
if (userProjection) {
|
if (userProjection) {
|
||||||
for (let i = 0, ii = loadExtents.length; i < ii; ++i) {
|
for (let i = 0, ii = loadExtents.length; i < ii; ++i) {
|
||||||
|
const extent = loadExtents[i];
|
||||||
|
const userExtent = toUserExtent(extent, projection);
|
||||||
vectorSource.loadFeatures(
|
vectorSource.loadFeatures(
|
||||||
toUserExtent(loadExtents[i], projection),
|
userExtent,
|
||||||
resolution,
|
toUserResolution(resolution, projection),
|
||||||
userProjection
|
userProjection
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {xhr} from '../featureloader.js';
|
|||||||
* returns an array of {@link module:ol/extent~Extent} with the extents to load. Usually this
|
* returns an array of {@link module:ol/extent~Extent} with the extents to load. Usually this
|
||||||
* is one of the standard {@link module:ol/loadingstrategy} strategies.
|
* is one of the standard {@link module:ol/loadingstrategy} strategies.
|
||||||
*
|
*
|
||||||
* @typedef {function(import("../extent.js").Extent, number): Array<import("../extent.js").Extent>} LoadingStrategy
|
* @typedef {function(import("../extent.js").Extent, number, import("../proj/Projection.js").default): Array<import("../extent.js").Extent>} LoadingStrategy
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -939,7 +939,7 @@ class VectorSource extends Source {
|
|||||||
*/
|
*/
|
||||||
loadFeatures(extent, resolution, projection) {
|
loadFeatures(extent, resolution, projection) {
|
||||||
const loadedExtentsRtree = this.loadedExtentsRtree_;
|
const loadedExtentsRtree = this.loadedExtentsRtree_;
|
||||||
const extentsToLoad = this.strategy_(extent, resolution);
|
const extentsToLoad = this.strategy_(extent, resolution, projection);
|
||||||
for (let i = 0, ii = extentsToLoad.length; i < ii; ++i) {
|
for (let i = 0, ii = extentsToLoad.length; i < ii; ++i) {
|
||||||
const extentToLoad = extentsToLoad[i];
|
const extentToLoad = extentsToLoad[i];
|
||||||
const alreadyLoaded = loadedExtentsRtree.forEachInExtent(
|
const alreadyLoaded = loadedExtentsRtree.forEachInExtent(
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import {approximatelyEquals} from '../../../../src/ol/extent.js';
|
||||||
|
import {
|
||||||
|
clearUserProjection,
|
||||||
|
get,
|
||||||
|
toUserExtent,
|
||||||
|
toUserResolution,
|
||||||
|
transformExtent,
|
||||||
|
useGeographic,
|
||||||
|
} from '../../../../src/ol/proj.js';
|
||||||
|
import {createXYZ} from '../../../../src/ol/tilegrid.js';
|
||||||
|
import {tile} from '../../../../src/ol/loadingstrategy.js';
|
||||||
|
|
||||||
|
describe('ol/loadingstrategy', function () {
|
||||||
|
describe('tile', function () {
|
||||||
|
afterEach(function () {
|
||||||
|
clearUserProjection();
|
||||||
|
});
|
||||||
|
it('uses a tile grid in view projection', function () {
|
||||||
|
useGeographic();
|
||||||
|
const tileGrid = createXYZ();
|
||||||
|
const strategy = tile(tileGrid);
|
||||||
|
const extent = tileGrid.getTileCoordExtent([1, 1, 1]);
|
||||||
|
const userExtent = toUserExtent(extent, get('EPSG:3857'));
|
||||||
|
const userResolution = toUserResolution(
|
||||||
|
tileGrid.getResolution(1),
|
||||||
|
get('EPSG:3857')
|
||||||
|
);
|
||||||
|
const extents = strategy(userExtent, userResolution, get('EPSG:3857'));
|
||||||
|
expect(
|
||||||
|
approximatelyEquals(
|
||||||
|
transformExtent(extents[0], 'EPSG:4326', 'EPSG:3857'),
|
||||||
|
extent,
|
||||||
|
1e-8
|
||||||
|
)
|
||||||
|
).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
fromLonLat,
|
fromLonLat,
|
||||||
fromUserCoordinate,
|
fromUserCoordinate,
|
||||||
fromUserExtent,
|
fromUserExtent,
|
||||||
|
fromUserResolution,
|
||||||
getPointResolution,
|
getPointResolution,
|
||||||
get as getProjection,
|
get as getProjection,
|
||||||
getTransform,
|
getTransform,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
toLonLat,
|
toLonLat,
|
||||||
toUserCoordinate,
|
toUserCoordinate,
|
||||||
toUserExtent,
|
toUserExtent,
|
||||||
|
toUserResolution,
|
||||||
transform,
|
transform,
|
||||||
transformExtent,
|
transformExtent,
|
||||||
useGeographic,
|
useGeographic,
|
||||||
@@ -151,6 +153,36 @@ describe('ol/proj.js', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('fromUserResolution()', function () {
|
||||||
|
it("adjusts a resolution for the user projection's units", function () {
|
||||||
|
useGeographic();
|
||||||
|
const user = 1 / METERS_PER_UNIT['degrees'];
|
||||||
|
const resolution = fromUserResolution(user, 'EPSG:3857');
|
||||||
|
expect(resolution).to.roughlyEqual(1, 1e-9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the original if no user projection is set', function () {
|
||||||
|
const user = METERS_PER_UNIT['meters'];
|
||||||
|
const resolution = fromUserResolution(user, 'EPSG:3857');
|
||||||
|
expect(resolution).to.eql(user);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toUserResolution()', function () {
|
||||||
|
it("adjusts a resolution for the user projection's units", function () {
|
||||||
|
useGeographic();
|
||||||
|
const dest = 1;
|
||||||
|
const resolution = toUserResolution(dest, 'EPSG:3857');
|
||||||
|
expect(resolution).to.eql(1 / METERS_PER_UNIT['degrees']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the original if no user projection is set', function () {
|
||||||
|
const dest = METERS_PER_UNIT['degrees'];
|
||||||
|
const resolution = toUserResolution(dest, 'EPSG:3857');
|
||||||
|
expect(resolution).to.eql(dest);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('toLonLat()', function () {
|
describe('toLonLat()', function () {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user