Merge branch 'master' into patch-4

This commit is contained in:
mike-000
2020-02-28 12:20:06 +00:00
committed by GitHub
172 changed files with 4761 additions and 3084 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ export class CollectionEvent extends Event {
/**
* @param {CollectionEventType} type Type.
* @param {*=} opt_element Element.
* @param {number} opt_index The index of the added or removed element.
* @param {number=} opt_index The index of the added or removed element.
*/
constructor(type, opt_element, opt_index) {
super(type);
-14
View File
@@ -58,20 +58,6 @@ class ImageTile extends Tile {
}
/**
* @inheritDoc
*/
disposeInternal() {
if (this.state == TileState.LOADING) {
this.unlistenImage_();
this.image_ = getBlankImage();
}
if (this.interimTile) {
this.interimTile.dispose();
}
super.disposeInternal();
}
/**
* Get the HTML image element for this tile (may be a Canvas, Image, or Video).
* @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
+31 -6
View File
@@ -1,7 +1,6 @@
/**
* @module ol/PluggableMap
*/
import {getUid} from './util.js';
import Collection from './Collection.js';
import CollectionEventType from './CollectionEventType.js';
import MapBrowserEvent from './MapBrowserEvent.js';
@@ -933,12 +932,16 @@ class PluggableMap extends BaseObject {
// coordinates so interactions cannot be used.
return;
}
let target = /** @type {Node} */ (mapBrowserEvent.originalEvent.target);
while (target) {
if (target.parentElement === this.overlayContainerStopEvent_) {
const target = /** @type {Node} */ (mapBrowserEvent.originalEvent.target);
if (!mapBrowserEvent.dragging) {
if (this.overlayContainerStopEvent_.contains(target) || !(document.body.contains(target) || this.viewport_.getRootNode && this.viewport_.getRootNode().contains(target))) {
// Abort if the event target is a child of the container that doesn't allow
// event propagation or is no longer in the page. It's possible for the target to no longer
// be in the page if it has been removed in an event listener, this might happen in a Control
// that recreates it's content based on user interaction either manually or via a render
// in something like https://reactjs.org/
return;
}
target = target.parentElement;
}
mapBrowserEvent.frameState = this.frameState_;
const interactionsArray = this.getInteractions().getArray();
@@ -1102,7 +1105,8 @@ class PluggableMap extends BaseObject {
}
const view = this.getView();
if (view) {
this.viewport_.setAttribute('data-view', getUid(view));
this.updateViewportSize_();
this.viewPropertyListenerKey_ = listen(
view, ObjectEventType.PROPERTYCHANGE,
this.handleViewPropertyChanged_, this);
@@ -1360,6 +1364,27 @@ class PluggableMap extends BaseObject {
parseFloat(computedStyle['borderBottomWidth'])
]);
}
this.updateViewportSize_();
}
/**
* Recomputes the viewport size and save it on the view object (if any)
* @private
*/
updateViewportSize_() {
const view = this.getView();
if (view) {
let size = undefined;
const computedStyle = getComputedStyle(this.viewport_);
if (computedStyle.width && computedStyle.height) {
size = [
parseInt(computedStyle.width, 10),
parseInt(computedStyle.height, 10)
];
}
view.setViewportSize(size);
}
}
}
+2 -3
View File
@@ -145,10 +145,9 @@ class Tile extends EventTarget {
}
/**
* @inheritDoc
* Called by the tile cache when the tile is removed from the cache due to expiry
*/
disposeInternal() {
this.setState(TileState.ABORT);
release() {
}
/**
+3 -12
View File
@@ -7,16 +7,7 @@ import {fromKey, getKey} from './tilecoord.js';
class TileCache extends LRUCache {
/**
* @param {number=} opt_highWaterMark High water mark.
*/
constructor(opt_highWaterMark) {
super(opt_highWaterMark);
}
/**
* @param {!Object<string, import("./TileRange.js").default>} usedTiles Used tiles.
* @param {!Object<string, boolean>} usedTiles Used tiles.
*/
expireCache(usedTiles) {
while (this.canExpireCache()) {
@@ -24,7 +15,7 @@ class TileCache extends LRUCache {
if (tile.getKey() in usedTiles) {
break;
} else {
this.pop().dispose();
this.pop().release();
}
}
}
@@ -42,7 +33,7 @@ class TileCache extends LRUCache {
this.forEach(function(tile) {
if (tile.tileCoord[0] !== z) {
this.remove(getKey(tile.tileCoord));
tile.dispose();
tile.release();
}
}.bind(this));
}
+2 -11
View File
@@ -84,8 +84,7 @@ class TileQueue extends PriorityQueue {
handleTileChange(event) {
const tile = /** @type {import("./Tile.js").default} */ (event.target);
const state = tile.getState();
if (tile.hifi && state === TileState.LOADED || state === TileState.ERROR ||
state === TileState.EMPTY || state === TileState.ABORT) {
if (tile.hifi && state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
tile.removeEventListener(EventType.CHANGE, this.boundHandleTileChange_);
const tileKey = tile.getKey();
if (tileKey in this.tilesLoadingKeys_) {
@@ -102,27 +101,19 @@ class TileQueue extends PriorityQueue {
*/
loadMoreTiles(maxTotalLoading, maxNewLoads) {
let newLoads = 0;
let abortedTiles = false;
let state, tile, tileKey;
while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads &&
this.getCount() > 0) {
tile = /** @type {import("./Tile.js").default} */ (this.dequeue()[0]);
tileKey = tile.getKey();
state = tile.getState();
if (state === TileState.ABORT) {
abortedTiles = true;
} else if (state === TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
if (state === TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
this.tilesLoadingKeys_[tileKey] = true;
++this.tilesLoading_;
++newLoads;
tile.load();
}
}
if (newLoads === 0 && abortedTiles) {
// Do not stop the render loop when all wanted tiles were aborted due to
// a small, saturated tile cache.
this.tileChangeCallback_();
}
}
}
+1 -2
View File
@@ -14,6 +14,5 @@ export default {
* @type {number}
*/
ERROR: 3,
EMPTY: 4,
ABORT: 5
EMPTY: 4
};
+23 -45
View File
@@ -4,7 +4,6 @@
import {getUid} from './util.js';
import Tile from './Tile.js';
import {createCanvasContext2D} from './dom.js';
import {unlistenByKey} from './events.js';
/**
@@ -19,6 +18,10 @@ import {unlistenByKey} from './events.js';
* @property {number} renderedTileZ
*/
/**
* @type {Array<HTMLCanvasElement>}
*/
const canvasPool = [];
class VectorRenderTile extends Tile {
@@ -26,13 +29,10 @@ class VectorRenderTile extends Tile {
* @param {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
* @param {import("./TileState.js").default} state State.
* @param {import("./tilecoord.js").TileCoord} urlTileCoord Wrapped tile coordinate for source urls.
* @param {import("./tilegrid/TileGrid.js").default} sourceTileGrid Tile grid of the source.
* @param {function(VectorRenderTile):Array<import("./VectorTile").default>} getSourceTiles Function
* to get an source tiles for this tile.
* @param {function(VectorRenderTile):void} removeSourceTiles Function to remove this tile from its
* source tiles's consumer count.
* to get source tiles for this tile.
*/
constructor(tileCoord, state, urlTileCoord, sourceTileGrid, getSourceTiles, removeSourceTiles) {
constructor(tileCoord, state, urlTileCoord, getSourceTiles) {
super(tileCoord, state, {transition: 0});
@@ -71,6 +71,11 @@ class VectorRenderTile extends Tile {
*/
this.replayState_ = {};
/**
* @type {Array<import("./VectorTile.js").default>}
*/
this.sourceTiles = null;
/**
* @type {number}
*/
@@ -79,23 +84,7 @@ class VectorRenderTile extends Tile {
/**
* @type {!function():Array<import("./VectorTile.js").default>}
*/
this.getSourceTiles = getSourceTiles.bind(this, this);
/**
* @type {!function(import("./VectorRenderTile.js").default):void}
*/
this.removeSourceTiles_ = removeSourceTiles;
/**
* @private
* @type {import("./tilegrid/TileGrid.js").default}
*/
this.sourceTileGrid_ = sourceTileGrid;
/**
* @type {Array<import("./events.js").EventsKey>}
*/
this.sourceTileListenerKeys = [];
this.getSourceTiles = getSourceTiles.bind(undefined, this);
/**
* z of the source tiles of the last getSourceTiles call.
@@ -115,27 +104,6 @@ class VectorRenderTile extends Tile {
this.wrappedTileCoord = urlTileCoord;
}
/**
* @inheritDoc
*/
disposeInternal() {
this.sourceTileListenerKeys.forEach(unlistenByKey);
this.sourceTileListenerKeys.length = 0;
this.removeSourceTiles_(this);
for (const key in this.context_) {
const canvas = this.context_[key].canvas;
canvas.width = 0;
canvas.height = 0;
}
for (const key in this.executorGroups) {
const executorGroups = this.executorGroups[key];
for (let i = 0, ii = executorGroups.length; i < ii; ++i) {
executorGroups[i].disposeInternal();
}
}
super.disposeInternal();
}
/**
* @param {import("./layer/Layer.js").default} layer Layer.
* @return {CanvasRenderingContext2D} The rendering context.
@@ -143,7 +111,7 @@ class VectorRenderTile extends Tile {
getContext(layer) {
const key = getUid(layer);
if (!(key in this.context_)) {
this.context_[key] = createCanvasContext2D();
this.context_[key] = createCanvasContext2D(1, 1, canvasPool);
}
return this.context_[key];
}
@@ -192,6 +160,16 @@ class VectorRenderTile extends Tile {
load() {
this.getSourceTiles();
}
/**
* @inheritDoc
*/
release() {
for (const key in this.context_) {
canvasPool.push(this.context_[key].canvas);
}
super.release();
}
}
-5
View File
@@ -18,11 +18,6 @@ class VectorTile extends Tile {
super(tileCoord, state, opt_options);
/**
* @type {number}
*/
this.consumers = 0;
/**
* Extent of this tile; set by the source.
* @type {import("./extent.js").Extent}
+65 -35
View File
@@ -2,7 +2,6 @@
* @module ol/View
*/
import {DEFAULT_TILE_SIZE} from './tilegrid/common.js';
import {getUid} from './util.js';
import {VOID} from './functions.js';
import {createExtent, none as centerNone} from './centerconstraint.js';
import BaseObject from './Object.js';
@@ -130,6 +129,14 @@ import {createMinMaxResolution} from './resolutionconstraint.js';
* @property {boolean} [smoothResolutionConstraint=true] If true, the resolution
* min/max values will be applied smoothly, i. e. allow the view to exceed slightly
* the given resolution or zoom bounds.
* @property {boolean} [showFullExtent=false] Allow the view to be zoomed out to
* show the full configured extent. By default, when a view is configured with an
* extent, users will not be able to zoom out so the viewport exceeds the extent in
* either dimension. This means the full extent may not be visible if the viewport
* is taller or wider than the aspect ratio of the configured extent. If
* showFullExtent is true, the user will be able to zoom out so that the viewport
* exceeds the height or width of the configured extent, but not both, allowing the
* full extent to be shown.
* @property {import("./proj.js").ProjectionLike} [projection='EPSG:3857'] The
* projection. The default is Spherical Mercator.
* @property {number} [resolution] The initial resolution for the view. The
@@ -294,6 +301,12 @@ class View extends BaseObject {
*/
this.projection_ = createProjection(options.projection, 'EPSG:3857');
/**
* @private
* @type {import("./size.js").Size}
*/
this.viewportSize_ = [100, 100];
/**
* @private
* @type {import("./coordinate.js").Coordinate|undefined}
@@ -649,7 +662,7 @@ class View extends BaseObject {
animation.targetResolution :
animation.sourceResolution + progress * (animation.targetResolution - animation.sourceResolution);
if (animation.anchor) {
const size = this.getSizeFromViewport_(this.getRotation());
const size = this.getViewportSize_(this.getRotation());
const constrainedResolution = this.constraints_.resolution(resolution, 0, size, true);
this.targetCenter_ = this.calculateCenterZoom(constrainedResolution, animation.anchor);
}
@@ -722,26 +735,33 @@ class View extends BaseObject {
}
/**
* Returns the current viewport size.
* @private
* @param {number=} opt_rotation Take into account the rotation of the viewport when giving the size
* @return {import("./size.js").Size} Viewport size or `[100, 100]` when no viewport is found.
*/
getSizeFromViewport_(opt_rotation) {
const size = [100, 100];
const selector = '.ol-viewport[data-view="' + getUid(this) + '"]';
const element = document.querySelector(selector);
if (element) {
const metrics = getComputedStyle(element);
size[0] = parseInt(metrics.width, 10);
size[1] = parseInt(metrics.height, 10);
}
getViewportSize_(opt_rotation) {
const size = this.viewportSize_;
if (opt_rotation) {
const w = size[0];
const h = size[1];
size[0] = Math.abs(w * Math.cos(opt_rotation)) + Math.abs(h * Math.sin(opt_rotation));
size[1] = Math.abs(w * Math.sin(opt_rotation)) + Math.abs(h * Math.cos(opt_rotation));
return [
Math.abs(w * Math.cos(opt_rotation)) + Math.abs(h * Math.sin(opt_rotation)),
Math.abs(w * Math.sin(opt_rotation)) + Math.abs(h * Math.cos(opt_rotation))
];
} else {
return size;
}
return size;
}
/**
* Stores the viewport size on the view. The viewport size is not read every time from the DOM
* to avoid performance hit and layout reflow.
* This should be done on map size change.
* @param {import("./size.js").Size=} opt_size Viewport size; if undefined, [100, 100] is assumed
*/
setViewportSize(opt_size) {
this.viewportSize_ = Array.isArray(opt_size) ? opt_size.slice() : [100, 100];
}
/**
@@ -773,6 +793,13 @@ class View extends BaseObject {
return this.constraints_;
}
/**
* @return {boolean} Resolution constraint is set
*/
getConstrainResolution() {
return this.options_.constrainResolution;
}
/**
* @param {Array<number>=} opt_hints Destination array.
* @return {Array<number>} Hint.
@@ -792,8 +819,8 @@ class View extends BaseObject {
* The size is the pixel dimensions of the box into which the calculated extent
* should fit. In most cases you want to get the extent of the entire map,
* that is `map.getSize()`.
* @param {import("./size.js").Size=} opt_size Box pixel size. If not provided, the size of the
* first map that uses this view will be used.
* @param {import("./size.js").Size=} opt_size Box pixel size. If not provided, the size
* of the map that uses this view will be used.
* @return {import("./extent.js").Extent} Extent.
* @api
*/
@@ -808,7 +835,7 @@ class View extends BaseObject {
* @return {import("./extent.js").Extent} Extent.
*/
calculateExtentInternal(opt_size) {
const size = opt_size || this.getSizeFromViewport_();
const size = opt_size || this.getViewportSize_();
const center = /** @type {!import("./coordinate.js").Coordinate} */ (this.getCenterInternal());
assert(center, 1); // The view center is not defined
const resolution = /** @type {!number} */ (this.getResolution());
@@ -931,7 +958,7 @@ class View extends BaseObject {
* the given size.
*/
getResolutionForExtentInternal(extent, opt_size) {
const size = opt_size || this.getSizeFromViewport_();
const size = opt_size || this.getViewportSize_();
const xResolution = getWidth(extent) / size[0];
const yResolution = getHeight(extent) / size[1];
return Math.max(xResolution, yResolution);
@@ -945,7 +972,7 @@ class View extends BaseObject {
*/
getResolutionForValueFunction(opt_power) {
const power = opt_power || 2;
const maxResolution = this.maxResolution_;
const maxResolution = this.getConstrainedResolution(this.maxResolution_);
const minResolution = this.minResolution_;
const max = Math.log(maxResolution / minResolution) / Math.log(power);
return (
@@ -976,17 +1003,17 @@ class View extends BaseObject {
* @return {function(number): number} Value for resolution function.
*/
getValueForResolutionFunction(opt_power) {
const power = opt_power || 2;
const maxResolution = this.maxResolution_;
const logPower = Math.log(opt_power || 2);
const maxResolution = this.getConstrainedResolution(this.maxResolution_);
const minResolution = this.minResolution_;
const max = Math.log(maxResolution / minResolution) / Math.log(power);
const max = Math.log(maxResolution / minResolution) / logPower;
return (
/**
* @param {number} resolution Resolution.
* @return {number} Value.
*/
function(resolution) {
const value = (Math.log(maxResolution / resolution) / Math.log(power)) / max;
const value = (Math.log(maxResolution / resolution) / logPower) / max;
return value;
});
}
@@ -1079,7 +1106,7 @@ class View extends BaseObject {
* @api
*/
fit(geometryOrExtent, opt_options) {
const options = assign({size: this.getSizeFromViewport_()}, opt_options || {});
const options = assign({size: this.getViewportSize_()}, opt_options || {});
/** @type {import("./geom/SimpleGeometry.js").default} */
let geometry;
@@ -1097,7 +1124,7 @@ class View extends BaseObject {
} else {
const userProjection = getUserProjection();
if (userProjection) {
geometry = /** @type {import("./geom/SimpleGeometry.js").default} */ (geometry.clone().transform(userProjection, this.getProjection()));
geometry = /** @type {import("./geom/SimpleGeometry.js").default} */ (geometryOrExtent.clone().transform(userProjection, this.getProjection()));
} else {
geometry = geometryOrExtent;
}
@@ -1114,7 +1141,7 @@ class View extends BaseObject {
const options = opt_options || {};
let size = options.size;
if (!size) {
size = this.getSizeFromViewport_();
size = this.getViewportSize_();
}
const padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0];
const nearest = options.nearest !== undefined ? options.nearest : false;
@@ -1261,7 +1288,7 @@ class View extends BaseObject {
*/
adjustResolutionInternal(ratio, opt_anchor) {
const isMoving = this.getAnimating() || this.getInteracting();
const size = this.getSizeFromViewport_(this.getRotation());
const size = this.getViewportSize_(this.getRotation());
const newResolution = this.constraints_.resolution(this.targetResolution_ * ratio, 0, size, isMoving);
if (opt_anchor) {
@@ -1385,7 +1412,7 @@ class View extends BaseObject {
// compute rotation
const newRotation = this.constraints_.rotation(this.targetRotation_, isMoving);
const size = this.getSizeFromViewport_(newRotation);
const size = this.getViewportSize_(newRotation);
const newResolution = this.constraints_.resolution(this.targetResolution_, 0, size, isMoving);
const newCenter = this.constraints_.center(this.targetCenter_, newResolution, size, isMoving);
@@ -1419,7 +1446,7 @@ class View extends BaseObject {
const direction = opt_resolutionDirection || 0;
const newRotation = this.constraints_.rotation(this.targetRotation_);
const size = this.getSizeFromViewport_(newRotation);
const size = this.getViewportSize_(newRotation);
const newResolution = this.constraints_.resolution(this.targetResolution_, direction, size);
const newCenter = this.constraints_.center(this.targetCenter_, newResolution, size);
@@ -1500,7 +1527,7 @@ class View extends BaseObject {
* @return {import("./coordinate.js").Coordinate|undefined} Valid center position.
*/
getConstrainedCenter(targetCenter, opt_targetResolution) {
const size = this.getSizeFromViewport_(this.getRotation());
const size = this.getViewportSize_(this.getRotation());
return this.constraints_.center(targetCenter, opt_targetResolution || this.getResolution(), size);
}
@@ -1529,7 +1556,7 @@ class View extends BaseObject {
*/
getConstrainedResolution(targetResolution, opt_direction) {
const direction = opt_direction || 0;
const size = this.getSizeFromViewport_(this.getRotation());
const size = this.getViewportSize_(this.getRotation());
return this.constraints_.resolution(targetResolution, direction, size);
}
@@ -1599,6 +1626,9 @@ export function createResolutionConstraint(options) {
const smooth =
options.smoothResolutionConstraint !== undefined ? options.smoothResolutionConstraint : true;
const showFullExtent =
options.showFullExtent !== undefined ? options.showFullExtent : false;
const projection = createProjection(options.projection, 'EPSG:3857');
const projExtent = projection.getExtent();
let constrainOnlyCenter = options.constrainOnlyCenter;
@@ -1616,10 +1646,10 @@ export function createResolutionConstraint(options) {
if (options.constrainResolution) {
resolutionConstraint = createSnapToResolutions(resolutions, smooth,
!constrainOnlyCenter && extent);
!constrainOnlyCenter && extent, showFullExtent);
} else {
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
!constrainOnlyCenter && extent);
!constrainOnlyCenter && extent, showFullExtent);
}
} else {
// calculate the default min and max resolution
@@ -1665,10 +1695,10 @@ export function createResolutionConstraint(options) {
if (options.constrainResolution) {
resolutionConstraint = createSnapToPower(
zoomFactor, maxResolution, minResolution, smooth,
!constrainOnlyCenter && extent);
!constrainOnlyCenter && extent, showFullExtent);
} else {
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
!constrainOnlyCenter && extent);
!constrainOnlyCenter && extent, showFullExtent);
}
}
return {constraint: resolutionConstraint, maxResolution: maxResolution,
-1
View File
@@ -325,7 +325,6 @@ class Attribution extends Control {
* Update the attribution element.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @this {Attribution}
* @api
*/
export function render(mapEvent) {
this.updateElement_(mapEvent.frameState);
+12 -1
View File
@@ -79,9 +79,10 @@ class Control extends BaseObject {
this.listenerKeys = [];
/**
* @private
* @type {function(import("../MapEvent.js").default): void}
*/
this.render = options.render ? options.render : VOID;
this.render_ = options.render ? options.render : VOID;
if (options.target) {
this.setTarget(options.target);
@@ -134,6 +135,16 @@ class Control extends BaseObject {
}
}
/**
* Update the projection. Rendering of the coordinates is done in
* `handleMouseMove` and `handleMouseUp`.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @api
*/
render(mapEvent) {
this.render_.call(this, mapEvent);
}
/**
* This function is used to set a target element for the control. It has no
* effect if it is called after the control has been added to the map (i.e.
+27
View File
@@ -9,6 +9,29 @@ import EventType from '../events/EventType.js';
const events = ['fullscreenchange', 'webkitfullscreenchange', 'MSFullscreenChange'];
/**
* @enum {string}
*/
const FullScreenEventType = {
/**
* Triggered after the map entered fullscreen.
* @event FullScreenEventType#enterfullscreen
* @api
*/
ENTERFULLSCREEN: 'enterfullscreen',
/**
* Triggered after the map leave fullscreen.
* @event FullScreenEventType#leavefullscreen
* @api
*/
LEAVEFULLSCREEN: 'leavefullscreen'
};
/**
* @typedef {Object} Options
* @property {string} [className='ol-full-screen'] CSS class name.
@@ -38,6 +61,8 @@ const events = ['fullscreenchange', 'webkitfullscreenchange', 'MSFullscreenChang
* The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to
* toggle the map in full screen mode.
*
* @fires FullScreenEventType#enterfullscreen
* @fires FullScreenEventType#leavefullscreen
* @api
*/
class FullScreen extends Control {
@@ -162,9 +187,11 @@ class FullScreen extends Control {
if (isFullScreen()) {
this.setClassName_(this.button_, true);
replaceNode(this.labelActiveNode_, this.labelNode_);
this.dispatchEvent(FullScreenEventType.ENTERFULLSCREEN);
} else {
this.setClassName_(this.button_, false);
replaceNode(this.labelNode_, this.labelActiveNode_);
this.dispatchEvent(FullScreenEventType.LEAVEFULLSCREEN);
}
if (map) {
map.updateSize();
-1
View File
@@ -248,7 +248,6 @@ class MousePosition extends Control {
* `handleMouseMove` and `handleMouseUp`.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @this {MousePosition}
* @api
*/
export function render(mapEvent) {
const frameState = mapEvent.frameState;
+18 -3
View File
@@ -17,6 +17,7 @@ import {replaceNode} from '../dom.js';
import {listen, listenOnce} from '../events.js';
import EventType from '../events/EventType.js';
import {containsExtent, equals as equalsExtent, getBottomRight, getTopLeft, scaleFromCenter} from '../extent.js';
import View from '../View.js';
/**
@@ -59,8 +60,8 @@ class ControlledMap extends PluggableMap {
* @property {HTMLElement|string} [target] Specify a target if you want the control
* to be rendered outside of the map's viewport.
* @property {string} [tipLabel='Overview map'] Text label to use for the button tip.
* @property {import("../View.js").default} [view] Custom view for the overview map. If not provided,
* a default view with an EPSG:3857 projection will be used.
* @property {View} [view] Custom view for the overview map (should use same projection as main map). If not provided,
* a default view with the same projection as the main map will be used.
*/
@@ -167,6 +168,13 @@ class OverviewMap extends Control {
this.ovmapDiv_ = document.createElement('div');
this.ovmapDiv_.className = 'ol-overviewmap-map';
/**
* Explicitly given view to be used instead of a view derived from the main map.
* @type {View}
* @private
*/
this.view_ = options.view;
/**
* @type {ControlledMap}
* @private
@@ -303,6 +311,14 @@ class OverviewMap extends Control {
* @private
*/
bindView_(view) {
if (!this.view_) {
// Unless an explicit view definition was given, derive default from whatever main map uses.
const newView = new View({
projection: view.getProjection()
});
this.ovmap_.setView(newView);
}
view.addEventListener(getChangeEventType(ViewProperty.ROTATION), this.boundHandleRotationChanged_);
// Sync once with the new view
this.handleRotationChanged_();
@@ -603,7 +619,6 @@ class OverviewMap extends Control {
* Update the overview map element.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @this {OverviewMap}
* @api
*/
export function render(mapEvent) {
this.validateExtent_();
+3 -3
View File
@@ -131,8 +131,9 @@ class Rotate extends Control {
// upon it
return;
}
if (view.getRotation() !== undefined) {
if (this.duration_ > 0) {
const rotation = view.getRotation();
if (rotation !== undefined) {
if (this.duration_ > 0 && rotation % (2 * Math.PI) !== 0) {
view.animate({
rotation: 0,
duration: this.duration_,
@@ -150,7 +151,6 @@ class Rotate extends Control {
* Update the rotate control element.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @this {Rotate}
* @api
*/
export function render(mapEvent) {
const frameState = mapEvent.frameState;
-1
View File
@@ -418,7 +418,6 @@ class ScaleLine extends Control {
* Update the scale line element.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @this {ScaleLine}
* @api
*/
export function render(mapEvent) {
const frameState = mapEvent.frameState;
-1
View File
@@ -339,7 +339,6 @@ class ZoomSlider extends Control {
* Update the zoomslider element.
* @param {import("../MapEvent.js").default} mapEvent Map event.
* @this {ZoomSlider}
* @api
*/
export function render(mapEvent) {
if (!mapEvent.frameState) {
+4 -2
View File
@@ -7,10 +7,12 @@
* Create an html canvas element and returns its 2d context.
* @param {number=} opt_width Canvas width.
* @param {number=} opt_height Canvas height.
* @param {Array<HTMLCanvasElement>=} opt_canvasPool Canvas pool to take existing canvas from.
* @return {CanvasRenderingContext2D} The context.
*/
export function createCanvasContext2D(opt_width, opt_height) {
const canvas = document.createElement('canvas');
export function createCanvasContext2D(opt_width, opt_height, opt_canvasPool) {
const canvas = opt_canvasPool && opt_canvasPool.length ?
opt_canvasPool.shift() : document.createElement('canvas');
if (opt_width) {
canvas.width = opt_width;
}
+2
View File
@@ -162,6 +162,8 @@ class GeoJSON extends JSONFeature {
if (crs) {
if (crs['type'] == 'name') {
projection = getProjection(crs['properties']['name']);
} else if (crs['type'] === 'EPSG') {
projection = getProjection('EPSG:' + crs['properties']['code']);
} else {
assert(false, 36); // Unknown SRS type
}
+1 -1
View File
@@ -349,7 +349,7 @@ class IIIFInfo {
}
/**
* @param {PreferredOptions} opt_preferredOptions Optional options for preferred format and quality.
* @param {PreferredOptions=} opt_preferredOptions Optional options for preferred format and quality.
* @returns {import("../source/IIIF.js").Options} IIIF tile source ready constructor options.
* @api
*/
+112 -37
View File
@@ -378,6 +378,11 @@ function createStyleDefaults() {
}
/**
* @type {HTMLTextAreaElement}
*/
let TEXTAREA;
/**
* @typedef {Object} Options
@@ -877,32 +882,32 @@ class KML extends XMLFeature {
* @return {Style} style Style.
*/
function createNameStyleFunction(foundStyle, name) {
let textStyle = null;
const textOffset = [0, 0];
let textAlign = 'start';
if (foundStyle.getImage()) {
let imageSize = foundStyle.getImage().getImageSize();
const imageStyle = foundStyle.getImage();
if (imageStyle) {
let imageSize = imageStyle.getImageSize();
if (imageSize === null) {
imageSize = DEFAULT_IMAGE_STYLE_SIZE;
}
if (imageSize.length == 2) {
const imageScale = foundStyle.getImage().getScale();
// Offset the label to be centered to the right of the icon, if there is
// one.
const imageScale = imageStyle.getScale();
// Offset the label to be centered to the right of the icon,
// if there is one.
textOffset[0] = imageScale * imageSize[0] / 2;
textOffset[1] = -imageScale * imageSize[1] / 2;
textAlign = 'left';
}
}
if (foundStyle.getText() !== null) {
let textStyle = foundStyle.getText();
if (textStyle) {
// clone the text style, customizing it with name, alignments and offset.
// Note that kml does not support many text options that OpenLayers does (rotation, textBaseline).
const foundText = foundStyle.getText();
textStyle = foundText.clone();
textStyle.setFont(foundText.getFont() || DEFAULT_TEXT_STYLE.getFont());
textStyle.setScale(foundText.getScale() || DEFAULT_TEXT_STYLE.getScale());
textStyle.setFill(foundText.getFill() || DEFAULT_TEXT_STYLE.getFill());
textStyle.setStroke(foundText.getStroke() || DEFAULT_TEXT_STROKE_STYLE);
textStyle = textStyle.clone();
textStyle.setFont(textStyle.getFont() || DEFAULT_TEXT_STYLE.getFont());
textStyle.setScale(textStyle.getScale() || DEFAULT_TEXT_STYLE.getScale());
textStyle.setFill(textStyle.getFill() || DEFAULT_TEXT_STYLE.getFill());
textStyle.setStroke(textStyle.getStroke() || DEFAULT_TEXT_STROKE_STYLE);
} else {
textStyle = DEFAULT_TEXT_STYLE.clone();
}
@@ -912,7 +917,12 @@ function createNameStyleFunction(foundStyle, name) {
textStyle.setTextAlign(textAlign);
const nameStyle = new Style({
text: textStyle
image: imageStyle,
text: textStyle,
// although nameStyle will be used only for Point geometries
// fill and stroke are included to assist writing of MultiGeometry styles
fill: foundStyle.getFill(),
stroke: foundStyle.getStroke()
});
return nameStyle;
}
@@ -932,45 +942,66 @@ function createFeatureStyleFunction(style, styleUrl, defaultStyle, sharedStyles,
/**
* @param {Feature} feature feature.
* @param {number} resolution Resolution.
* @return {Array<Style>} Style.
* @return {Array<Style>|Style} Style.
*/
function(feature, resolution) {
let drawName = showPointNames;
/** @type {Style|undefined} */
let nameStyle;
let name = '';
let multiGeometryPoints = [];
if (drawName) {
const geometry = feature.getGeometry();
if (geometry) {
drawName = geometry.getType() === GeometryType.POINT;
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
multiGeometryPoints = geometry.getGeometriesArray().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
});
drawName = multiGeometryPoints.length > 0;
} else {
drawName = type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
}
}
}
if (drawName) {
name = /** @type {string} */ (feature.get('name'));
drawName = drawName && !!name;
// convert any html character codes
if (drawName && name.search(/&[^&]+;/) > -1) {
if (!TEXTAREA) {
TEXTAREA = document.createElement('textarea');
}
TEXTAREA.innerHTML = name;
name = TEXTAREA.value;
}
}
let featureStyle = defaultStyle;
if (style) {
if (drawName) {
nameStyle = createNameStyleFunction(style[0], name);
return style.concat(nameStyle);
}
return style;
}
if (styleUrl) {
const foundStyle = findStyle(styleUrl, defaultStyle, sharedStyles);
if (drawName) {
nameStyle = createNameStyleFunction(foundStyle[0], name);
return foundStyle.concat(nameStyle);
}
return foundStyle;
featureStyle = style;
} else if (styleUrl) {
featureStyle = findStyle(styleUrl, defaultStyle, sharedStyles);
}
if (drawName) {
nameStyle = createNameStyleFunction(defaultStyle[0], name);
return defaultStyle.concat(nameStyle);
const nameStyle = createNameStyleFunction(featureStyle[0], name);
if (multiGeometryPoints.length > 0) {
// in multigeometries restrict the name style to points and create a
// style without image or text for geometries requiring fill or stroke
// including any polygon specific style if there is one
nameStyle.setGeometry(new GeometryCollection(multiGeometryPoints));
const baseStyle = new Style({
geometry: featureStyle[0].getGeometry(),
image: null,
fill: featureStyle[0].getFill(),
stroke: featureStyle[0].getStroke(),
text: null
});
return [nameStyle, baseStyle].concat(featureStyle.slice(1));
}
return nameStyle;
}
return defaultStyle;
return featureStyle;
}
);
}
@@ -1759,13 +1790,57 @@ function readStyle(node, objectStack) {
const textStyle = /** @type {Text} */
('textStyle' in styleObject ?
styleObject['textStyle'] : DEFAULT_TEXT_STYLE);
let strokeStyle = /** @type {Stroke} */
const strokeStyle = /** @type {Stroke} */
('strokeStyle' in styleObject ?
styleObject['strokeStyle'] : DEFAULT_STROKE_STYLE);
const outline = /** @type {boolean|undefined} */
(styleObject['outline']);
if (outline !== undefined && !outline) {
strokeStyle = null;
// if the polystyle specifies no outline two styles are needed,
// one for non-polygon geometries where linestrings require a stroke
// and one for polygons where there should be no stroke
return [
new Style({
geometry: function(feature) {
const geometry = feature.getGeometry();
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection(
geometry.getGeometriesArray().filter(function(geometry) {
const type = geometry.getType();
return type !== GeometryType.POLYGON && type !== GeometryType.MULTI_POLYGON;
})
);
} else if (type !== GeometryType.POLYGON && type !== GeometryType.MULTI_POLYGON) {
return geometry;
}
},
fill: fillStyle,
image: imageStyle,
stroke: strokeStyle,
text: textStyle,
zIndex: undefined // FIXME
}),
new Style({
geometry: function(feature) {
const geometry = feature.getGeometry();
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection(
geometry.getGeometriesArray().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
})
);
} else if (type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON) {
return geometry;
}
},
fill: fillStyle,
stroke: null,
zIndex: undefined // FIXME
})
];
}
return [new Style({
fill: fillStyle,
@@ -2527,7 +2602,7 @@ function writeLineStyle(node, style, objectStack) {
const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node};
const properties = {
'color': style.getColor(),
'width': style.getWidth()
'width': Number(style.getWidth()) || 1
};
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = LINE_STYLE_SEQUENCE[parentNode.namespaceURI];
+4 -1
View File
@@ -74,7 +74,10 @@ class WMTSCapabilities extends XML {
* @inheritDoc
*/
readFromNode(node) {
const version = node.getAttribute('version').trim();
let version = node.getAttribute('version');
if (version) {
version = version.trim();
}
let WMTSCapabilityObject = this.owsParser_.readFromNode(node);
if (!WMTSCapabilityObject) {
return null;
+2 -2
View File
@@ -5,7 +5,7 @@ import {abstract} from '../util.js';
import {extend} from '../array.js';
import FeatureFormat from '../format/Feature.js';
import FormatType from '../format/FormatType.js';
import {isDocument, parse} from '../xml.js';
import {isDocument, parse, getXMLSerializer} from '../xml.js';
/**
* @classdesc
@@ -23,7 +23,7 @@ class XMLFeature extends FeatureFormat {
* @type {XMLSerializer}
* @private
*/
this.xmlSerializer_ = new XMLSerializer();
this.xmlSerializer_ = getXMLSerializer();
}
/**
+6 -6
View File
@@ -1,7 +1,7 @@
/**
* @module ol/format/xsd
*/
import {getAllTextContent, DOCUMENT} from '../xml.js';
import {getAllTextContent, getDocument} from '../xml.js';
import {padNumber} from '../string.js';
@@ -112,7 +112,7 @@ export function writeBooleanTextNode(node, bool) {
* @param {string} string String.
*/
export function writeCDATASection(node, string) {
node.appendChild(DOCUMENT.createCDATASection(string));
node.appendChild(getDocument().createCDATASection(string));
}
@@ -128,7 +128,7 @@ export function writeDateTimeTextNode(node, dateTime) {
padNumber(date.getUTCHours(), 2) + ':' +
padNumber(date.getUTCMinutes(), 2) + ':' +
padNumber(date.getUTCSeconds(), 2) + 'Z';
node.appendChild(DOCUMENT.createTextNode(string));
node.appendChild(getDocument().createTextNode(string));
}
@@ -138,7 +138,7 @@ export function writeDateTimeTextNode(node, dateTime) {
*/
export function writeDecimalTextNode(node, decimal) {
const string = decimal.toPrecision();
node.appendChild(DOCUMENT.createTextNode(string));
node.appendChild(getDocument().createTextNode(string));
}
@@ -148,7 +148,7 @@ export function writeDecimalTextNode(node, decimal) {
*/
export function writeNonNegativeIntegerTextNode(node, nonNegativeInteger) {
const string = nonNegativeInteger.toString();
node.appendChild(DOCUMENT.createTextNode(string));
node.appendChild(getDocument().createTextNode(string));
}
@@ -157,5 +157,5 @@ export function writeNonNegativeIntegerTextNode(node, nonNegativeInteger) {
* @param {string} string String.
*/
export function writeStringTextNode(node, string) {
node.appendChild(DOCUMENT.createTextNode(string));
node.appendChild(getDocument().createTextNode(string));
}
+2
View File
@@ -13,6 +13,8 @@ import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js
* @property {import("../events/condition.js").Condition} [condition] A function that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a boolean
* to indicate whether that event should be handled.
* Default is {@link module:ol/events/condition~noModifierKeys} and {@link module:ol/events/condition~primaryAction}.
* In addition, if there is a `tabindex` attribute on the map element,
* {@link module:ol/events/condition~focus} will also be applied.
* @property {import("../Kinetic.js").default} [kinetic] Kinetic inertia to apply to the pan.
*/
+65 -14
View File
@@ -143,7 +143,13 @@ const DrawEventType = {
* @event DrawEvent#drawend
* @api
*/
DRAWEND: 'drawend'
DRAWEND: 'drawend',
/**
* Triggered upon feature draw abortion
* @event DrawEvent#drawabort
* @api
*/
DRAWABORT: 'drawabort'
};
@@ -505,7 +511,7 @@ class Draw extends PointerInteraction {
if (this.freehand_ &&
event.type === MapBrowserEventType.POINTERDRAG &&
this.sketchFeature_ !== null) {
this.addToDrawing_(event);
this.addToDrawing_(event.coordinate);
pass = false;
} else if (this.freehand_ &&
event.type === MapBrowserEventType.POINTERDOWN) {
@@ -580,12 +586,11 @@ class Draw extends PointerInteraction {
this.finishDrawing();
}
} else {
this.addToDrawing_(event);
this.addToDrawing_(event.coordinate);
}
pass = false;
} else if (this.freehand_) {
this.finishCoordinate_ = null;
this.abortDrawing_();
this.abortDrawing();
}
if (!pass && this.stopClick_) {
event.stopPropagation();
@@ -764,13 +769,12 @@ class Draw extends PointerInteraction {
/**
* Add a new coordinate to the drawing.
* @param {import("../MapBrowserEvent.js").default} event Event.
* @param {!PointCoordType} coordinate Coordinate
* @private
*/
addToDrawing_(event) {
const coordinate = event.coordinate;
addToDrawing_(coordinate) {
const geometry = this.sketchFeature_.getGeometry();
const projection = event.map.getView().getProjection();
const projection = this.getMap().getView().getProjection();
let done;
let coordinates;
if (this.mode_ === Mode.LINE_STRING) {
@@ -835,7 +839,7 @@ class Draw extends PointerInteraction {
}
if (coordinates.length === 0) {
this.finishCoordinate_ = null;
this.abortDrawing();
}
this.updateSketchFeatures_();
@@ -903,9 +907,56 @@ class Draw extends PointerInteraction {
}
/**
* Extend an existing geometry by adding additional points. This only works
* on features with `LineString` geometries, where the interaction will
* extend lines by adding points to the end of the coordinates array.
* Stop drawing without adding the sketch feature to the target layer.
* @api
*/
abortDrawing() {
const sketchFeature = this.abortDrawing_();
if (sketchFeature) {
this.dispatchEvent(new DrawEvent(DrawEventType.DRAWABORT, sketchFeature));
}
}
/**
* Append coordinates to the end of the geometry that is currently being drawn.
* This can be used when drawing LineStrings or Polygons. Coordinates will
* either be appended to the current LineString or the outer ring of the current
* Polygon.
* @param {!LineCoordType} coordinates Linear coordinates to be appended into
* the coordinate array.
* @api
*/
appendCoordinates(coordinates) {
const mode = this.mode_;
let sketchCoords = [];
if (mode === Mode.LINE_STRING) {
sketchCoords = /** @type {LineCoordType} */ this.sketchCoords_;
} else if (mode === Mode.POLYGON) {
sketchCoords = this.sketchCoords_ && this.sketchCoords_.length ? /** @type {PolyCoordType} */ (this.sketchCoords_)[0] : [];
}
// Remove last coordinate from sketch drawing (this coordinate follows cursor position)
const ending = sketchCoords.pop();
// Append coordinate list
for (let i = 0; i < coordinates.length; i++) {
this.addToDrawing_(coordinates[i]);
}
// Duplicate last coordinate for sketch drawing
this.addToDrawing_(ending);
}
/**
* Initiate draw mode by starting from an existing geometry which will
* receive new additional points. This only works on features with
* `LineString` geometries, where the interaction will extend lines by adding
* points to the end of the coordinates array.
* This will change the original feature, instead of drawing a copy.
*
* The function will dispatch a `drawstart` event.
*
* @param {!Feature<LineString>} feature Feature to be extended.
* @api
*/
@@ -948,7 +999,7 @@ class Draw extends PointerInteraction {
const map = this.getMap();
const active = this.getActive();
if (!map || !active) {
this.abortDrawing_();
this.abortDrawing();
}
this.overlay_.setMap(active ? map : null);
}
+17 -7
View File
@@ -23,6 +23,8 @@ export const Mode = {
* takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a
* boolean to indicate whether that event should be handled. Default is
* {@link module:ol/events/condition~always}.
* In addition, if there is a `tabindex` attribute on the map element,
* {@link module:ol/events/condition~focus} will also be applied.
* @property {number} [maxDelta=1] Maximum mouse wheel delta.
* @property {number} [duration=250] Animation duration in milliseconds.
* @property {number} [timeout=80] Mouse wheel timeout duration in milliseconds.
@@ -130,7 +132,7 @@ class MouseWheelZoom extends Interaction {
* @private
* @type {number}
*/
this.trackpadDeltaPerZoom_ = 300;
this.deltaPerZoom_ = 300;
}
@@ -212,15 +214,18 @@ class MouseWheelZoom extends Interaction {
Mode.WHEEL;
}
if (this.mode_ === Mode.TRACKPAD) {
const view = map.getView();
const view = map.getView();
if (this.mode_ === Mode.TRACKPAD && !view.getConstrainResolution()) {
if (this.trackpadTimeoutId_) {
clearTimeout(this.trackpadTimeoutId_);
} else {
if (view.getAnimating()) {
view.cancelAnimations();
}
view.beginInteraction();
}
this.trackpadTimeoutId_ = setTimeout(this.endInteraction_.bind(this), this.trackpadEventGap_);
view.adjustZoom(-delta / this.trackpadDeltaPerZoom_, this.lastAnchor_);
this.trackpadTimeoutId_ = setTimeout(this.endInteraction_.bind(this), this.timeout_);
view.adjustZoom(-delta / this.deltaPerZoom_, this.lastAnchor_);
this.startTime_ = now;
return false;
}
@@ -244,8 +249,13 @@ class MouseWheelZoom extends Interaction {
if (view.getAnimating()) {
view.cancelAnimations();
}
const delta = clamp(this.totalDelta_, -this.maxDelta_, this.maxDelta_);
zoomByDelta(view, -delta, this.lastAnchor_, this.duration_);
let delta = -clamp(this.totalDelta_, -this.maxDelta_ * this.deltaPerZoom_, this.maxDelta_ * this.deltaPerZoom_) / this.deltaPerZoom_;
if (view.getConstrainResolution()) {
// view has a zoom constraint, zoom by 1
delta = delta ? delta > 0 ? 1 : -1 : 0;
}
zoomByDelta(view, delta, this.lastAnchor_, this.duration_);
this.mode_ = undefined;
this.totalDelta_ = 0;
this.lastAnchor_ = null;
+32 -18
View File
@@ -61,7 +61,7 @@ const SelectEventType = {
* @property {import("../style/Style.js").StyleLike} [style]
* Style for the selected features. By default the default edit style is used
* (see {@link module:ol/style}).
* If set to `false` the selected feature's style will not change.
* If set to a falsey value, the selected feature's style will not change.
* @property {import("../events/condition.js").Condition} [removeCondition] A function
* that takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a
* boolean to indicate whether that event should be handled.
@@ -133,6 +133,12 @@ class SelectEvent extends Event {
}
/**
* Original feature styles to reset to when features are no longer selected.
* @type {Object.<number, import("../style/Style.js").default|Array.<import("../style/Style.js").default>|import("../style/Style.js").StyleFunction>}
*/
const originalFeatureStyles = {};
/**
* @classdesc
@@ -209,14 +215,6 @@ class Select extends Interaction {
*/
this.style_ = options.style !== undefined ? options.style : getDefaultStyleFunction();
/**
* An association between selected feature (key)
* and original style (value)
* @private
* @type {Object.<number, import("../style/Style.js").default|Array.<import("../style/Style.js").default>|import("../style/Style.js").StyleFunction>}
*/
this.featureStyleAssociation_ = {};
/**
* @private
* @type {import("../Collection.js").default}
@@ -319,11 +317,11 @@ class Select extends Interaction {
setMap(map) {
const currentMap = this.getMap();
if (currentMap && this.style_) {
this.features_.forEach(this.removeSelectedStyle_.bind(this));
this.features_.forEach(this.restorePreviousStyle_.bind(this));
}
super.setMap(map);
if (map && this.style_) {
this.features_.forEach(this.giveSelectedStyle_.bind(this));
this.features_.forEach(this.applySelectedStyle_.bind(this));
}
}
@@ -334,7 +332,7 @@ class Select extends Interaction {
addFeature_(evt) {
const feature = evt.element;
if (this.style_) {
this.giveSelectedStyle_(feature);
this.applySelectedStyle_(feature);
}
}
@@ -345,17 +343,26 @@ class Select extends Interaction {
removeFeature_(evt) {
const feature = evt.element;
if (this.style_) {
this.removeSelectedStyle_(feature);
this.restorePreviousStyle_(feature);
}
}
/**
* @return {import("../style/Style.js").default|Array.<import("../style/Style.js").default>|import("../style/Style.js").StyleFunction|null} Select style.
*/
getStyle() {
return this.style_;
}
/**
* @param {import("../Feature.js").default} feature Feature
* @private
*/
giveSelectedStyle_(feature) {
applySelectedStyle_(feature) {
const key = getUid(feature);
this.featureStyleAssociation_[key] = feature.getStyle();
if (!(key in originalFeatureStyles)) {
originalFeatureStyles[key] = feature.getStyle();
}
feature.setStyle(this.style_);
}
@@ -363,10 +370,17 @@ class Select extends Interaction {
* @param {import("../Feature.js").default} feature Feature
* @private
*/
removeSelectedStyle_(feature) {
restorePreviousStyle_(feature) {
const key = getUid(feature);
feature.setStyle(this.featureStyleAssociation_[key]);
delete this.featureStyleAssociation_[key];
const selectInteractions = /** @type {Array<Select>} */ (this.getMap().getInteractions().getArray().filter(function(interaction) {
return interaction instanceof Select && interaction.getStyle() && interaction.getFeatures().getArray().indexOf(feature) !== -1;
}));
if (selectInteractions.length > 0) {
feature.setStyle(selectInteractions[selectInteractions.length - 1].getStyle());
} else {
feature.setStyle(originalFeatureStyles[key]);
delete originalFeatureStyles[key];
}
}
/**
+4
View File
@@ -19,6 +19,10 @@ import Layer from './Layer.js';
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage
* this layer in its layers collection, and the layer will be rendered on top. This is useful for
* temporary layers. The standard way to add a layer to a map and have it managed by the map is to
+4
View File
@@ -21,6 +21,10 @@ import {assign} from '../obj.js';
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {number} [preload=0] Preload. Load low-resolution tiles up to `preload` levels. `0`
* means no preloading.
* @property {import("../source/Tile.js").default} [source] Source for this layer.
+4
View File
@@ -21,6 +21,10 @@ import {createDefaultStyle, toFunction as toStyleFunction} from '../style/Style.
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {import("../render.js").OrderFunction} [renderOrder] Render order. Function to be used when sorting
* features before rendering. By default features are drawn in the order that they are created. Use
* `null` to avoid the sort, but get an undefined draw order.
+143 -63
View File
@@ -15,15 +15,16 @@ import {
getTransform,
transformExtent
} from '../proj.js';
import {getCenter, intersects, equals, getIntersection, isEmpty} from '../extent.js';
import {getCenter, getHeight, getWidth, intersects, equals, getIntersection, isEmpty} from '../extent.js';
import {clamp} from '../math.js';
import Style from '../style/Style.js';
import Feature from '../Feature.js';
import {bbox} from '../loadingstrategy.js';
import {meridian, parallel} from '../geom/flat/geodesic.js';
import GeometryLayout from '../geom/GeometryLayout.js';
import Point from '../geom/Point.js';
import Collection from '../Collection.js';
import {getVectorContext} from '../render.js';
import EventType from '../render/EventType.js';
/**
@@ -65,6 +66,10 @@ const INTERVALS = [
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {number} [maxLines=100] The maximum number of meridians and
* parallels from the center of the map. The default value of 100 means that at
* most 200 meridians and 200 parallels will be displayed. The default value is
@@ -379,6 +384,8 @@ class Graticule extends VectorLayer {
this.meridiansLabels_ = [];
this.parallelsLabels_ = [];
this.addEventListener(EventType.POSTRENDER, this.drawLabels_.bind(this));
}
/**
@@ -391,7 +398,7 @@ class Graticule extends VectorLayer {
this.setSource(
new VectorSource({
loader: this.loaderFunction.bind(this),
strategy: bbox,
strategy: this.strategyFunction.bind(this),
features: new Collection(),
overlaps: false,
useSpatialIndex: false,
@@ -414,6 +421,12 @@ class Graticule extends VectorLayer {
stroke: this.strokeStyle_
});
/**
* @type {?import("../extent.js").Extent}
* @private
*/
this.loadedExtent_ = null;
/**
* @type {?import("../extent.js").Extent}
*/
@@ -421,7 +434,21 @@ class Graticule extends VectorLayer {
this.setRenderOrder(null);
this.tmpExtent_ = null;
}
/**
* Strategy function for loading features based on the view's extent and
* resolution.
* @param {import("../extent.js").Extent} extent Extent.
* @param {number} resolution Resolution.
* @return {Array<import("../extent.js").Extent>} Extents.
*/
strategyFunction(extent, resolution) {
if (this.loadedExtent_ && !equals(this.loadedExtent_, extent)) {
// we should not keep track of loaded extents
this.getSource().removeLoadedExtent(this.loadedExtent_);
}
return [extent];
}
/**
@@ -431,16 +458,12 @@ class Graticule extends VectorLayer {
* @param {import("../proj/Projection.js").default} projection Projection
*/
loaderFunction(extent, resolution, projection) {
this.loadedExtent_ = extent;
const source = this.getSource();
// only consider the intersection between our own extent & the requested one
const layerExtent = this.getExtent() || [-Infinity, -Infinity, Infinity, Infinity];
const renderExtent = getIntersection(layerExtent, extent, this.tmpExtent_);
// we should not keep track of loaded extents
setTimeout(function() {
source.removeLoadedExtent(extent);
}, 0);
const renderExtent = getIntersection(layerExtent, extent);
if (this.renderedExtent_ && equals(this.renderedExtent_, renderExtent)) {
return;
@@ -468,10 +491,10 @@ class Graticule extends VectorLayer {
// first make sure we have enough features in the pool
let featureCount = this.meridians_.length + this.parallels_.length;
if (this.meridiansLabels_) {
featureCount += this.meridiansLabels_.length;
featureCount += this.meridians_.length;
}
if (this.parallelsLabels_) {
featureCount += this.parallelsLabels_.length;
featureCount += this.parallels_.length;
}
let feature;
@@ -498,27 +521,6 @@ class Graticule extends VectorLayer {
feature.setStyle(this.lineStyle_);
featuresColl.push(feature);
}
let labelData;
if (this.meridiansLabels_) {
for (i = 0, l = this.meridiansLabels_.length; i < l; ++i) {
labelData = this.meridiansLabels_[i];
feature = this.featurePool_[poolIndex++];
feature.setGeometry(labelData.geom);
feature.setStyle(this.lonLabelStyle_);
feature.set('graticule_label', labelData.text);
featuresColl.push(feature);
}
}
if (this.parallelsLabels_) {
for (i = 0, l = this.parallelsLabels_.length; i < l; ++i) {
labelData = this.parallelsLabels_[i];
feature = this.featurePool_[poolIndex++];
feature.setGeometry(labelData.geom);
feature.setStyle(this.latLabelStyle_);
feature.set('graticule_label', labelData.text);
featuresColl.push(feature);
}
}
}
/**
@@ -535,11 +537,15 @@ class Graticule extends VectorLayer {
const lineString = this.getMeridian_(lon, minLat, maxLat, squaredTolerance, index);
if (intersects(lineString.getExtent(), extent)) {
if (this.meridiansLabels_) {
const textPoint = this.getMeridianPoint_(lineString, extent, index);
this.meridiansLabels_[index] = {
geom: textPoint,
text: this.lonLabelFormatter_(lon)
};
const text = this.lonLabelFormatter_(lon);
if (index in this.meridiansLabels_) {
this.meridiansLabels_[index].text = text;
} else {
this.meridiansLabels_[index] = {
geom: new Point([]),
text: text
};
}
}
this.meridians_[index++] = lineString;
}
@@ -560,17 +566,83 @@ class Graticule extends VectorLayer {
const lineString = this.getParallel_(lat, minLon, maxLon, squaredTolerance, index);
if (intersects(lineString.getExtent(), extent)) {
if (this.parallelsLabels_) {
const textPoint = this.getParallelPoint_(lineString, extent, index);
this.parallelsLabels_[index] = {
geom: textPoint,
text: this.latLabelFormatter_(lat)
};
const text = this.latLabelFormatter_(lat);
if (index in this.parallelsLabels_) {
this.parallelsLabels_[index].text = text;
} else {
this.parallelsLabels_[index] = {
geom: new Point([]),
text: text
};
}
}
this.parallels_[index++] = lineString;
}
return index;
}
/**
* @param {import("../render/Event.js").default} event Render event.
* @private
*/
drawLabels_(event) {
const rotation = event.frameState.viewState.rotation;
const extent = event.frameState.extent;
let rotationCenter, rotationExtent;
if (rotation) {
rotationCenter = getCenter(extent);
const width = getWidth(extent);
const height = getHeight(extent);
const cr = Math.abs(Math.cos(rotation));
const sr = Math.abs(Math.sin(rotation));
const unrotatedWidth = (sr * height - cr * width) / (sr * sr - cr * cr);
const unrotatedHeight = (sr * width - cr * height) / (sr * sr - cr * cr);
rotationExtent = [
rotationCenter[0] - unrotatedWidth / 2, rotationCenter[1] - unrotatedHeight / 2,
rotationCenter[0] + unrotatedWidth / 2, rotationCenter[1] + unrotatedHeight / 2
];
}
const vectorContext = getVectorContext(event);
let poolIndex = this.meridians_.length + this.parallels_.length;
let feature, index, l, textPoint;
if (this.meridiansLabels_) {
for (index = 0, l = this.meridiansLabels_.length; index < l; ++index) {
const lineString = this.meridians_[index];
if (!rotation) {
textPoint = this.getMeridianPoint_(lineString, extent, index);
} else {
const clone = lineString.clone();
clone.rotate(-rotation, rotationCenter);
textPoint = this.getMeridianPoint_(clone, rotationExtent, index);
textPoint.rotate(rotation, rotationCenter);
}
feature = this.featurePool_[poolIndex++];
feature.setGeometry(textPoint);
feature.set('graticule_label', this.meridiansLabels_[index].text);
vectorContext.drawFeature(feature, this.lonLabelStyle_(feature));
}
}
if (this.parallelsLabels_) {
for (index = 0, l = this.parallels_.length; index < l; ++index) {
const lineString = this.parallels_[index];
if (!rotation) {
textPoint = this.getParallelPoint_(lineString, extent, index);
} else {
const clone = lineString.clone();
clone.rotate(-rotation, rotationCenter);
textPoint = this.getParallelPoint_(clone, rotationExtent, index);
textPoint.rotate(rotation, rotationCenter);
}
feature = this.featurePool_[poolIndex++];
feature.setGeometry(textPoint);
feature.set('graticule_label', this.parallelsLabels_[index].text);
vectorContext.drawFeature(feature, this.latLabelStyle_(feature));
}
}
}
/**
* @param {import("../extent.js").Extent} extent Extent.
* @param {import("../coordinate.js").Coordinate} center Center.
@@ -727,19 +799,23 @@ class Graticule extends VectorLayer {
*/
getMeridianPoint_(lineString, extent, index) {
const flatCoordinates = lineString.getFlatCoordinates();
const clampedBottom = Math.max(extent[1], flatCoordinates[1]);
const clampedTop = Math.min(extent[3], flatCoordinates[flatCoordinates.length - 1]);
let bottom = 1;
let top = flatCoordinates.length - 1;
if (flatCoordinates[bottom] > flatCoordinates[top]) {
bottom = top;
top = 1;
}
const clampedBottom = Math.max(extent[1], flatCoordinates[bottom]);
const clampedTop = Math.min(extent[3], flatCoordinates[top]);
const lat = clamp(
extent[1] + Math.abs(extent[1] - extent[3]) * this.lonLabelPosition_,
clampedBottom, clampedTop);
const coordinate = [flatCoordinates[0], lat];
let point;
if (index in this.meridiansLabels_) {
point = this.meridiansLabels_[index].geom;
point.setCoordinates(coordinate);
} else {
point = new Point(coordinate);
}
const coordinate0 = flatCoordinates[bottom - 1] +
(flatCoordinates[top - 1] - flatCoordinates[bottom - 1]) * (lat - flatCoordinates[bottom]) /
(flatCoordinates[top] - flatCoordinates[bottom]);
const coordinate = [coordinate0, lat];
const point = this.meridiansLabels_[index].geom;
point.setCoordinates(coordinate);
return point;
}
@@ -783,19 +859,23 @@ class Graticule extends VectorLayer {
*/
getParallelPoint_(lineString, extent, index) {
const flatCoordinates = lineString.getFlatCoordinates();
const clampedLeft = Math.max(extent[0], flatCoordinates[0]);
const clampedRight = Math.min(extent[2], flatCoordinates[flatCoordinates.length - 2]);
let left = 0;
let right = flatCoordinates.length - 2;
if (flatCoordinates[left] > flatCoordinates[right]) {
left = right;
right = 0;
}
const clampedLeft = Math.max(extent[0], flatCoordinates[left]);
const clampedRight = Math.min(extent[2], flatCoordinates[right]);
const lon = clamp(
extent[0] + Math.abs(extent[0] - extent[2]) * this.latLabelPosition_,
clampedLeft, clampedRight);
const coordinate = [lon, flatCoordinates[1]];
let point;
if (index in this.parallelsLabels_) {
point = this.parallelsLabels_[index].geom;
point.setCoordinates(coordinate);
} else {
point = new Point(coordinate);
}
const coordinate1 = flatCoordinates[left + 1] +
(flatCoordinates[right + 1] - flatCoordinates[left + 1]) * (lon - flatCoordinates[left]) /
(flatCoordinates[right] - flatCoordinates[left]);
const coordinate = [lon, coordinate1];
const point = this.parallelsLabels_[index].geom;
point.setCoordinates(coordinate);
return point;
}
+4
View File
@@ -33,6 +33,10 @@ import SourceState from '../source/State.js';
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {Array<import("./Base.js").default>|import("../Collection.js").default<import("./Base.js").default>} [layers] Child layers.
*/
+7 -1
View File
@@ -24,6 +24,10 @@ import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {Array<string>} [gradient=['#00f', '#0ff', '#0f0', '#ff0', '#f00']] The color gradient
* of the heatmap, specified as an array of CSS color strings.
* @property {number} [radius=8] Radius size in pixels.
@@ -294,7 +298,9 @@ class Heatmap extends VectorLayer {
gl_FragColor.rgb *= gl_FragColor.a;
}`,
uniforms: {
u_gradientTexture: this.gradient_
u_gradientTexture: function() {
return this.gradient_;
}.bind(this)
}
}
]
+12 -1
View File
@@ -9,6 +9,7 @@ import LayerProperty from './Property.js';
import {assign} from '../obj.js';
import RenderEventType from '../render/EventType.js';
import SourceState from '../source/State.js';
import {assert} from '../asserts.js';
/**
* @typedef {function(import("../PluggableMap.js").FrameState):HTMLElement} RenderFunction
@@ -30,6 +31,10 @@ import SourceState from '../source/State.js';
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {import("../source/Source.js").default} [source] Source for this layer. If not provided to the constructor,
* the source can be set by calling {@link module:ol/layer/Layer#setSource layer.setSource(source)} after
* construction.
@@ -243,7 +248,13 @@ class Layer extends BaseLayer {
if (map) {
this.mapPrecomposeKey_ = listen(map, RenderEventType.PRECOMPOSE, function(evt) {
const renderEvent = /** @type {import("../render/Event.js").default} */ (evt);
renderEvent.frameState.layerStatesArray.push(this.getLayerState(false));
const layerStatesArray = renderEvent.frameState.layerStatesArray;
const layerState = this.getLayerState(false);
// A layer can only be added to the map once. Use either `layer.setMap()` or `map.addLayer()`, not both.
assert(!layerStatesArray.some(function(arrayLayerState) {
return arrayLayerState.layer === layerState.layer;
}), 67);
layerStatesArray.push(layerState);
}, this);
this.mapRenderKey_ = listen(this, EventType.CHANGE, map.render, map);
this.changed();
+4
View File
@@ -20,6 +20,10 @@ import CanvasVectorImageLayerRenderer from '../renderer/canvas/VectorImageLayer.
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {import("../render.js").OrderFunction} [renderOrder] Render order. Function to be used when sorting
* features before rendering. By default features are drawn in the order that they are created. Use
* `null` to avoid the sort, but get an undefined draw order.
+4
View File
@@ -24,6 +24,10 @@ import {assign} from '../obj.js';
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {import("../render.js").OrderFunction} [renderOrder] Render order. Function to be used when sorting
* features before rendering. By default features are drawn in the order that they are created. Use
* `null` to avoid the sort, but get an undefined draw order.
+4
View File
@@ -23,6 +23,10 @@ import Layer from './Layer.js';
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {number} [minZoom] The minimum view zoom level (exclusive) above which this layer will be
* visible.
* @property {number} [maxZoom] The maximum view zoom level (inclusive) at which this layer will
* be visible.
* @property {import("../source/Vector.js").default} [source] Source.
* @property {boolean} [disableHitDetection=false] Setting this to true will provide a slight performance boost, but will
* prevent all hit detection on the layer.
+60 -39
View File
@@ -4,8 +4,8 @@
import {getFontParameters} from '../css.js';
import {createCanvasContext2D} from '../dom.js';
import {clear} from '../obj.js';
import {create as createTransform} from '../transform.js';
import LabelCache from './canvas/LabelCache.js';
import BaseObject from '../Object.js';
import EventTarget from '../events/Target.js';
/**
@@ -13,6 +13,12 @@ import LabelCache from './canvas/LabelCache.js';
* @property {import("../colorlike.js").ColorLike} fillStyle
*/
/**
* @typedef Label
* @property {number} width
* @property {number} height
* @property {Array<string|number>} contextInstructions
*/
/**
* @typedef {Object} FillStrokeState
@@ -164,21 +170,23 @@ export const defaultPadding = [0, 0, 0, 0];
*/
export const defaultLineWidth = 1;
/**
* @type {BaseObject}
*/
export const checkedFonts = new BaseObject();
/**
* The label cache for text rendering. To change the default cache size of 2048
* entries, use {@link module:ol/structs/LRUCache#setSize}.
* @type {LabelCache}
* Deprecated - there is no label cache any more.
* @type {?}
* @api
* @deprecated
*/
export const labelCache = new LabelCache();
/**
* @type {!Object<string, number>}
*/
export const checkedFonts = {};
export const labelCache = new EventTarget();
labelCache.setSize = function() {
console.warn('labelCache is deprecated.'); //eslint-disable-line
};
/**
* @type {CanvasRenderingContext2D}
@@ -200,9 +208,8 @@ export const textHeights = {};
* Clears the label cache when a font becomes available.
* @param {string} fontSpec CSS font spec.
*/
export const checkFont = (function() {
export const registerFont = (function() {
const retries = 100;
const checked = checkedFonts;
const size = '32px ';
const referenceFonts = ['monospace', 'serif'];
const len = referenceFonts.length;
@@ -235,19 +242,18 @@ export const checkFont = (function() {
function check() {
let done = true;
for (const font in checked) {
if (checked[font] < retries) {
const fonts = checkedFonts.getKeys();
for (let i = 0, ii = fonts.length; i < ii; ++i) {
const font = fonts[i];
if (checkedFonts.get(font) < retries) {
if (isAvailable.apply(this, font.split('\n'))) {
checked[font] = retries;
clear(textHeights);
// Make sure that loaded fonts are picked up by Safari
measureContext = null;
measureFont = undefined;
if (labelCache.getCount()) {
labelCache.clear();
}
checkedFonts.set(font, retries);
} else {
++checked[font];
checkedFonts.set(font, checkedFonts.get(font) + 1, true);
done = false;
}
}
@@ -267,10 +273,10 @@ export const checkFont = (function() {
for (let i = 0, ii = families.length; i < ii; ++i) {
const family = families[i];
const key = font.style + '\n' + font.weight + '\n' + family;
if (!(key in checked)) {
checked[key] = retries;
if (checkedFonts.get(key) === undefined) {
checkedFonts.set(key, retries, true);
if (!isAvailable(font.style, font.weight, family)) {
checked[key] = 0;
checkedFonts.set(key, 0, true);
if (interval === undefined) {
interval = setInterval(check, 32);
}
@@ -381,14 +387,11 @@ export function rotateAtOffset(context, rotation, offsetX, offsetY) {
}
export const resetTransform = createTransform();
/**
* @param {CanvasRenderingContext2D} context Context.
* @param {import("../transform.js").Transform|null} transform Transform.
* @param {number} opacity Opacity.
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
* @param {Label|HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} labelOrImage Label.
* @param {number} originX Origin X.
* @param {number} originY Origin Y.
* @param {number} w Width.
@@ -397,23 +400,41 @@ export const resetTransform = createTransform();
* @param {number} y Y.
* @param {number} scale Scale.
*/
export function drawImage(context,
transform, opacity, image, originX, originY, w, h, x, y, scale) {
let alpha;
if (opacity != 1) {
alpha = context.globalAlpha;
context.globalAlpha = alpha * opacity;
export function drawImageOrLabel(context,
transform, opacity, labelOrImage, originX, originY, w, h, x, y, scale) {
context.save();
if (opacity !== 1) {
context.globalAlpha *= opacity;
}
if (transform) {
context.setTransform.apply(context, transform);
}
context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale);
if (opacity != 1) {
context.globalAlpha = alpha;
if ((/** @type {*} */ (labelOrImage).contextInstructions)) {
// label
context.translate(x, y);
context.scale(scale, scale);
executeLabelInstructions(/** @type {Label} */ (labelOrImage), context);
} else {
// image
context.drawImage(/** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (labelOrImage), originX, originY, w, h, x, y, w * scale, h * scale);
}
if (transform) {
context.setTransform.apply(context, resetTransform);
context.restore();
}
/**
* @param {Label} label Label.
* @param {CanvasRenderingContext2D} context Context.
*/
function executeLabelInstructions(label, context) {
const contextInstructions = label.contextInstructions;
for (let i = 0, ii = contextInstructions.length; i < ii; i += 2) {
if (Array.isArray(contextInstructions[i + 1])) {
CanvasRenderingContext2D.prototype[contextInstructions[i]].apply(context, contextInstructions[i + 1]);
} else {
context[contextInstructions[i]] = contextInstructions[i + 1];
}
}
}
+88 -92
View File
@@ -7,7 +7,7 @@ import {createEmpty, createOrUpdate,
import {lineStringLength} from '../../geom/flat/length.js';
import {drawTextOnPath} from '../../geom/flat/textpath.js';
import {transform2D} from '../../geom/flat/transform.js';
import {drawImage, defaultPadding, defaultTextBaseline} from '../canvas.js';
import {drawImageOrLabel, defaultPadding, defaultTextBaseline} from '../canvas.js';
import CanvasInstruction from './Instruction.js';
import {TEXT_ALIGN} from './TextBuilder.js';
import {
@@ -16,9 +16,7 @@ import {
apply as applyTransform,
setFromArray as transformSetFromArray
} from '../../transform.js';
import {createCanvasContext2D} from '../../dom.js';
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import Disposable from '../../Disposable.js';
import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import RBush from 'rbush/rbush.js';
@@ -52,7 +50,7 @@ const p3 = [];
const p4 = [];
class Executor extends Disposable {
class Executor {
/**
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
@@ -60,7 +58,6 @@ class Executor extends Disposable {
* @param {SerializableInstructions} instructions The serializable instructions
*/
constructor(resolution, pixelRatio, overlaps, instructions) {
super();
/**
* @protected
@@ -154,85 +151,84 @@ class Executor extends Disposable {
* @type {Object<string, Object<string, number>>}
*/
this.widths_ = {};
}
/**
* @inheritDoc
*/
disposeInternal() {
labelCache.release(this);
super.disposeInternal();
/**
* @private
* @type {Object<string, import("../canvas.js").Label>}
*/
this.labels_ = {};
}
/**
* @param {string} text Text.
* @param {string} textKey Text style key.
* @param {string} fillKey Fill style key.
* @param {string} strokeKey Stroke style key.
* @return {HTMLCanvasElement} Image.
* @return {import("../canvas.js").Label} Label.
*/
getTextImage(text, textKey, fillKey, strokeKey) {
let label;
const key = strokeKey + textKey + text + fillKey + this.pixelRatio;
createLabel(text, textKey, fillKey, strokeKey) {
const key = text + textKey + fillKey + strokeKey;
if (this.labels_[key]) {
return this.labels_[key];
}
const strokeState = strokeKey ? this.strokeStates[strokeKey] : null;
const fillState = fillKey ? this.fillStates[fillKey] : null;
const textState = this.textStates[textKey];
const pixelRatio = this.pixelRatio;
const scale = textState.scale * pixelRatio;
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
const strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
if (!labelCache.containsKey(key)) {
const strokeState = strokeKey ? this.strokeStates[strokeKey] : null;
const fillState = fillKey ? this.fillStates[fillKey] : null;
const textState = this.textStates[textKey];
const pixelRatio = this.pixelRatio;
const scale = textState.scale * pixelRatio;
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
const strokeWidth = strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
const lines = text.split('\n');
const numLines = lines.length;
const widths = [];
const width = measureTextWidths(textState.font, lines, widths);
const lineHeight = measureTextHeight(textState.font);
const height = lineHeight * numLines;
const renderWidth = width + strokeWidth;
const context = createCanvasContext2D(
// make canvas 2 pixels wider to account for italic text width measurement errors
Math.ceil((renderWidth + 2) * scale),
Math.ceil((height + strokeWidth) * scale));
label = context.canvas;
labelCache.set(key, label);
if (scale != 1) {
context.scale(scale, scale);
}
context.font = textState.font;
if (strokeKey) {
context.strokeStyle = strokeState.strokeStyle;
context.lineWidth = strokeWidth;
context.lineCap = strokeState.lineCap;
context.lineJoin = strokeState.lineJoin;
context.miterLimit = strokeState.miterLimit;
if (context.setLineDash && strokeState.lineDash.length) {
context.setLineDash(strokeState.lineDash);
context.lineDashOffset = strokeState.lineDashOffset;
}
}
if (fillKey) {
context.fillStyle = fillState.fillStyle;
}
context.textBaseline = 'middle';
context.textAlign = 'center';
const leftRight = (0.5 - align);
const x = align * renderWidth + leftRight * strokeWidth;
let i;
if (strokeKey) {
for (i = 0; i < numLines; ++i) {
context.strokeText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
}
}
if (fillKey) {
for (i = 0; i < numLines; ++i) {
context.fillText(lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight);
}
const lines = text.split('\n');
const numLines = lines.length;
const widths = [];
const width = measureTextWidths(textState.font, lines, widths);
const lineHeight = measureTextHeight(textState.font);
const height = lineHeight * numLines;
const renderWidth = width + strokeWidth;
const contextInstructions = [];
/** @type {import("../canvas.js").Label} */
const label = {
// make canvas 2 pixels wider to account for italic text width measurement errors
width: Math.ceil((renderWidth + 2) * scale),
height: Math.ceil((height + strokeWidth) * scale),
contextInstructions: contextInstructions
};
if (scale != 1) {
contextInstructions.push('scale', [scale, scale]);
}
contextInstructions.push('font', textState.font);
if (strokeKey) {
contextInstructions.push('strokeStyle', strokeState.strokeStyle);
contextInstructions.push('lineWidth', strokeWidth);
contextInstructions.push('lineCap', strokeState.lineCap);
contextInstructions.push('lineJoin', strokeState.lineJoin);
contextInstructions.push('miterLimit', strokeState.miterLimit);
if (CanvasRenderingContext2D.prototype.setLineDash) {
contextInstructions.push('setLineDash', [strokeState.lineDash]);
contextInstructions.push('lineDashOffset', strokeState.lineDashOffset);
}
}
return labelCache.get(key, this);
if (fillKey) {
contextInstructions.push('fillStyle', fillState.fillStyle);
}
contextInstructions.push('textBaseline', 'middle');
contextInstructions.push('textAlign', 'center');
const leftRight = (0.5 - align);
const x = align * renderWidth + leftRight * strokeWidth;
let i;
if (strokeKey) {
for (i = 0; i < numLines; ++i) {
contextInstructions.push('strokeText', [lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight]);
}
}
if (fillKey) {
for (i = 0; i < numLines; ++i) {
contextInstructions.push('fillText', [lines[i], x + leftRight * widths[i], 0.5 * (strokeWidth + lineHeight) + i * lineHeight]);
}
}
this.labels_[key] = label;
return label;
}
/**
@@ -265,7 +261,7 @@ class Executor extends Disposable {
* @param {CanvasRenderingContext2D} context Context.
* @param {number} x X.
* @param {number} y Y.
* @param {HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} image Image.
* @param {import("../canvas.js").Label|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} imageOrLabel Image.
* @param {number} anchorX Anchor X.
* @param {number} anchorY Anchor Y.
* @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group.
@@ -281,11 +277,11 @@ class Executor extends Disposable {
* @param {Array<*>} fillInstruction Fill instruction.
* @param {Array<*>} strokeInstruction Stroke instruction.
*/
replayImage_(
replayImageOrLabel_(
context,
x,
y,
image,
imageOrLabel,
anchorX,
anchorY,
declutterGroup,
@@ -307,8 +303,8 @@ class Executor extends Disposable {
x -= anchorX;
y -= anchorY;
const w = (width + originX > image.width) ? image.width - originX : width;
const h = (height + originY > image.height) ? image.height - originY : height;
const w = (width + originX > imageOrLabel.width) ? imageOrLabel.width - originX : width;
const h = (height + originY > imageOrLabel.height) ? imageOrLabel.height - originY : height;
const boxW = padding[3] + w * scale + padding[1];
const boxH = padding[0] + h * scale + padding[2];
const boxX = x - padding[3];
@@ -362,11 +358,11 @@ class Executor extends Disposable {
}
extend(declutterGroup, tmpExtent);
const declutterArgs = intersects ?
[context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] :
[context, transform ? transform.slice(0) : null, opacity, imageOrLabel, originX, originY, w, h, x, y, scale] :
null;
if (declutterArgs) {
if (fillStroke) {
declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4);
declutterArgs.push(fillInstruction, strokeInstruction, p1.slice(0), p2.slice(0), p3.slice(0), p4.slice(0));
}
declutterGroup.push(declutterArgs);
}
@@ -376,7 +372,7 @@ class Executor extends Disposable {
/** @type {Array<*>} */ (fillInstruction),
/** @type {Array<*>} */ (strokeInstruction));
}
drawImage(context, transform, opacity, image, originX, originY, w, h, x, y, scale);
drawImageOrLabel(context, transform, opacity, imageOrLabel, originX, originY, w, h, x, y, scale);
}
}
@@ -451,7 +447,7 @@ class Executor extends Disposable {
declutterData[13], declutterData[14], declutterData[15], declutterData[16],
declutterData[11], declutterData[12]);
}
drawImage.apply(undefined, declutterData);
drawImageOrLabel.apply(undefined, declutterData);
if (currentAlpha !== opacity) {
context.globalAlpha = currentAlpha;
}
@@ -470,12 +466,12 @@ class Executor extends Disposable {
* @param {string} textKey The key of the text state.
* @param {string} strokeKey The key for the stroke state.
* @param {string} fillKey The key for the fill state.
* @return {{label: HTMLCanvasElement, anchorX: number, anchorY: number}} The text image and its anchor.
* @return {{label: import("../canvas.js").Label, anchorX: number, anchorY: number}} The text image and its anchor.
*/
drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey) {
drawLabelWithPointPlacement_(text, textKey, strokeKey, fillKey) {
const textState = this.textStates[textKey];
const label = this.getTextImage(text, textKey, fillKey, strokeKey);
const label = this.createLabel(text, textKey, fillKey, strokeKey);
const strokeState = this.strokeStates[strokeKey];
const pixelRatio = this.pixelRatio;
@@ -483,7 +479,7 @@ class Executor extends Disposable {
const baseline = TEXT_ALIGN[textState.textBaseline || defaultTextBaseline];
const strokeWidth = strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
// Remove the 2 pixels we added in getTextImage() for the anchor
// Remove the 2 pixels we added in createLabel() for the anchor
const width = label.width / pixelRatio - 2 * textState.scale;
const anchorX = align * width + 2 * (0.5 - align) * strokeWidth;
const anchorY = baseline * label.height / pixelRatio + 2 * (0.5 - baseline) * strokeWidth;
@@ -648,7 +644,7 @@ class Executor extends Disposable {
textKey = /** @type {string} */ (instruction[19]);
strokeKey = /** @type {string} */ (instruction[20]);
fillKey = /** @type {string} */ (instruction[21]);
const labelWithAnchor = this.drawTextImageWithPointPlacement_(text, textKey, strokeKey, fillKey);
const labelWithAnchor = this.drawLabelWithPointPlacement_(text, textKey, strokeKey, fillKey);
image = labelWithAnchor.label;
instruction[3] = image;
const textOffsetX = /** @type {number} */ (instruction[22]);
@@ -701,7 +697,7 @@ class Executor extends Disposable {
}
declutterGroup = declutterGroups[index];
}
this.replayImage_(context,
this.replayImageOrLabel_(context,
pixelCoordinates[d], pixelCoordinates[d + 1], image, anchorX, anchorY,
declutterGroup, height, opacity, originX, originY, rotation, scale,
snapToPixel, width, padding,
@@ -758,10 +754,10 @@ class Executor extends Disposable {
for (c = 0, cc = parts.length; c < cc; ++c) {
part = parts[c]; // x, y, anchorX, rotation, chunk
chars = /** @type {string} */ (part[4]);
label = this.getTextImage(chars, textKey, '', strokeKey);
label = this.createLabel(chars, textKey, '', strokeKey);
anchorX = /** @type {number} */ (part[2]) + strokeWidth;
anchorY = baseline * label.height + (0.5 - baseline) * 2 * strokeWidth - offsetY;
this.replayImage_(context,
this.replayImageOrLabel_(context,
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
/** @type {number} */ (part[3]), pixelRatioScale, false, label.width,
@@ -772,10 +768,10 @@ class Executor extends Disposable {
for (c = 0, cc = parts.length; c < cc; ++c) {
part = parts[c]; // x, y, anchorX, rotation, chunk
chars = /** @type {string} */ (part[4]);
label = this.getTextImage(chars, textKey, fillKey, '');
label = this.createLabel(chars, textKey, fillKey, '');
anchorX = /** @type {number} */ (part[2]);
anchorY = baseline * label.height - offsetY;
this.replayImage_(context,
this.replayImageOrLabel_(context,
/** @type {number} */ (part[0]), /** @type {number} */ (part[1]), label,
anchorX, anchorY, declutterGroup, label.height, 1, 0, 0,
/** @type {number} */ (part[3]), pixelRatioScale, false, label.width,
+1 -21
View File
@@ -10,7 +10,6 @@ import {isEmpty} from '../../obj.js';
import BuilderType from './BuilderType.js';
import {create as createTransform, compose as composeTransform} from '../../transform.js';
import Executor from './Executor.js';
import Disposable from '../../Disposable.js';
/**
* @const
@@ -26,7 +25,7 @@ const ORDER = [
];
class ExecutorGroup extends Disposable {
class ExecutorGroup {
/**
* @param {import("../../extent.js").Extent} maxExtent Max extent for clipping. When a
* `maxExtent` was set on the Buillder for this executor group, the same `maxExtent`
@@ -40,7 +39,6 @@ class ExecutorGroup extends Disposable {
* @param {number=} opt_renderBuffer Optional rendering buffer.
*/
constructor(maxExtent, resolution, pixelRatio, overlaps, allInstructions, opt_renderBuffer) {
super();
/**
* @private
@@ -128,24 +126,6 @@ class ExecutorGroup extends Disposable {
}
}
/**
* @inheritDoc
*/
disposeInternal() {
for (const z in this.executorsByZIndex_) {
const executors = this.executorsByZIndex_[z];
for (const key in executors) {
executors[key].disposeInternal();
}
}
if (this.hitDetectionContext_) {
const canvas = this.hitDetectionContext_.canvas;
canvas.width = 0;
canvas.height = 0;
}
super.disposeInternal();
}
/**
* @param {Array<BuilderType>} executors Executors.
-69
View File
@@ -1,69 +0,0 @@
import {getUid} from '../../util.js';
import LRUCache from '../../structs/LRUCache.js';
/**
* @module ol/render/canvas/LabelCache
*/
/**
* @classdesc
* Cache of pre-rendered labels.
*/
class LabelCache extends LRUCache {
/**
* @inheritDoc
*/
constructor(opt_highWaterMark) {
super(opt_highWaterMark);
this.consumers = {};
}
clear() {
this.consumers = {};
super.clear();
}
/**
* @override
* @param {string} key Label key.
* @param {import("./Executor.js").default} consumer Label consumer.
* @return {HTMLCanvasElement} Label.
*/
get(key, consumer) {
const canvas = super.get(key);
const consumerId = getUid(consumer);
if (!(consumerId in this.consumers)) {
this.consumers[consumerId] = {};
}
this.consumers[consumerId][key] = true;
return canvas;
}
prune() {
outer:
while (this.canExpireCache()) {
const key = this.peekLastKey();
for (const consumerId in this.consumers) {
if (key in this.consumers[consumerId]) {
break outer;
}
}
const canvas = this.pop();
canvas.width = 0;
canvas.height = 0;
for (const consumerId in this.consumers) {
delete this.consumers[consumerId][key];
}
}
}
/**
* @param {import("./Executor.js").default} consumer Label consumer.
*/
release(consumer) {
delete this.consumers[getUid(consumer)];
}
}
export default LabelCache;
+3 -5
View File
@@ -6,7 +6,7 @@ import {asColorLike} from '../../colorlike.js';
import {intersects} from '../../extent.js';
import {matchingChunk} from '../../geom/flat/straightchunk.js';
import GeometryType from '../../geom/GeometryType.js';
import {labelCache, defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, checkFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js';
import {defaultTextAlign, defaultPadding, defaultLineCap, defaultLineDashOffset, defaultLineDash, defaultLineJoin, defaultFillStyle, registerFont, defaultFont, defaultLineWidth, defaultMiterLimit, defaultStrokeStyle, defaultTextBaseline} from '../canvas.js';
import CanvasInstruction from './Instruction.js';
import CanvasBuilder from './Builder.js';
import TextPlacement from '../../style/TextPlacement.js';
@@ -131,8 +131,6 @@ class CanvasTextBuilder extends CanvasBuilder {
* @type {string}
*/
this.strokeKey_ = '';
labelCache.prune();
}
/**
@@ -433,7 +431,7 @@ class CanvasTextBuilder extends CanvasBuilder {
textState = this.textState_;
const font = textStyle.getFont() || defaultFont;
checkFont(font);
registerFont(font);
const textScale = textStyle.getScale();
textState.overflow = textStyle.getOverflow();
textState.font = font;
@@ -461,7 +459,7 @@ class CanvasTextBuilder extends CanvasBuilder {
strokeState.lineCap + strokeState.lineDashOffset + '|' + strokeState.lineWidth +
strokeState.lineJoin + strokeState.miterLimit + '[' + strokeState.lineDash.join() + ']' :
'';
this.textKey_ = textState.font + textState.scale + (textState.textAlign || '?');
this.textKey_ = textState.font + textState.scale + (textState.textAlign || '?') + (textState.textBaseline || '?');
this.fillKey_ = fillState ?
(typeof fillState.fillStyle == 'string' ? fillState.fillStyle : ('|' + getUid(fillState.fillStyle))) :
'';
+3 -2
View File
@@ -33,7 +33,7 @@ export function createHitDetectionImageData(size, transforms, features, styleFun
const renderer = new CanvasImmediateRenderer(context, 0.5, extent, null, rotation);
const featureCount = features.length;
// Stretch hit detection index to use the whole available color range
const indexFactor = Math.ceil((256 * 256 * 256 - 1) / featureCount);
const indexFactor = Math.floor((256 * 256 * 256 - 1) / featureCount);
const featuresByZIndex = {};
for (let i = 1; i <= featureCount; ++i) {
const feature = features[i - 1];
@@ -121,6 +121,7 @@ export function createHitDetectionImageData(size, transforms, features, styleFun
}
}
}
document.body.appendChild(context.canvas);
return context.getImageData(0, 0, canvas.width, canvas.height);
}
@@ -141,7 +142,7 @@ export function hitDetect(pixel, features, imageData) {
const g = imageData.data[index + 1];
const b = imageData.data[index + 2];
const i = b + (256 * (g + (256 * r)));
const indexFactor = Math.ceil((256 * 256 * 256 - 1) / features.length);
const indexFactor = Math.floor((256 * 256 * 256 - 1) / features.length);
if (i && i % indexFactor === 0) {
resultFeatures.push(features[i / indexFactor - 1]);
}
+4 -4
View File
@@ -8,9 +8,9 @@ import RenderEventType from '../render/EventType.js';
import MapRenderer from './Map.js';
import SourceState from '../source/State.js';
import {replaceChildren} from '../dom.js';
import {labelCache} from '../render/canvas.js';
import EventType from '../events/EventType.js';
import {listen, unlistenByKey} from '../events.js';
import {checkedFonts} from '../render/canvas.js';
import ObjectEventType from '../ObjectEventType.js';
/**
@@ -29,7 +29,7 @@ class CompositeMapRenderer extends MapRenderer {
/**
* @type {import("../events.js").EventsKey}
*/
this.labelCacheKey_ = listen(labelCache, EventType.CLEAR, map.redrawText.bind(map));
this.fontChangeListenerKey_ = listen(checkedFonts, ObjectEventType.PROPERTYCHANGE, map.redrawText.bind(map));
/**
* @private
@@ -73,7 +73,7 @@ class CompositeMapRenderer extends MapRenderer {
}
disposeInternal() {
unlistenByKey(this.labelCacheKey_);
unlistenByKey(this.fontChangeListenerKey_);
this.element_.parentNode.removeChild(this.element_);
super.disposeInternal();
}
-22
View File
@@ -362,7 +362,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
projection, extent, z, tileLayer.getPreload());
this.updateCacheSize_(frameState, tileSource);
this.scheduleExpireCache(frameState, tileSource);
this.postRender(context, frameState);
@@ -474,27 +473,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
usedTiles[tileSourceKey][tile.getKey()] = true;
}
/**
* Check if the cache is big enough, and increase its size if necessary.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @param {import("../../source/Tile.js").default} tileSource Tile source.
* @private
*/
updateCacheSize_(frameState, tileSource) {
const tileSourceKey = getUid(tileSource);
let size = 0;
if (tileSourceKey in frameState.usedTiles) {
size += Object.keys(frameState.usedTiles[tileSourceKey]).length;
}
if (tileSourceKey in frameState.wantedTiles) {
size += Object.keys(frameState.wantedTiles[tileSourceKey]).length;
}
const tileCache = tileSource.tileCache;
if (tileCache.highWaterMark < size) {
tileCache.highWaterMark = size;
}
}
/**
* Manage tile pyramid.
* This function performs a number of functions related to the tiles at the
@@ -105,6 +105,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer {
const context = vectorRenderer.context;
const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, {
declutterItems: [],
extent: renderedExtent,
size: [width, height],
viewState: /** @type {import("../../View.js").State} */ (assign({}, frameState.viewState, {
rotation: 0
+4 -4
View File
@@ -329,6 +329,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
prepareFrame(frameState) {
const vectorLayer = this.getLayer();
const vectorSource = vectorLayer.getSource();
if (!vectorSource) {
return false;
}
const animating = frameState.viewHints[ViewHint.ANIMATING];
const interacting = frameState.viewHints[ViewHint.INTERACTING];
@@ -384,9 +387,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
return true;
}
if (this.replayGroup_) {
this.replayGroup_.dispose();
}
this.replayGroup_ = null;
this.dirty_ = false;
@@ -457,7 +457,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
* @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.
* @param {import("../../proj.js").TransformFunction} opt_transform Transform from user to view projection.
* @param {import("../../proj.js").TransformFunction=} opt_transform Transform from user to view projection.
* @return {boolean} `true` if an image is loading.
*/
renderFeature(feature, squaredTolerance, styles, builderGroup, opt_transform) {
+1 -11
View File
@@ -117,8 +117,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
let render;
const tileUid = getUid(tile);
const state = tile.getState();
if (((state === TileState.LOADED && tile.hifi) ||
state === TileState.ERROR || state === TileState.ABORT) &&
if (((state === TileState.LOADED && tile.hifi) || state === TileState.ERROR) &&
tileUid in this.tileListenerKeys_) {
unlistenByKey(this.tileListenerKeys_[tileUid]);
delete this.tileListenerKeys_[tileUid];
@@ -219,12 +218,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
const sourceTiles = source.getSourceTiles(pixelRatio, projection, tile);
const layerUid = getUid(layer);
const executorGroups = tile.executorGroups[layerUid];
if (executorGroups) {
for (let i = 0, ii = executorGroups.length; i < ii; ++i) {
executorGroups[i].dispose();
}
}
delete tile.hitDetectionImageData[layerUid];
tile.executorGroups[layerUid] = [];
for (let t = 0, tt = sourceTiles.length; t < tt; ++t) {
@@ -468,9 +461,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
const clipZs = [];
for (let i = tiles.length - 1; i >= 0; --i) {
const tile = /** @type {import("../../VectorRenderTile.js").default} */ (tiles[i]);
if (tile.getState() == TileState.ABORT) {
continue;
}
const tileCoord = tile.tileCoord;
const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0];
-10
View File
@@ -211,16 +211,6 @@ class ReprojTile extends Tile {
}
}
/**
* @inheritDoc
*/
disposeInternal() {
if (this.state == TileState.LOADING) {
this.unlistenSources_();
}
super.disposeInternal();
}
/**
* Get the HTML Canvas element for this tile.
* @return {HTMLCanvasElement} Canvas.
+16 -8
View File
@@ -11,16 +11,21 @@ import {clamp} from './math.js';
*/
/**
* Returns a modified resolution taking into acocunt the viewport size and maximum
* Returns a modified resolution taking into account the viewport size and maximum
* allowed extent.
* @param {number} resolution Resolution
* @param {import("./extent.js").Extent=} maxExtent Maximum allowed extent.
* @param {import("./size.js").Size} viewportSize Viewport size.
* @param {boolean} showFullExtent Whether to show the full extent.
* @return {number} Capped resolution.
*/
function getViewportClampedResolution(resolution, maxExtent, viewportSize) {
function getViewportClampedResolution(resolution, maxExtent, viewportSize, showFullExtent) {
const xResolution = getWidth(maxExtent) / viewportSize[0];
const yResolution = getHeight(maxExtent) / viewportSize[1];
if (showFullExtent) {
return Math.min(resolution, Math.max(xResolution, yResolution));
}
return Math.min(resolution, Math.min(xResolution, yResolution));
}
@@ -52,9 +57,10 @@ function getSmoothClampedResolution(resolution, maxResolution, minResolution) {
* @param {Array<number>} resolutions Resolutions.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @param {boolean=} opt_showFullExtent If true, allows us to show the full extent. Default: false.
* @return {Type} Zoom function.
*/
export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent) {
export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent, opt_showFullExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
@@ -68,7 +74,7 @@ export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent)
const maxResolution = resolutions[0];
const minResolution = resolutions[resolutions.length - 1];
const cappedMaxRes = opt_maxExtent ?
getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
getViewportClampedResolution(maxResolution, opt_maxExtent, size, opt_showFullExtent) :
maxResolution;
// during interacting or animating, allow intermediary values
@@ -100,9 +106,10 @@ export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent)
* @param {number=} opt_minResolution Minimum resolution.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @param {boolean=} opt_showFullExtent If true, allows us to show the full extent. Default: false.
* @return {Type} Zoom function.
*/
export function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent) {
export function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent, opt_showFullExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
@@ -114,7 +121,7 @@ export function createSnapToPower(power, maxResolution, opt_minResolution, opt_s
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
const cappedMaxRes = opt_maxExtent ?
getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
getViewportClampedResolution(maxResolution, opt_maxExtent, size, opt_showFullExtent) :
maxResolution;
const minResolution = opt_minResolution !== undefined ? opt_minResolution : 0;
@@ -148,9 +155,10 @@ export function createSnapToPower(power, maxResolution, opt_minResolution, opt_s
* @param {number} minResolution Min resolution.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @param {boolean=} opt_showFullExtent If true, allows us to show the full extent. Default: false.
* @return {Type} Zoom function.
*/
export function createMinMaxResolution(maxResolution, minResolution, opt_smooth, opt_maxExtent) {
export function createMinMaxResolution(maxResolution, minResolution, opt_smooth, opt_maxExtent, opt_showFullExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
@@ -162,7 +170,7 @@ export function createMinMaxResolution(maxResolution, minResolution, opt_smooth,
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
const cappedMaxRes = opt_maxExtent ?
getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
getViewportClampedResolution(maxResolution, opt_maxExtent, size, opt_showFullExtent) :
maxResolution;
const smooth = opt_smooth !== undefined ? opt_smooth : true;
+1 -1
View File
@@ -71,7 +71,7 @@ export function createSnapToZero(opt_tolerance) {
return (
/**
* @param {number|undefined} rotation Rotation.
* @param {boolean} opt_isMoving True if an interaction or animation is in progress.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Rotation.
*/
function(rotation, opt_isMoving) {
+1 -1
View File
@@ -50,7 +50,7 @@ const TOS_ATTRIBUTION = '<a class="ol-attribution-bing-tos" ' +
/**
* @typedef {Object} Options
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {boolean} [hidpi=false] If `true` hidpi tiles will be requested.
* @property {string} [culture='en-us'] Culture code.
* @property {string} key Bing Maps API key. Get yours at http://www.bingmapsportal.com/.
+1 -1
View File
@@ -9,7 +9,7 @@ import XYZ from './XYZ.js';
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
+33 -10
View File
@@ -29,7 +29,7 @@ import VectorSource from './Vector.js';
* ```
* See {@link module:ol/geom/Polygon~Polygon#getInteriorPoint} for a way to get a cluster
* calculation point for polygons.
* @property {VectorSource} source Source.
* @property {VectorSource} [source] Source.
* @property {boolean} [wrapX=true] Whether to wrap the world horizontally.
*/
@@ -39,6 +39,10 @@ import VectorSource from './Vector.js';
* Layer source to cluster vector data. Works out of the box with point
* geometries. For other geometry types, or if not all geometries should be
* considered for clustering, a custom `geometryFunction` can be defined.
*
* If the instance is disposed without also disposing the underlying
* source `setSource(null)` has to be called to remove the listener reference
* from the wrapped source.
* @api
*/
class Cluster extends VectorSource {
@@ -81,13 +85,17 @@ class Cluster extends VectorSource {
return geometry;
};
/**
* @type {VectorSource}
* @protected
*/
this.source = options.source;
this.boundRefresh_ = this.refresh.bind(this);
this.source.addEventListener(EventType.CHANGE, this.refresh.bind(this));
this.setSource(options.source || null);
}
/**
* @override
*/
clear(opt_fast) {
this.features.length = 0;
super.clear(opt_fast);
}
/**
@@ -132,7 +140,23 @@ class Cluster extends VectorSource {
}
/**
* handle the source changing
* Replace the wrapped source.
* @param {VectorSource} source The new source for this instance.
* @api
*/
setSource(source) {
if (this.source) {
this.source.removeEventListener(EventType.CHANGE, this.boundRefresh_);
}
this.source = source;
if (source) {
source.addEventListener(EventType.CHANGE, this.boundRefresh_);
}
this.refresh();
}
/**
* Handle the source changing.
* @override
*/
refresh() {
@@ -145,10 +169,9 @@ class Cluster extends VectorSource {
* @protected
*/
cluster() {
if (this.resolution === undefined) {
if (this.resolution === undefined || !this.source) {
return;
}
this.features.length = 0;
const extent = createEmpty();
const mapDistance = this.distance * this.resolution;
const features = this.source.getFeatures();
+6 -3
View File
@@ -9,6 +9,7 @@ import {Versions} from '../format/IIIFInfo.js';
import {assert} from '../asserts.js';
import TileGrid from '../tilegrid/TileGrid.js';
import TileImage from './TileImage.js';
import {toSize} from '../size.js';
/**
* @typedef {Object} Options
@@ -60,7 +61,7 @@ function formatPercentage(percentage) {
class IIIF extends TileImage {
/**
* @param {Options} opt_options Tile source options. Use {@link import("../format/IIIFInfo.js").IIIFInfo}
* @param {Options=} opt_options Tile source options. Use {@link import("../format/IIIFInfo.js").IIIFInfo}
* to parse Image API service information responses into constructor options.
* @api
*/
@@ -89,7 +90,7 @@ class IIIF extends TileImage {
const extent = options.extent || [0, -height, width, 0];
const supportsListedSizes = sizes != undefined && Array.isArray(sizes) && sizes.length > 0;
const supportsListedTiles = tileSize != undefined && (typeof tileSize === 'number' && Number.isInteger(tileSize) && tileSize > 0 || Array.isArray(tileSize) && tileSize.length > 0);
const supportsListedTiles = tileSize !== undefined && (typeof tileSize === 'number' && Number.isInteger(tileSize) && tileSize > 0 || Array.isArray(tileSize) && tileSize.length > 0);
const supportsArbitraryTiling = supports != undefined && Array.isArray(supports) &&
(supports.includes('regionByPx') || supports.includes('regionByPct')) &&
(supports.includes('sizeByWh') || supports.includes('sizeByH') ||
@@ -267,7 +268,9 @@ class IIIF extends TileImage {
return baseUrl + regionParam + '/' + sizeParam + '/0/' + quality + '.' + format;
};
const IiifTileClass = CustomTile.bind(null, tilePixelRatio, tileGrid);
const IiifTileClass = CustomTile.bind(null, toSize(tileSize || 256).map(function(size) {
return size * tilePixelRatio;
}));
super({
attributions: options.attributions,
+1 -1
View File
@@ -20,7 +20,7 @@ export const ATTRIBUTION = '&#169; ' +
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin='anonymous'] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
+1 -1
View File
@@ -90,7 +90,7 @@ const ProviderConfig = {
/**
* @typedef {Object} Options
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {string} layer Layer name.
* @property {number} [minZoom] Minimum zoom.
* @property {number} [maxZoom] Maximum zoom.
+10 -16
View File
@@ -69,24 +69,21 @@ class TileSource extends Source {
*/
this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null;
let cacheSize = options.cacheSize;
if (cacheSize === undefined) {
const tileSize = [256, 256];
const tileGrid = options.tileGrid;
if (tileGrid) {
toSize(tileGrid.getTileSize(tileGrid.getMinZoom()), tileSize);
}
const canUseScreen = typeof screen !== 'undefined';
const width = canUseScreen ? (screen.availWidth || screen.width) : 1920;
const height = canUseScreen ? (screen.availHeight || screen.height) : 1080;
cacheSize = 4 * Math.ceil(width / tileSize[0]) * Math.ceil(height / tileSize[1]);
const tileSize = [256, 256];
const tileGrid = options.tileGrid;
if (tileGrid) {
toSize(tileGrid.getTileSize(tileGrid.getMinZoom()), tileSize);
}
const canUseScreen = typeof screen !== 'undefined';
const width = canUseScreen ? (screen.availWidth || screen.width) : 1920;
const height = canUseScreen ? (screen.availHeight || screen.height) : 1080;
const minCacheSize = 4 * Math.ceil(width / tileSize[0]) * Math.ceil(height / tileSize[1]);
/**
* @protected
* @type {import("../TileCache.js").default}
*/
this.tileCache = new TileCache(cacheSize);
this.tileCache = new TileCache(Math.max(minCacheSize, options.cacheSize || 0));
/**
* @protected
@@ -125,7 +122,7 @@ class TileSource extends Source {
/**
* @param {import("../proj/Projection.js").default} projection Projection.
* @param {!Object<string, import("../TileRange.js").default>} usedTiles Used tiles.
* @param {!Object<string, boolean>} usedTiles Used tiles.
*/
expireCache(projection, usedTiles) {
const tileCache = this.getTileCacheForProjection(projection);
@@ -317,9 +314,6 @@ class TileSource extends Source {
this.tileCache.clear();
}
/**
* @inheritDoc
*/
refresh() {
this.clear();
super.refresh();
+1 -1
View File
@@ -13,7 +13,7 @@ import {appendParams} from '../uri.js';
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
+1 -1
View File
@@ -17,7 +17,7 @@ import {getForProjection as getTileGridForProjection} from '../tilegrid.js';
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
+1 -1
View File
@@ -39,7 +39,7 @@ import {createXYZ, extentFromProjection} from '../tilegrid.js';
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
+1 -1
View File
@@ -20,7 +20,7 @@ import {appendParams} from '../uri.js';
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
+1 -1
View File
@@ -139,7 +139,7 @@ class UrlTile extends TileSource {
} else if (uid in this.tileLoadingKeys_) {
delete this.tileLoadingKeys_[uid];
type = tileState == TileState.ERROR ? TileEventType.TILELOADERROR :
(tileState == TileState.LOADED || tileState == TileState.ABORT) ?
tileState == TileState.LOADED ?
TileEventType.TILELOADEND : undefined;
}
if (type != undefined) {
-3
View File
@@ -914,9 +914,6 @@ class VectorSource extends Source {
}
}
/**
* @inheritDoc
*/
refresh() {
this.clear(true);
this.loadedExtentsRtree_.clear();
+27 -58
View File
@@ -12,8 +12,8 @@ import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.
import {buffer as bufferExtent, getIntersection, intersects} from '../extent.js';
import EventType from '../events/EventType.js';
import {loadFeaturesXhr} from '../featureloader.js';
import {equals, remove} from '../array.js';
import {listen, unlistenByKey} from '../events.js';
import {equals} from '../array.js';
import TileCache from '../TileCache.js';
/**
* @typedef {Object} Options
@@ -30,9 +30,10 @@ import {listen, unlistenByKey} from '../events.js';
* @property {import("./State.js").default} [state] Source state.
* @property {typeof import("../VectorTile.js").default} [tileClass] Class used to instantiate image tiles.
* Default is {@link module:ol/VectorTile}.
* @property {number} [maxZoom=22] Optional max zoom level.
* @property {number} [minZoom] Optional min zoom level.
* @property {number|import("../size.js").Size} [tileSize=512] Optional tile size.
* @property {number} [maxZoom=22] Optional max zoom level. Not used if `tileGrid` is provided.
* @property {number} [minZoom] Optional min zoom level. Not used if `tileGrid` is provided.
* @property {number|import("../size.js").Size} [tileSize=512] Optional tile size. Not used if `tileGrid` is provided.
* @property {number} [maxResolution] Optional tile grid resolution at level zero. Not used if `tileGrid` is provided.
* @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
* @property {import("../Tile.js").LoadFunction} [tileLoadFunction]
* Optional function to load a tile given a URL. Could look like this for pbf tiles:
@@ -105,6 +106,7 @@ class VectorTile extends UrlTile {
const tileGrid = options.tileGrid || createXYZ({
extent: extent,
maxResolution: options.maxResolution,
maxZoom: options.maxZoom !== undefined ? options.maxZoom : 22,
minZoom: options.minZoom,
tileSize: options.tileSize || 512
@@ -140,15 +142,9 @@ class VectorTile extends UrlTile {
/**
* @private
* @type {Object<string, import("../VectorTile.js").default>}
* @type {TileCache}
*/
this.sourceTileByKey_ = {};
/**
* @private
* @type {Object<string, Array<import("../VectorTile.js").default>>}
*/
this.sourceTilesByTileKey_ = {};
this.sourceTileCache = new TileCache(this.tileCache.highWaterMark);
/**
* @private
@@ -228,8 +224,16 @@ class VectorTile extends UrlTile {
*/
clear() {
this.tileCache.clear();
this.sourceTileByKey_ = {};
this.sourceTilesByTileKey_ = {};
this.sourceTileCache.clear();
}
/**
* @param {import("../proj/Projection.js").default} projection Projection.
* @param {!Object<string, boolean>} usedTiles Used tiles.
*/
expireCache(projection, usedTiles) {
super.expireCache(projection, usedTiles);
this.sourceTileCache.expireCache({});
}
/**
@@ -254,7 +258,7 @@ class VectorTile extends UrlTile {
const sourceZ = sourceTileGrid.getZForResolution(resolution, 1);
const minZoom = sourceTileGrid.getMinZoom();
const previousSourceTiles = this.sourceTilesByTileKey_[tile.getKey()];
const previousSourceTiles = tile.sourceTiles;
let sourceTiles, covered, loadedZ;
if (previousSourceTiles && previousSourceTiles.length > 0 && previousSourceTiles[0].tileCoord[0] === sourceZ) {
sourceTiles = previousSourceTiles;
@@ -270,8 +274,8 @@ class VectorTile extends UrlTile {
const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
let sourceTile;
if (tileUrl !== undefined) {
if (tileUrl in this.sourceTileByKey_) {
sourceTile = this.sourceTileByKey_[tileUrl];
if (this.sourceTileCache.containsKey(tileUrl)) {
sourceTile = this.sourceTileCache.get(tileUrl);
const state = sourceTile.getState();
if (state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
sourceTiles.push(sourceTile);
@@ -283,7 +287,7 @@ class VectorTile extends UrlTile {
sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
sourceTile.projection = projection;
sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
this.sourceTileByKey_[tileUrl] = sourceTile;
this.sourceTileCache.set(tileUrl, sourceTile);
sourceTile.addEventListener(EventType.CHANGE, this.handleTileChange.bind(this));
sourceTile.load();
}
@@ -294,13 +298,12 @@ class VectorTile extends UrlTile {
}
if (sourceTile.getState() !== TileState.EMPTY && tile.getState() === TileState.IDLE) {
tile.loadingSourceTiles++;
const key = listen(sourceTile, EventType.CHANGE, function() {
sourceTile.addEventListener(EventType.CHANGE, function listenChange() {
const state = sourceTile.getState();
const sourceTileKey = sourceTile.getKey();
if (state === TileState.LOADED || state === TileState.ERROR) {
if (state === TileState.LOADED) {
remove(tile.sourceTileListenerKeys, key);
unlistenByKey(key);
sourceTile.removeEventListener(EventType.CHANGE, listenChange);
tile.loadingSourceTiles--;
delete tile.errorSourceTileKeys[sourceTileKey];
} else if (state === TileState.ERROR) {
@@ -314,7 +317,6 @@ class VectorTile extends UrlTile {
}
}
});
tile.sourceTileListenerKeys.push(key);
}
}.bind(this));
if (!covered) {
@@ -332,43 +334,12 @@ class VectorTile extends UrlTile {
if (tile.getState() < TileState.LOADED) {
tile.setState(TileState.LOADED);
} else if (!previousSourceTiles || !equals(sourceTiles, previousSourceTiles)) {
this.removeSourceTiles(tile);
this.addSourceTiles(tile, sourceTiles);
tile.sourceTiles = sourceTiles;
}
}
return sourceTiles;
}
/**
* @param {VectorRenderTile} tile Tile.
* @param {Array<import("../VectorTile").default>} sourceTiles Source tiles.
*/
addSourceTiles(tile, sourceTiles) {
this.sourceTilesByTileKey_[tile.getKey()] = sourceTiles;
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
sourceTiles[i].consumers++;
}
}
/**
* @param {VectorRenderTile} tile Tile.
*/
removeSourceTiles(tile) {
const tileKey = tile.getKey();
if (tileKey in this.sourceTilesByTileKey_) {
const sourceTiles = this.sourceTilesByTileKey_[tileKey];
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
const sourceTile = sourceTiles[i];
sourceTile.consumers--;
if (sourceTile.consumers === 0) {
sourceTile.dispose();
delete this.sourceTileByKey_[sourceTile.getKey()];
}
}
}
delete this.sourceTilesByTileKey_[tileKey];
}
/**
* @inheritDoc
*/
@@ -410,9 +381,7 @@ class VectorTile extends UrlTile {
tileCoord,
empty ? TileState.EMPTY : TileState.IDLE,
urlTileCoord,
this.tileGrid,
this.getSourceTiles.bind(this, pixelRatio, projection),
this.removeSourceTiles.bind(this));
this.getSourceTiles.bind(this, pixelRatio, projection));
newTile.key = key;
if (tile) {
+1 -1
View File
@@ -15,7 +15,7 @@ import {appendParams} from '../uri.js';
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
+7 -4
View File
@@ -9,7 +9,7 @@ import {createXYZ, extentFromProjection} from '../tilegrid.js';
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value if you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
@@ -19,8 +19,9 @@ import {createXYZ, extentFromProjection} from '../tilegrid.js';
* Higher values can increase reprojection performance, but decrease precision.
* @property {object} [reprojectionContextOptions] Optional properties to set on the canvas context used
* for reprojection. For example specify `{imageSmoothingEnabled: false}` to disable image smoothing.
* @property {number} [maxZoom=18] Optional max zoom level.
* @property {number} [minZoom=0] Optional min zoom level.
* @property {number} [maxZoom=42] Optional max zoom level. Not used if `tileGrid` is provided.
* @property {number} [minZoom=0] Optional min zoom level. Not used if `tileGrid` is provided.
* @property {number} [maxResolution] Optional tile grid resolution at level zero. Not used if `tileGrid` is provided.
* @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
* @property {import("../Tile.js").LoadFunction} [tileLoadFunction] Optional function to load a tile given a URL. The default is
* ```js
@@ -33,9 +34,10 @@ import {createXYZ, extentFromProjection} from '../tilegrid.js';
* by 512px images (for retina/hidpi devices) then `tilePixelRatio`
* should be set to `2`.
* @property {number|import("../size.js").Size} [tileSize=[256, 256]] The tile size used by the tile service.
* Not used if `tileGrid` is provided.
* @property {import("../Tile.js").UrlFunction} [tileUrlFunction] Optional function to get
* tile URL given a tile coordinate and the projection.
* Required if url or urls are not provided.
* Required if `url` or `urls` are not provided.
* @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 used instead of defining each one separately in the `urls` option.
@@ -80,6 +82,7 @@ class XYZ extends TileImage {
const tileGrid = options.tileGrid !== undefined ? options.tileGrid :
createXYZ({
extent: extentFromProjection(projection),
maxResolution: options.maxResolution,
maxZoom: options.maxZoom,
minZoom: options.minZoom,
tileSize: options.tileSize
+16 -23
View File
@@ -8,7 +8,6 @@ import TileState from '../TileState.js';
import {expandUrl, createFromTileUrlFunctions} from '../tileurlfunction.js';
import {assert} from '../asserts.js';
import {createCanvasContext2D} from '../dom.js';
import {getTopLeft} from '../extent.js';
import {toSize} from '../size.js';
import TileImage from './TileImage.js';
import TileGrid from '../tilegrid/TileGrid.js';
@@ -26,8 +25,7 @@ const TierSizeCalculation = {
export class CustomTile extends ImageTile {
/**
* @param {number} tilePixelRatio Tile pixel ratio to display the tile
* @param {import("../tilegrid/TileGrid.js").default} tileGrid TileGrid that the tile belongs to.
* @param {import("../size.js").Size} tileSize Full tile size.
* @param {import("../tilecoord.js").TileCoord} tileCoord Tile coordinate.
* @param {TileState} state State.
* @param {string} src Image source URI.
@@ -35,7 +33,8 @@ export class CustomTile extends ImageTile {
* @param {import("../Tile.js").LoadFunction} tileLoadFunction Tile load function.
* @param {import("../Tile.js").Options=} opt_options Tile options.
*/
constructor(tilePixelRatio, tileGrid, tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options) {
constructor(tileSize, tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options) {
super(tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options);
/**
@@ -45,14 +44,10 @@ export class CustomTile extends ImageTile {
this.zoomifyImage_ = null;
/**
* @private
* @type {import("../size.js").Size}
*/
this.tileSize_ = toSize(tileGrid.getTileSize(tileCoord[0])).map(
function(x) {
return x * tilePixelRatio;
}
);
this.tileSize_ = tileSize;
}
/**
@@ -85,7 +80,7 @@ export class CustomTile extends ImageTile {
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will increase if too small.
* @property {number} [cacheSize] Tile cache size. The default depends on the screen size. Will be ignored if too small.
* @property {null|string} [crossOrigin] The `crossOrigin` attribute for loaded images. Note that
* you must provide a `crossOrigin` value you want to access pixel data with the Canvas renderer.
* See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.
@@ -116,8 +111,8 @@ export class CustomTile extends ImageTile {
* @property {number} [transition] Duration of the opacity transition for rendering.
* To disable the opacity transition, pass `transition: 0`.
* @property {number} [tileSize=256] Tile size. Same tile size is used for all zoom levels.
* @property {number} [zDirection=0] Indicate which resolution should be used
* by a renderer if the view resolution does not match any resolution of the tile source.
* @property {number} [zDirection] Indicate which resolution should be used
* by a renderer if the views resolution does not match any resolution of the tile source.
* If 0, the nearest resolution will be used. If 1, the nearest lower resolution
* will be used. If -1, the nearest higher resolution will be used.
*/
@@ -143,13 +138,12 @@ class Zoomify extends TileImage {
options.tierSizeCalculation :
TierSizeCalculation.DEFAULT;
const tilePixelRatio = options.tilePixelRatio || 1;
const imageWidth = size[0];
const imageHeight = size[1];
const extent = options.extent || [0, -size[1], size[0], 0];
const tierSizeInTiles = [];
const tileSize = options.tileSize || DEFAULT_TILE_SIZE;
const tilePixelRatio = options.tilePixelRatio || 1;
let tileSizeForTierSizeCalculation = tileSize;
let tileSizeForTierSizeCalculation = tileSize * tilePixelRatio;
switch (tierSizeCalculation) {
case TierSizeCalculation.DEFAULT:
@@ -181,10 +175,10 @@ class Zoomify extends TileImage {
tierSizeInTiles.push([1, 1]);
tierSizeInTiles.reverse();
const resolutions = [1];
const resolutions = [tilePixelRatio];
const tileCountUpToTier = [0];
for (let i = 1, ii = tierSizeInTiles.length; i < ii; i++) {
resolutions.push(1 << i);
resolutions.push(tilePixelRatio << i);
tileCountUpToTier.push(
tierSizeInTiles[i - 1][0] * tierSizeInTiles[i - 1][1] +
tileCountUpToTier[i - 1]
@@ -194,8 +188,7 @@ class Zoomify extends TileImage {
const tileGrid = new TileGrid({
tileSize: tileSize,
extent: extent,
origin: getTopLeft(extent),
extent: options.extent || [0, -imageHeight, imageWidth, 0],
resolutions: resolutions
});
@@ -205,6 +198,8 @@ class Zoomify extends TileImage {
}
const urls = expandUrl(url);
const tileWidth = tileSize * tilePixelRatio;
/**
* @param {string} template Template.
* @return {import("../Tile.js").UrlFunction} Tile URL function.
@@ -228,8 +223,6 @@ class Zoomify extends TileImage {
const tileIndex =
tileCoordX +
tileCoordY * tierSizeInTiles[tileCoordZ][0];
const tileSize = tileGrid.getTileSize(tileCoordZ);
const tileWidth = Array.isArray(tileSize) ? tileSize[0] : tileSize;
const tileGroup = ((tileIndex + tileCountUpToTier[tileCoordZ]) / tileWidth) | 0;
const localContext = {
'z': tileCoordZ,
@@ -248,7 +241,7 @@ class Zoomify extends TileImage {
const tileUrlFunction = createFromTileUrlFunctions(urls.map(createFromTemplate));
const ZoomifyTileClass = CustomTile.bind(null, tilePixelRatio, tileGrid);
const ZoomifyTileClass = CustomTile.bind(null, toSize(tileSize * tilePixelRatio));
super({
attributions: options.attributions,
-7
View File
@@ -1,10 +1,3 @@
/**
* @license
* Latitude/longitude spherical geodesy formulae taken from
* http://www.movable-type.co.uk/scripts/latlong.html
* Licensed under CC-BY-3.0.
*/
/**
* @module ol/sphere
*/
+1 -6
View File
@@ -3,8 +3,6 @@
*/
import {assert} from '../asserts.js';
import EventTarget from '../events/Target.js';
import EventType from '../events/EventType.js';
/**
@@ -25,15 +23,13 @@ import EventType from '../events/EventType.js';
* @fires import("../events/Event.js").default
* @template T
*/
class LRUCache extends EventTarget {
class LRUCache {
/**
* @param {number=} opt_highWaterMark High water mark.
*/
constructor(opt_highWaterMark) {
super();
/**
* @type {number}
*/
@@ -82,7 +78,6 @@ class LRUCache extends EventTarget {
this.entries_ = {};
this.oldest_ = null;
this.newest_ = null;
this.dispatchEvent(EventType.CLEAR);
}
+5 -2
View File
@@ -10,6 +10,7 @@ import RegularShape from './RegularShape.js';
* @property {import("./Fill.js").default} [fill] Fill style.
* @property {number} radius Circle radius.
* @property {import("./Stroke.js").default} [stroke] Stroke style.
* @property {Array<number>} [displacement=[0,0]] displacement
*/
@@ -30,7 +31,8 @@ class CircleStyle extends RegularShape {
points: Infinity,
fill: options.fill,
radius: options.radius,
stroke: options.stroke
stroke: options.stroke,
displacement: options.displacement !== undefined ? options.displacement : [0, 0]
});
}
@@ -45,7 +47,8 @@ class CircleStyle extends RegularShape {
const style = new CircleStyle({
fill: this.getFill() ? this.getFill().clone() : undefined,
stroke: this.getStroke() ? this.getStroke().clone() : undefined,
radius: this.getRadius()
radius: this.getRadius(),
displacement: this.getDisplacement().slice()
});
style.setOpacity(this.getOpacity());
style.setScale(this.getScale());
+5 -1
View File
@@ -33,6 +33,7 @@ import ImageStyle from './Image.js';
* to provide the size of the image, with the `imgSize` option.
* @property {Array<number>} [offset=[0, 0]] Offset, which, together with the size and the offset origin, define the
* sub-rectangle to use from the original icon image.
* @property {Array<number>} [displacement=[0,0]] Displacement the icon
* @property {import("./IconOrigin.js").default} [offsetOrigin='top-left'] Origin of the offset: `bottom-left`, `bottom-right`,
* `top-left` or `top-right`.
* @property {number} [opacity=1] Opacity of the icon.
@@ -84,6 +85,7 @@ class Icon extends ImageStyle {
opacity: opacity,
rotation: rotation,
scale: scale,
displacement: options.displacement !== undefined ? options.displacement : [0, 0],
rotateWithView: rotateWithView
});
@@ -177,7 +179,6 @@ class Icon extends ImageStyle {
* @type {Array<number>}
*/
this.offset_ = options.offset !== undefined ? options.offset : [0, 0];
/**
* @private
* @type {import("./IconOrigin.js").default}
@@ -336,6 +337,7 @@ class Icon extends ImageStyle {
return this.origin_;
}
let offset = this.offset_;
const displacement = this.getDisplacement();
if (this.offsetOrigin_ != IconOrigin.TOP_LEFT) {
const size = this.getSize();
@@ -353,6 +355,8 @@ class Icon extends ImageStyle {
offset[1] = iconImageSize[1] - size[1] - offset[1];
}
}
offset[0] += displacement[0];
offset[1] += displacement[1];
this.origin_ = offset;
return this.origin_;
}
+18 -1
View File
@@ -10,6 +10,7 @@ import {abstract} from '../util.js';
* @property {boolean} rotateWithView
* @property {number} rotation
* @property {number} scale
* @property {Array<number>} displacement
*/
@@ -51,6 +52,12 @@ class ImageStyle {
*/
this.scale_ = options.scale;
/**
* @private
* @type {Array<number>}
*/
this.displacement_ = options.displacement;
}
/**
@@ -63,7 +70,8 @@ class ImageStyle {
opacity: this.getOpacity(),
scale: this.getScale(),
rotation: this.getRotation(),
rotateWithView: this.getRotateWithView()
rotateWithView: this.getRotateWithView(),
displacement: this.getDisplacement().slice()
});
}
@@ -103,6 +111,15 @@ class ImageStyle {
return this.scale_;
}
/**
* Get the displacement of the shape
* @return {Array<number>} Shape's center displacement
* @api
*/
getDisplacement() {
return this.displacement_;
}
/**
* Get the anchor point in pixels. The anchor determines the center point for the
* symbolizer.
+1
View File
@@ -35,6 +35,7 @@ export const SymbolType = {
* @property {string} [src] Path to the image to be used for the symbol. Only required with `symbolType: 'image'`.
* @property {import("../color.js").Color|Array<ExpressionValue>|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol).
* @property {ExpressionValue} [opacity=1] Opacity.
* @property {ExpressionValue} [rotation=0] Symbol rotation in radians.
* @property {Array<ExpressionValue, ExpressionValue>} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered.
* @property {Array<ExpressionValue, ExpressionValue, ExpressionValue, ExpressionValue>} [textureCoord] Texture coordinates. If not specified, the whole texture will be used (range for 0 to 1 on both axes).
* @property {boolean} [rotateWithView=false] Specify whether the symbol must rotate with the view or stay upwards.
+7 -3
View File
@@ -20,6 +20,7 @@ import ImageStyle from './Image.js';
* @property {number} [radius1] Outer radius of a star.
* @property {number} [radius2] Inner radius of a star.
* @property {number} [angle=0] Shape's angle in radians. A value of 0 will have one of the shape's point facing up.
* @property {Array<number>} [displacement=[0,0]] Displacement of the shape
* @property {import("./Stroke.js").default} [stroke] Stroke style.
* @property {number} [rotation=0] Rotation in radians (positive rotation clockwise).
* @property {boolean} [rotateWithView=false] Whether to rotate the shape with the view.
@@ -61,7 +62,8 @@ class RegularShape extends ImageStyle {
opacity: 1,
rotateWithView: rotateWithView,
rotation: options.rotation !== undefined ? options.rotation : 0,
scale: 1
scale: 1,
displacement: options.displacement !== undefined ? options.displacement : [0, 0]
});
/**
@@ -160,7 +162,8 @@ class RegularShape extends ImageStyle {
angle: this.getAngle(),
stroke: this.getStroke() ? this.getStroke().clone() : undefined,
rotation: this.getRotation(),
rotateWithView: this.getRotateWithView()
rotateWithView: this.getRotateWithView(),
displacement: this.getDisplacement().slice()
});
style.setOpacity(this.getOpacity());
style.setScale(this.getScale());
@@ -353,12 +356,13 @@ class RegularShape extends ImageStyle {
// canvas.width and height are rounded to the closest integer
size = this.canvas_.width;
const imageSize = size;
const displacement = this.getDisplacement();
this.draw_(renderOptions, context, 0, 0);
this.createHitDetectionCanvas_(renderOptions);
this.anchor_ = [size / 2, size / 2];
this.anchor_ = [size / 2 - displacement[0], size / 2 + displacement[1]];
this.size_ = [size, size];
this.imageSize_ = [imageSize, imageSize];
}
+3 -2
View File
@@ -13,9 +13,10 @@ import Stroke from './Stroke.js';
* A function that takes an {@link module:ol/Feature} and a `{number}`
* representing the view's resolution. The function should return a
* {@link module:ol/style/Style} or an array of them. This way e.g. a
* vector layer can be styled.
* vector layer can be styled. If the function returns `undefined`, the
* feature will not be rendered.
*
* @typedef {function(import("../Feature.js").FeatureLike, number):(Style|Array<Style>)} StyleFunction
* @typedef {function(import("../Feature.js").FeatureLike, number):(Style|Array<Style>|void)} StyleFunction
*/
/**
+2
View File
@@ -16,6 +16,8 @@ import {asArray, isStringColor} from '../color.js';
* Note: those will be taken from the attributes provided to the renderer
* * `['var', 'varName']` fetches a value from the style variables, or 0 if undefined
* * `['time']` returns the time in seconds since the creation of the layer
* * `['zoom']` returns the current zoom level
* * `['resolution']` returns the current resolution
*
* * Math operators:
* * `['*', value1, value2]` multiplies `value1` by `value2`
+9 -6
View File
@@ -72,8 +72,9 @@ export function createForExtent(extent, opt_maxZoom, opt_tileSize, opt_corner) {
/**
* @typedef {Object} XYZOptions
* @property {import("./extent.js").Extent} [extent] Extent for the tile grid. The origin for an XYZ tile grid is the
* top-left corner of the extent. The zero level of the grid is defined by the resolution at which one tile fits in the
* provided extent. If not provided, the extent of the EPSG:3857 projection is used.
* top-left corner of the extent. If `maxResolution` is not provided the zero level of the grid is defined by the resolution
* at which one tile fits in the provided extent. If not provided, the extent of the EPSG:3857 projection is used.
* @property {number} [maxResolution] Resolution at level zero.
* @property {number} [maxZoom] Maximum zoom. The default is `42`. This determines the number of levels
* in the grid set. For example, a `maxZoom` of 21 means there are 22 levels in the grid set.
* @property {number} [minZoom=0] Minimum zoom.
@@ -99,7 +100,8 @@ export function createXYZ(opt_options) {
resolutions: resolutionsFromExtent(
extent,
xyzOptions.maxZoom,
xyzOptions.tileSize
xyzOptions.tileSize,
xyzOptions.maxResolution
)
};
return new TileGrid(gridOptions);
@@ -113,9 +115,10 @@ export function createXYZ(opt_options) {
* DEFAULT_MAX_ZOOM).
* @param {number|import("./size.js").Size=} opt_tileSize Tile size (default uses
* DEFAULT_TILE_SIZE).
* @param {number=} opt_maxResolution Resolution at level zero.
* @return {!Array<number>} Resolutions array.
*/
function resolutionsFromExtent(extent, opt_maxZoom, opt_tileSize) {
function resolutionsFromExtent(extent, opt_maxZoom, opt_tileSize, opt_maxResolution) {
const maxZoom = opt_maxZoom !== undefined ?
opt_maxZoom : DEFAULT_MAX_ZOOM;
@@ -124,8 +127,8 @@ function resolutionsFromExtent(extent, opt_maxZoom, opt_tileSize) {
const tileSize = toSize(opt_tileSize !== undefined ?
opt_tileSize : DEFAULT_TILE_SIZE);
const maxResolution = Math.max(
width / tileSize[0], height / tileSize[1]);
const maxResolution = opt_maxResolution > 0 ? opt_maxResolution :
Math.max(width / tileSize[0], height / tileSize[1]);
const length = maxZoom + 1;
const resolutions = new Array(length);
+3 -1
View File
@@ -558,6 +558,7 @@ class WebGLHelper extends Disposable {
if (value instanceof HTMLCanvasElement || value instanceof HTMLImageElement || value instanceof ImageData) {
// create a texture & put data
if (!uniform.texture) {
uniform.prevValue = undefined;
uniform.texture = gl.createTexture();
}
gl.activeTexture(gl[`TEXTURE${textureSlot}`]);
@@ -567,7 +568,8 @@ class WebGLHelper extends Disposable {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const imageReady = !(value instanceof HTMLImageElement) || /** @type {HTMLImageElement} */(value).complete;
if (imageReady) {
if (imageReady && uniform.prevValue !== value) {
uniform.prevValue = value;
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value);
}
+38 -3
View File
@@ -56,6 +56,12 @@ export class ShaderBuilder {
*/
this.sizeExpression = 'vec2(1.0)';
/**
* @type {string}
* @private
*/
this.rotationExpression = '0.0';
/**
* @type {string}
* @private
@@ -138,6 +144,18 @@ export class ShaderBuilder {
return this;
}
/**
* Sets an expression to compute the rotation of the shape.
* This expression can use all the uniforms and attributes available
* in the vertex shader, and should evaluate to a `float` value in radians.
* @param {string} expression Size expression
* @return {ShaderBuilder} the builder object
*/
setRotationExpression(expression) {
this.rotationExpression = expression;
return this;
}
/**
* Sets an expression to compute the offset of the symbol from the point center.
* This expression can use all the uniforms and attributes available
@@ -291,10 +309,24 @@ ${varyings.map(function(varying) {
}).join('\n')}
void main(void) {
mat4 offsetMatrix = ${offsetMatrix};
vec2 size = ${this.sizeExpression};
vec2 halfSize = ${this.sizeExpression} * 0.5;
vec2 offset = ${this.offsetExpression};
float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0;
float angle = ${this.rotationExpression};
float offsetX;
float offsetY;
if (a_index == 0.0) {
offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle);
offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle);
} else if (a_index == 1.0) {
offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle);
offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle);
} else if (a_index == 2.0) {
offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle);
offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle);
} else {
offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle);
offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle);
}
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
vec4 texCoord = ${this.texCoordExpression};
@@ -381,6 +413,7 @@ export function parseLiteralStyle(style) {
const texCoord = symbStyle.textureCoord || [0, 0, 1, 1];
const offset = symbStyle.offset || [0, 0];
const opacity = symbStyle.opacity !== undefined ? symbStyle.opacity : 1;
const rotation = symbStyle.rotation !== undefined ? symbStyle.rotation : 0;
/**
* @type {import("../style/expressions.js").ParsingContext}
@@ -406,6 +439,7 @@ export function parseLiteralStyle(style) {
};
const parsedColor = expressionToGlsl(fragContext, color, ValueTypes.COLOR);
const parsedOpacity = expressionToGlsl(fragContext, opacity, ValueTypes.NUMBER);
const parsedRotation = expressionToGlsl(fragContext, rotation, ValueTypes.NUMBER);
let opacityFilter = '1.0';
const visibleSize = `vec2(${expressionToGlsl(fragContext, size, ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER)}).x`;
@@ -427,6 +461,7 @@ export function parseLiteralStyle(style) {
const builder = new ShaderBuilder()
.setSizeExpression(`vec2(${parsedSize})`)
.setRotationExpression(parsedRotation)
.setSymbolOffsetExpression(parsedOffset)
.setTextureCoordinateExpression(parsedTexCoord)
.setSymbolRotateWithView(!!symbStyle.rotateWithView)
+49 -10
View File
@@ -23,15 +23,6 @@ import {extend} from './array.js';
*/
/**
* This document should be used when creating nodes for XML serializations. This
* document is also used by {@link module:ol/xml~createElementNS}
* @const
* @type {Document}
*/
export const DOCUMENT = document.implementation.createDocument('', '', null);
/**
* @type {string}
*/
@@ -44,7 +35,7 @@ export const XML_SCHEMA_INSTANCE_URI = 'http://www.w3.org/2001/XMLSchema-instanc
* @return {Element} Node.
*/
export function createElementNS(namespaceURI, qualifiedName) {
return DOCUMENT.createElementNS(namespaceURI, qualifiedName);
return getDocument().createElementNS(namespaceURI, qualifiedName);
}
@@ -494,3 +485,51 @@ export function pushSerializeAndPop(object, serializersNS, nodeFactory, values,
serialize(serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this);
return /** @type {O|undefined} */ (objectStack.pop());
}
let xmlSerializer_ = undefined;
/**
* Register a XMLSerializer. Can be used to inject a XMLSerializer
* where there is no globally available implementation.
*
* @param {XMLSerializer} xmlSerializer A XMLSerializer.
* @api
*/
export function registerXMLSerializer(xmlSerializer) {
xmlSerializer_ = xmlSerializer;
}
/**
* @return {XMLSerializer} The XMLSerializer.
*/
export function getXMLSerializer() {
if (xmlSerializer_ === undefined && typeof XMLSerializer !== 'undefined') {
xmlSerializer_ = new XMLSerializer();
}
return xmlSerializer_;
}
let document_ = undefined;
/**
* Register a Document to use when creating nodes for XML serializations. Can be used
* to inject a Document where there is no globally available implementation.
*
* @param {Document} document A Document.
* @api
*/
export function registerDocument(document) {
document_ = document;
}
/**
* Get a document that should be used when creating nodes for XML serializations.
* @return {Document} The document.
*/
export function getDocument() {
if (document_ === undefined && typeof document !== 'undefined') {
document_ = document.implementation.createDocument('', '', null);
}
return document_;
}