Merge remote-tracking branch 'origin/master' into fix_triangulation

Conflicts:
	src/ol/TileCache.js
This commit is contained in:
philip
2020-01-11 17:36:40 +00:00
139 changed files with 10057 additions and 2161 deletions
-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.
+33 -2
View File
@@ -2,13 +2,14 @@
* @module ol/MapBrowserEventHandler
*/
import '@openlayers/pepjs';
import {DEVICE_PIXEL_RATIO} from './has.js';
import 'elm-pep';
import {DEVICE_PIXEL_RATIO, PASSIVE_EVENT_LISTENERS} from './has.js';
import MapBrowserEventType from './MapBrowserEventType.js';
import MapBrowserPointerEvent from './MapBrowserPointerEvent.js';
import {listen, unlistenByKey} from './events.js';
import EventTarget from './events/Target.js';
import PointerEventType from './pointer/EventType.js';
import EventType from './events/EventType.js';
class MapBrowserEventHandler extends EventTarget {
@@ -84,6 +85,12 @@ class MapBrowserEventHandler extends EventTarget {
PointerEventType.POINTERDOWN,
this.handlePointerDown_, this);
/**
* @type {PointerEvent}
* @private
*/
this.originalPointerMoveEvent_;
/**
* @type {?import("./events.js").EventsKey}
* @private
@@ -92,6 +99,13 @@ class MapBrowserEventHandler extends EventTarget {
PointerEventType.POINTERMOVE,
this.relayEvent_, this);
/**
* @private
*/
this.boundHandleTouchMove_ = this.handleTouchMove_.bind(this);
this.element_.addEventListener(EventType.TOUCHMOVE, this.boundHandleTouchMove_,
PASSIVE_EVENT_LISTENERS ? {passive: false} : false);
}
/**
@@ -246,11 +260,26 @@ class MapBrowserEventHandler extends EventTarget {
* @private
*/
relayEvent_(pointerEvent) {
this.originalPointerMoveEvent_ = pointerEvent;
const dragging = !!(this.down_ && this.isMoving_(pointerEvent));
this.dispatchEvent(new MapBrowserPointerEvent(
pointerEvent.type, this.map_, pointerEvent, dragging));
}
/**
* Flexible handling of a `touch-action: none` css equivalent: because calling
* `preventDefault()` on a `pointermove` event does not stop native page scrolling
* and zooming, we also listen for `touchmove` and call `preventDefault()` on it
* when an interaction (currently `DragPan` handles the event.
* @param {TouchEvent} event Event.
* @private
*/
handleTouchMove_(event) {
if (this.originalPointerMoveEvent_.defaultPrevented) {
event.preventDefault();
}
}
/**
* @param {PointerEvent} pointerEvent Pointer
* event.
@@ -271,6 +300,8 @@ class MapBrowserEventHandler extends EventTarget {
unlistenByKey(this.relayedListenerKey_);
this.relayedListenerKey_ = null;
}
this.element_.removeEventListener(EventType.TOUCHMOVE, this.boundHandleTouchMove_);
if (this.pointerdownListenerKey_) {
unlistenByKey(this.pointerdownListenerKey_);
this.pointerdownListenerKey_ = null;
+33 -41
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';
@@ -22,7 +21,7 @@ import {listen, unlistenByKey} from './events.js';
import EventType from './events/EventType.js';
import {clone, createOrUpdateEmpty, equals, getForViewAndSize, isEmpty} from './extent.js';
import {TRUE} from './functions.js';
import {DEVICE_PIXEL_RATIO, IMAGE_DECODE} from './has.js';
import {DEVICE_PIXEL_RATIO, IMAGE_DECODE, PASSIVE_EVENT_LISTENERS} from './has.js';
import LayerGroup from './layer/Group.js';
import {hasArea} from './size.js';
import {DROP} from './structs/PriorityQueue.js';
@@ -131,17 +130,6 @@ import {toUserCoordinate, fromUserCoordinate} from './proj.js';
*/
/**
* @param {HTMLElement} element Element.
* @param {string} touchAction Value for `touch-action'.
*/
function setTouchAction(element, touchAction) {
element.style.msTouchAction = touchAction;
element.style.touchAction = touchAction;
element.setAttribute('touch-action', touchAction);
}
/**
* @fires import("./MapBrowserEvent.js").MapBrowserEvent
* @fires import("./MapEvent.js").MapEvent
@@ -305,15 +293,10 @@ class PluggableMap extends BaseObject {
*/
this.keyHandlerKeys_ = null;
/**
* @private
* @type {?Array<import("./events.js").EventsKey>}
*/
this.focusHandlerKeys_ = null;
const handleBrowserEvent = this.handleBrowserEvent.bind(this);
this.viewport_.addEventListener(EventType.CONTEXTMENU, handleBrowserEvent, false);
this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent, false);
this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent,
PASSIVE_EVENT_LISTENERS ? {passive: false} : false);
/**
* @type {Collection<import("./control/Control.js").default>}
@@ -949,12 +932,14 @@ class PluggableMap extends BaseObject {
// coordinates so interactions cannot be used.
return;
}
let target = mapBrowserEvent.originalEvent.target;
while (target instanceof HTMLElement) {
if (target.parentElement === this.overlayContainerStopEvent_) {
return;
let target = /** @type {Node} */ (mapBrowserEvent.originalEvent.target);
if (!mapBrowserEvent.dragging) {
while (target && target !== this.viewport_) {
if (target.parentElement === this.overlayContainerStopEvent_) {
return;
}
target = target.parentElement;
}
target = target.parentElement;
}
mapBrowserEvent.frameState = this.frameState_;
const interactionsArray = this.getInteractions().getArray();
@@ -1043,12 +1028,6 @@ class PluggableMap extends BaseObject {
targetElement = this.getTargetElement();
}
if (this.focusHandlerKeys_) {
for (let i = 0, ii = this.focusHandlerKeys_.length; i < ii; ++i) {
unlistenByKey(this.focusHandlerKeys_[i]);
}
this.focusHandlerKeys_ = null;
}
if (this.keyHandlerKeys_) {
for (let i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
unlistenByKey(this.keyHandlerKeys_[i]);
@@ -1077,15 +1056,6 @@ class PluggableMap extends BaseObject {
if (!this.renderer_) {
this.renderer_ = this.createRenderer();
}
let hasFocus = true;
if (targetElement.hasAttribute('tabindex')) {
hasFocus = document.activeElement === targetElement;
this.focusHandlerKeys_ = [
listen(targetElement, EventType.FOCUS, setTouchAction.bind(this, this.viewport_, 'none')),
listen(targetElement, EventType.BLUR, setTouchAction.bind(this, this.viewport_, 'auto'))
];
}
setTouchAction(this.viewport_, hasFocus ? 'none' : 'auto');
const keyboardEventTarget = !this.keyboardEventTarget_ ?
targetElement : this.keyboardEventTarget_;
@@ -1133,7 +1103,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);
@@ -1391,6 +1362,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() {
}
/**
+2 -12
View File
@@ -6,15 +6,6 @@ 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.
*/
@@ -24,8 +15,7 @@ class TileCache extends LRUCache {
if (tile.getKey() in usedTiles) {
break;
} else {
// This lets the GC clean up the object
this.pop();
this.pop().release();
}
}
}
@@ -43,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
};
+25 -47
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});
@@ -61,9 +61,9 @@ class VectorRenderTile extends Tile {
this.errorSourceTileKeys = {};
/**
* @type {ImageData}
* @type {Object<number, ImageData>}
*/
this.hitDetectionImageData = null;
this.hitDetectionImageData = {};
/**
* @private
@@ -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();
}
}
+3 -6
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}
@@ -105,7 +100,9 @@ class VectorTile extends Tile {
if (this.state == TileState.IDLE) {
this.setState(TileState.LOADING);
this.tileLoadFunction_(this, this.url_);
this.loader_(this.extent, this.resolution, this.projection);
if (this.loader_) {
this.loader_(this.extent, this.resolution, this.projection);
}
}
}
+63 -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';
@@ -294,6 +293,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}
@@ -312,6 +317,12 @@ class View extends BaseObject {
*/
this.targetRotation_;
/**
* @private
* @type {import("./coordinate.js").Coordinate|undefined}
*/
this.cancelAnchor_ = undefined;
if (options.center) {
options.center = fromUserCoordinate(options.center, this.projection_);
}
@@ -584,13 +595,19 @@ class View extends BaseObject {
*/
cancelAnimations() {
this.setHint(ViewHint.ANIMATING, -this.hints_[ViewHint.ANIMATING]);
let anchor;
for (let i = 0, ii = this.animations_.length; i < ii; ++i) {
const series = this.animations_[i];
if (series[0].callback) {
animationCallback(series[0].callback, false);
}
anchor = anchor ||
series.filter(function(animation) {
return !animation.complete;
})[0].anchor;
}
this.animations_.length = 0;
this.cancelAnchor_ = anchor;
}
/**
@@ -637,7 +654,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);
}
@@ -710,26 +727,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];
}
/**
@@ -780,8 +804,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
*/
@@ -796,7 +820,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());
@@ -919,7 +943,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);
@@ -933,7 +957,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 (
@@ -964,17 +988,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;
});
}
@@ -1067,7 +1091,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;
@@ -1085,7 +1109,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;
}
@@ -1102,7 +1126,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;
@@ -1249,10 +1273,10 @@ 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 !== undefined) {
if (opt_anchor) {
this.targetCenter_ = this.calculateCenterZoom(newResolution, opt_anchor);
}
@@ -1292,7 +1316,7 @@ class View extends BaseObject {
adjustRotationInternal(delta, opt_anchor) {
const isMoving = this.getAnimating() || this.getInteracting();
const newRotation = this.constraints_.rotation(this.targetRotation_ + delta, isMoving);
if (opt_anchor !== undefined) {
if (opt_anchor) {
this.targetCenter_ = this.calculateCenterRotate(newRotation, opt_anchor);
}
this.targetRotation_ += delta;
@@ -1373,7 +1397,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);
@@ -1390,6 +1414,7 @@ class View extends BaseObject {
if (this.getAnimating() && !opt_doNotCancelAnims) {
this.cancelAnimations();
}
this.cancelAnchor_ = undefined;
}
/**
@@ -1406,11 +1431,11 @@ 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);
if (duration === 0) {
if (duration === 0 && !this.cancelAnchor_) {
this.targetResolution_ = newResolution;
this.targetRotation_ = newRotation;
this.targetCenter_ = newCenter;
@@ -1418,6 +1443,9 @@ class View extends BaseObject {
return;
}
const anchor = opt_anchor || (duration === 0 ? this.cancelAnchor_ : undefined);
this.cancelAnchor_ = undefined;
if (this.getResolution() !== newResolution ||
this.getRotation() !== newRotation ||
!this.getCenterInternal() ||
@@ -1433,7 +1461,7 @@ class View extends BaseObject {
resolution: newResolution,
duration: duration,
easing: easeOut,
anchor: opt_anchor
anchor: anchor
});
}
}
@@ -1484,7 +1512,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);
}
@@ -1513,7 +1541,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);
}
+2 -1
View File
@@ -1,12 +1,13 @@
/**
* @module ol/control/MousePosition
*/
import 'elm-pep';
import {listen} from '../events.js';
import EventType from '../pointer/EventType.js';
import {getChangeEventType} from '../Object.js';
import Control from './Control.js';
import {getTransformFromProjections, identityTransform, get as getProjection, getUserProjection} from '../proj.js';
import '@openlayers/pepjs';
/**
+2
View File
@@ -304,6 +304,8 @@ class OverviewMap extends Control {
*/
bindView_(view) {
view.addEventListener(getChangeEventType(ViewProperty.ROTATION), this.boundHandleRotationChanged_);
// Sync once with the new view
this.handleRotationChanged_();
}
/**
+2 -2
View File
@@ -1,6 +1,8 @@
/**
* @module ol/control/ZoomSlider
*/
import 'elm-pep';
import Control from './Control.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE} from '../css.js';
import {easeOut} from '../easing.js';
@@ -9,7 +11,6 @@ import {stopPropagation} from '../events/Event.js';
import EventType from '../events/EventType.js';
import {clamp} from '../math.js';
import PointerEventType from '../pointer/EventType.js';
import '@openlayers/pepjs';
/**
@@ -135,7 +136,6 @@ class ZoomSlider extends Control {
thumbElement.setAttribute('type', 'button');
thumbElement.className = className + '-thumb ' + CLASS_UNSELECTABLE;
const containerElement = this.element;
containerElement.setAttribute('touch-action', 'none');
containerElement.className = className + ' ' + CLASS_UNSELECTABLE + ' ' + CLASS_CONTROL;
containerElement.appendChild(thumbElement);
+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;
}
+1
View File
@@ -34,5 +34,6 @@ export default {
KEYPRESS: 'keypress',
LOAD: 'load',
RESIZE: 'resize',
TOUCHMOVE: 'touchmove',
WHEEL: 'wheel'
};
+3 -6
View File
@@ -207,12 +207,9 @@ export const shiftKeyOnly = function(mapBrowserEvent) {
* @api
*/
export const targetNotEditable = function(mapBrowserEvent) {
const target = mapBrowserEvent.target;
const tagName = /** @type {Element} */ (target).tagName;
return (
tagName !== 'INPUT' &&
tagName !== 'SELECT' &&
tagName !== 'TEXTAREA');
const originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (mapBrowserEvent.originalEvent);
const tagName = /** @type {Element} */ (originalEvent.target).tagName;
return tagName !== 'INPUT' && tagName !== 'SELECT' && tagName !== 'TEXTAREA';
};
+21 -8
View File
@@ -386,6 +386,8 @@ function createStyleDefaults() {
* @property {Array<Style>} [defaultStyle] Default style. The
* default default style is the same as Google Earth.
* @property {boolean} [writeStyles=true] Write styles into KML.
* @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.
*/
@@ -458,6 +460,13 @@ class KML extends XMLFeature {
this.showPointNames_ = options.showPointNames !== undefined ?
options.showPointNames : true;
/**
* @private
* @type {null|string}
*/
this.crossOrigin_ = options.crossOrigin !== undefined ?
options.crossOrigin : 'anonymous';
}
/**
@@ -494,7 +503,7 @@ class KML extends XMLFeature {
*/
readPlacemark_(node, objectStack) {
const object = pushParseAndPop({'geometry': null},
PLACEMARK_PARSERS, node, objectStack);
PLACEMARK_PARSERS, node, objectStack, this);
if (!object) {
return undefined;
}
@@ -537,7 +546,7 @@ class KML extends XMLFeature {
readSharedStyle_(node, objectStack) {
const id = node.getAttribute('id');
if (id !== null) {
const style = readStyle(node, objectStack);
const style = readStyle.call(this, node, objectStack);
if (style) {
let styleUri;
let baseURI = node.baseURI;
@@ -565,7 +574,7 @@ class KML extends XMLFeature {
if (id === null) {
return;
}
const styleMapValue = readStyleMapValue(node, objectStack);
const styleMapValue = readStyleMapValue.call(this, node, objectStack);
if (!styleMapValue) {
return;
}
@@ -1112,13 +1121,14 @@ const STYLE_MAP_PARSERS = makeStructureNS(
/**
* @this {KML}
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Array<Style>|string|undefined} StyleMap.
*/
function readStyleMapValue(node, objectStack) {
return pushParseAndPop(undefined,
STYLE_MAP_PARSERS, node, objectStack);
STYLE_MAP_PARSERS, node, objectStack, this);
}
@@ -1137,6 +1147,7 @@ const ICON_STYLE_PARSERS = makeStructureNS(
/**
* @this {KML}
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
@@ -1223,7 +1234,7 @@ function iconStyleParser(node, objectStack) {
anchorOrigin: anchorOrigin,
anchorXUnits: anchorXUnits,
anchorYUnits: anchorYUnits,
crossOrigin: 'anonymous', // FIXME should this be configurable?
crossOrigin: this.crossOrigin_,
offset: offset,
offsetOrigin: IconOrigin.BOTTOM_LEFT,
rotation: rotation,
@@ -1719,13 +1730,14 @@ const STYLE_PARSERS = makeStructureNS(
/**
* @this {KML}
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
* @return {Array<Style>} Style.
*/
function readStyle(node, objectStack) {
const styleObject = pushParseAndPop(
{}, STYLE_PARSERS, node, objectStack);
{}, STYLE_PARSERS, node, objectStack, this);
if (!styleObject) {
return null;
}
@@ -1885,7 +1897,7 @@ const PAIR_PARSERS = makeStructureNS(
*/
function pairDataParser(node, objectStack) {
const pairObject = pushParseAndPop(
{}, PAIR_PARSERS, node, objectStack);
{}, PAIR_PARSERS, node, objectStack, this);
if (!pairObject) {
return;
}
@@ -1907,11 +1919,12 @@ function pairDataParser(node, objectStack) {
/**
* @this {KML}
* @param {Element} node Node.
* @param {Array<*>} objectStack Object stack.
*/
function placemarkStyleMapParser(node, objectStack) {
const styleMapValue = readStyleMapValue(node, objectStack);
const styleMapValue = readStyleMapValue.call(this, node, objectStack);
if (!styleMapValue) {
return;
}
+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));
}
+20
View File
@@ -44,3 +44,23 @@ export const DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
* @type {boolean}
*/
export const IMAGE_DECODE = typeof Image !== 'undefined' && Image.prototype.decode;
/**
* @type {boolean}
*/
export const PASSIVE_EVENT_LISTENERS = (function() {
let passive = false;
try {
const options = Object.defineProperty({}, 'passive', {
get: function() {
passive = true;
}
});
window.addEventListener('_', null, options);
window.removeEventListener('_', null, options);
} catch (error) {
// passive not supported
}
return passive;
})();
+4 -19
View File
@@ -3,7 +3,7 @@
*/
// FIXME draw drag box
import Event from '../events/Event.js';
import {always, mouseOnly, mouseActionButton} from '../events/condition.js';
import {mouseActionButton} from '../events/condition.js';
import {VOID} from '../functions.js';
import PointerInteraction from './Pointer.js';
import RenderBox from '../render/Box.js';
@@ -22,7 +22,7 @@ import RenderBox from '../render/Box.js';
* @property {string} [className='ol-dragbox'] CSS class name for styling the box.
* @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 ol/events/condition~always}.
* Default is {@link ol/events/condition~mouseActionButton}.
* @property {number} [minArea=64] The minimum area of the box in pixel, this value is used by the default
* `boxEndCondition` function.
* @property {EndCondition} [boxEndCondition] A function that takes a {@link module:ol/MapBrowserEvent~MapBrowserEvent} and two
@@ -104,8 +104,6 @@ class DragBoxEvent extends Event {
* (see {@link module:ol/interaction/DragZoom~DragZoom} and
* {@link module:ol/interaction/DragRotateAndZoom}).
*
* This interaction is only supported for mouse devices.
*
* @fires DragBoxEvent
* @api
*/
@@ -148,7 +146,7 @@ class DragBox extends PointerInteraction {
* @private
* @type {import("../events/condition.js").Condition}
*/
this.condition_ = options.condition ? options.condition : always;
this.condition_ = options.condition ? options.condition : mouseActionButton;
/**
* @private
@@ -186,10 +184,6 @@ class DragBox extends PointerInteraction {
* @inheritDoc
*/
handleDragEvent(mapBrowserEvent) {
if (!mouseOnly(mapBrowserEvent)) {
return;
}
this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);
this.dispatchEvent(new DragBoxEvent(DragBoxEventType.BOXDRAG,
@@ -200,10 +194,6 @@ class DragBox extends PointerInteraction {
* @inheritDoc
*/
handleUpEvent(mapBrowserEvent) {
if (!mouseOnly(mapBrowserEvent)) {
return true;
}
this.box_.setMap(null);
if (this.boxEndCondition_(mapBrowserEvent, this.startPixel_, mapBrowserEvent.pixel)) {
@@ -218,12 +208,7 @@ class DragBox extends PointerInteraction {
* @inheritDoc
*/
handleDownEvent(mapBrowserEvent) {
if (!mouseOnly(mapBrowserEvent)) {
return false;
}
if (mouseActionButton(mapBrowserEvent) &&
this.condition_(mapBrowserEvent)) {
if (this.condition_(mapBrowserEvent)) {
this.startPixel_ = mapBrowserEvent.pixel;
this.box_.setMap(mapBrowserEvent.map);
this.box_.setPixels(this.startPixel_, this.startPixel_);
+17 -2
View File
@@ -3,7 +3,7 @@
*/
import {scale as scaleCoordinate, rotate as rotateCoordinate} from '../coordinate.js';
import {easeOut} from '../easing.js';
import {noModifierKeys, primaryAction} from '../events/condition.js';
import {noModifierKeys, primaryAction, focus} from '../events/condition.js';
import {FALSE} from '../functions.js';
import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js';
@@ -69,6 +69,20 @@ class DragPan extends PointerInteraction {
}
/**
* @private
* @param {import("../MapBrowserEvent").default} mapBrowserEvent Event.
* @return {boolean} Condition passes.
*/
conditionInternal_(mapBrowserEvent) {
let pass = true;
if (mapBrowserEvent.map.getTargetElement().hasAttribute('tabindex')) {
pass = focus(mapBrowserEvent);
}
return pass && this.condition_(mapBrowserEvent);
}
/**
* @inheritDoc
*/
@@ -101,6 +115,7 @@ class DragPan extends PointerInteraction {
}
this.lastCentroid = centroid;
this.lastPointersCount_ = targetPointers.length;
mapBrowserEvent.originalEvent.preventDefault();
}
/**
@@ -145,7 +160,7 @@ class DragPan extends PointerInteraction {
* @inheritDoc
*/
handleDownEvent(mapBrowserEvent) {
if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) {
if (this.targetPointers.length > 0 && this.conditionInternal_(mapBrowserEvent)) {
const map = mapBrowserEvent.map;
const view = map.getView();
this.lastCentroid = null;
+50 -28
View File
@@ -24,6 +24,7 @@ import InteractionProperty from './Property.js';
import VectorLayer from '../layer/Vector.js';
import VectorSource from '../source/Vector.js';
import {createEditingStyle} from '../style/Style.js';
import {fromUserCoordinate, getUserProjection} from '../proj.js';
/**
@@ -104,11 +105,12 @@ import {createEditingStyle} from '../style/Style.js';
/**
* Function that takes an array of coordinates and an optional existing geometry as
* arguments, and returns a geometry. The optional existing geometry is the
* geometry that is returned when the function is called without a second
* argument.
* @typedef {function(!SketchCoordType, import("../geom/SimpleGeometry.js").default=):
* Function that takes an array of coordinates and an optional existing geometry
* and a projection as arguments, and returns a geometry. The optional existing
* geometry is the geometry that is returned when the function is called without
* a second argument.
* @typedef {function(!SketchCoordType, import("../geom/SimpleGeometry.js").default=,
* import("../proj/Projection.js").default):
* import("../geom/SimpleGeometry.js").default} GeometryFunction
*/
@@ -296,14 +298,20 @@ class Draw extends PointerInteraction {
/**
* @param {!LineCoordType} coordinates The coordinates.
* @param {import("../geom/SimpleGeometry.js").default=} opt_geometry Optional geometry.
* @param {import("../proj/Projection.js").default} projection The view projection.
* @return {import("../geom/SimpleGeometry.js").default} A geometry.
*/
geometryFunction = function(coordinates, opt_geometry) {
geometryFunction = function(coordinates, opt_geometry, projection) {
const circle = opt_geometry ? /** @type {Circle} */ (opt_geometry) :
new Circle([NaN, NaN]);
const center = fromUserCoordinate(coordinates[0], projection);
const squaredLength = squaredCoordinateDistance(
coordinates[0], coordinates[1]);
circle.setCenterAndRadius(coordinates[0], Math.sqrt(squaredLength));
center, fromUserCoordinate(coordinates[1], projection));
circle.setCenterAndRadius(center, Math.sqrt(squaredLength));
const userProjection = getUserProjection();
if (userProjection) {
circle.transform(projection, userProjection);
}
return circle;
};
} else {
@@ -319,9 +327,10 @@ class Draw extends PointerInteraction {
/**
* @param {!LineCoordType} coordinates The coordinates.
* @param {import("../geom/SimpleGeometry.js").default=} opt_geometry Optional geometry.
* @param {import("../proj/Projection.js").default} projection The view projection.
* @return {import("../geom/SimpleGeometry.js").default} A geometry.
*/
geometryFunction = function(coordinates, opt_geometry) {
geometryFunction = function(coordinates, opt_geometry, projection) {
let geometry = opt_geometry;
if (geometry) {
if (mode === Mode.POLYGON) {
@@ -675,6 +684,7 @@ class Draw extends PointerInteraction {
*/
startDrawing_(event) {
const start = event.coordinate;
const projection = event.map.getView().getProjection();
this.finishCoordinate_ = start;
if (this.mode_ === Mode.POINT) {
this.sketchCoords_ = start.slice();
@@ -688,7 +698,7 @@ class Draw extends PointerInteraction {
this.sketchLine_ = new Feature(
new LineString(this.sketchLineCoords_));
}
const geometry = this.geometryFunction_(this.sketchCoords_);
const geometry = this.geometryFunction_(this.sketchCoords_, undefined, projection);
this.sketchFeature_ = new Feature();
if (this.geometryName_) {
this.sketchFeature_.setGeometryName(this.geometryName_);
@@ -706,6 +716,7 @@ class Draw extends PointerInteraction {
modifyDrawing_(event) {
let coordinate = event.coordinate;
const geometry = this.sketchFeature_.getGeometry();
const projection = event.map.getView().getProjection();
let coordinates, last;
if (this.mode_ === Mode.POINT) {
last = this.sketchCoords_;
@@ -722,7 +733,7 @@ class Draw extends PointerInteraction {
}
last[0] = coordinate[0];
last[1] = coordinate[1];
this.geometryFunction_(/** @type {!LineCoordType} */ (this.sketchCoords_), geometry);
this.geometryFunction_(/** @type {!LineCoordType} */ (this.sketchCoords_), geometry, projection);
if (this.sketchPoint_) {
const sketchPointGeom = this.sketchPoint_.getGeometry();
sketchPointGeom.setCoordinates(coordinate);
@@ -759,6 +770,7 @@ class Draw extends PointerInteraction {
addToDrawing_(event) {
const coordinate = event.coordinate;
const geometry = this.sketchFeature_.getGeometry();
const projection = event.map.getView().getProjection();
let done;
let coordinates;
if (this.mode_ === Mode.LINE_STRING) {
@@ -772,7 +784,7 @@ class Draw extends PointerInteraction {
}
}
coordinates.push(coordinate.slice());
this.geometryFunction_(coordinates, geometry);
this.geometryFunction_(coordinates, geometry, projection);
} else if (this.mode_ === Mode.POLYGON) {
coordinates = /** @type {PolyCoordType} */ (this.sketchCoords_)[0];
if (coordinates.length >= this.maxPoints_) {
@@ -786,7 +798,7 @@ class Draw extends PointerInteraction {
if (done) {
this.finishCoordinate_ = coordinates[0];
}
this.geometryFunction_(this.sketchCoords_, geometry);
this.geometryFunction_(this.sketchCoords_, geometry, projection);
}
this.updateSketchFeatures_();
if (done) {
@@ -803,13 +815,14 @@ class Draw extends PointerInteraction {
return;
}
const geometry = this.sketchFeature_.getGeometry();
const projection = this.getMap().getView().getProjection();
let coordinates;
/** @type {LineString} */
let sketchLineGeom;
if (this.mode_ === Mode.LINE_STRING) {
coordinates = /** @type {LineCoordType} */ (this.sketchCoords_);
coordinates.splice(-2, 1);
this.geometryFunction_(coordinates, geometry);
this.geometryFunction_(coordinates, geometry, projection);
if (coordinates.length >= 2) {
this.finishCoordinate_ = coordinates[coordinates.length - 2].slice();
}
@@ -818,7 +831,7 @@ class Draw extends PointerInteraction {
coordinates.splice(-2, 1);
sketchLineGeom = this.sketchLine_.getGeometry();
sketchLineGeom.setCoordinates(coordinates);
this.geometryFunction_(this.sketchCoords_, geometry);
this.geometryFunction_(this.sketchCoords_, geometry, projection);
}
if (coordinates.length === 0) {
@@ -841,14 +854,15 @@ class Draw extends PointerInteraction {
}
let coordinates = this.sketchCoords_;
const geometry = sketchFeature.getGeometry();
const projection = this.getMap().getView().getProjection();
if (this.mode_ === Mode.LINE_STRING) {
// remove the redundant last point
coordinates.pop();
this.geometryFunction_(coordinates, geometry);
this.geometryFunction_(coordinates, geometry, projection);
} else if (this.mode_ === Mode.POLYGON) {
// remove the redundant last point in ring
/** @type {PolyCoordType} */ (coordinates)[0].pop();
this.geometryFunction_(coordinates, geometry);
this.geometryFunction_(coordinates, geometry, projection);
coordinates = geometry.getCoordinates();
}
@@ -881,12 +895,10 @@ class Draw extends PointerInteraction {
abortDrawing_() {
this.finishCoordinate_ = null;
const sketchFeature = this.sketchFeature_;
if (sketchFeature) {
this.sketchFeature_ = null;
this.sketchPoint_ = null;
this.sketchLine_ = null;
this.overlay_.getSource().clear(true);
}
this.sketchFeature_ = null;
this.sketchPoint_ = null;
this.sketchLine_ = null;
this.overlay_.getSource().clear(true);
return sketchFeature;
}
@@ -968,9 +980,9 @@ function getDefaultStyleFunction() {
* @api
*/
export function createRegularPolygon(opt_sides, opt_angle) {
return function(coordinates, opt_geometry) {
const center = /** @type {LineCoordType} */ (coordinates)[0];
const end = /** @type {LineCoordType} */ (coordinates)[1];
return function(coordinates, opt_geometry, projection) {
const center = fromUserCoordinate(/** @type {LineCoordType} */ (coordinates)[0], projection);
const end = fromUserCoordinate(/** @type {LineCoordType} */ (coordinates)[1], projection);
const radius = Math.sqrt(
squaredCoordinateDistance(center, end));
const geometry = opt_geometry ? /** @type {Polygon} */ (opt_geometry) :
@@ -982,6 +994,10 @@ export function createRegularPolygon(opt_sides, opt_angle) {
angle = Math.atan(y / x) - (x < 0 ? Math.PI : 0);
}
makeRegular(geometry, center, radius, angle);
const userProjection = getUserProjection();
if (userProjection) {
geometry.transform(projection, userProjection);
}
return geometry;
};
}
@@ -996,8 +1012,10 @@ export function createRegularPolygon(opt_sides, opt_angle) {
*/
export function createBox() {
return (
function(coordinates, opt_geometry) {
const extent = boundingExtent(/** @type {LineCoordType} */ (coordinates));
function(coordinates, opt_geometry, projection) {
const extent = boundingExtent(/** @type {LineCoordType} */ (coordinates).map(function(coordinate) {
return fromUserCoordinate(coordinate, projection);
}));
const boxCoordinates = [[
getBottomLeft(extent),
getBottomRight(extent),
@@ -1011,6 +1029,10 @@ export function createBox() {
} else {
geometry = new Polygon(boxCoordinates);
}
const userProjection = getUserProjection();
if (userProjection) {
geometry.transform(projection, userProjection);
}
return geometry;
}
);
+5 -4
View File
@@ -807,8 +807,8 @@ class Modify extends PointerInteraction {
if (!this.condition_(evt)) {
return false;
}
this.handlePointerAtPixel_(evt.pixel, evt.map);
const pixelCoordinate = evt.coordinate;
this.handlePointerAtPixel_(evt.pixel, evt.map, pixelCoordinate);
this.dragSegments_.length = 0;
this.modified_ = false;
const vertexFeature = this.vertexFeature_;
@@ -916,16 +916,17 @@ class Modify extends PointerInteraction {
*/
handlePointerMove_(evt) {
this.lastPixel_ = evt.pixel;
this.handlePointerAtPixel_(evt.pixel, evt.map);
this.handlePointerAtPixel_(evt.pixel, evt.map, evt.coordinate);
}
/**
* @param {import("../pixel.js").Pixel} pixel Pixel
* @param {import("../PluggableMap.js").default} map Map.
* @param {import("../coordinate.js").Coordinate=} opt_coordinate The pixel Coordinate.
* @private
*/
handlePointerAtPixel_(pixel, map) {
const pixelCoordinate = map.getCoordinateFromPixel(pixel);
handlePointerAtPixel_(pixel, map, opt_coordinate) {
const pixelCoordinate = opt_coordinate || map.getCoordinateFromPixel(pixel);
const projection = map.getView().getProjection();
const sortByDistance = function(a, b) {
return projectedDistanceToSegmentDataSquared(pixelCoordinate, a, projection) -
+17 -3
View File
@@ -1,7 +1,7 @@
/**
* @module ol/interaction/MouseWheelZoom
*/
import {always} from '../events/condition.js';
import {always, focus} from '../events/condition.js';
import EventType from '../events/EventType.js';
import {DEVICE_PIXEL_RATIO, FIREFOX} from '../has.js';
import Interaction, {zoomByDelta} from './Interaction.js';
@@ -134,13 +134,27 @@ class MouseWheelZoom extends Interaction {
}
/**
* @private
* @param {import("../MapBrowserEvent").default} mapBrowserEvent Event.
* @return {boolean} Condition passes.
*/
conditionInternal_(mapBrowserEvent) {
let pass = true;
if (mapBrowserEvent.map.getTargetElement().hasAttribute('tabindex')) {
pass = focus(mapBrowserEvent);
}
return pass && this.condition_(mapBrowserEvent);
}
/**
* @private
*/
endInteraction_() {
this.trackpadTimeoutId_ = undefined;
const view = this.getMap().getView();
view.endInteraction(undefined, Math.sign(this.lastDelta_), this.lastAnchor_);
view.endInteraction(undefined, this.lastDelta_ ? (this.lastDelta_ > 0 ? 1 : -1) : 0, this.lastAnchor_);
}
/**
@@ -149,7 +163,7 @@ class MouseWheelZoom extends Interaction {
* @override
*/
handleEvent(mapBrowserEvent) {
if (!this.condition_(mapBrowserEvent)) {
if (!this.conditionInternal_(mapBrowserEvent)) {
return true;
}
const type = mapBrowserEvent.type;
+10 -3
View File
@@ -95,6 +95,16 @@ class PointerInteraction extends Interaction {
}
/**
* Returns the current number of pointers involved in the interaction,
* e.g. `2` when two fingers are used.
* @return {number} The number of pointers.
* @api
*/
getPointerCount() {
return this.targetPointers.length;
}
/**
* Handle pointer down events.
* @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event.
@@ -136,9 +146,6 @@ class PointerInteraction extends Interaction {
} else {
if (mapBrowserEvent.type == MapBrowserEventType.POINTERDOWN) {
const handled = this.handleDownEvent(mapBrowserEvent);
if (handled) {
mapBrowserEvent.preventDefault();
}
this.handlingDownUpSequence = handled;
stopEvent = this.stopDown(handled);
} else if (mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE) {
+3 -1
View File
@@ -294,7 +294,9 @@ class Heatmap extends VectorLayer {
gl_FragColor.rgb *= gl_FragColor.a;
}`,
uniforms: {
u_gradientTexture: this.gradient_
u_gradientTexture: function() {
return this.gradient_;
}.bind(this)
}
}
]
+6 -3
View File
@@ -40,7 +40,9 @@ import {assign} from '../obj.js';
* * `'hybrid'`: Polygon and line elements are rendered as images, so pixels are scaled during zoom
* animations. Point symbols and texts are accurately rendered as vectors and can stay upright on
* rotated views.
*
* * `'vector'`: Everything is rendered as vectors. Use this mode for improved performance on vector
* tile layers with only a few rendered features (e.g. for highlighting a subset of features of
* another layer with the same source).
* @property {import("../source/VectorTile.js").default} [source] Source.
* @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
@@ -92,8 +94,9 @@ class VectorTileLayer extends BaseVectorLayer {
const renderMode = options.renderMode || VectorTileRenderType.HYBRID;
assert(renderMode == undefined ||
renderMode == VectorTileRenderType.IMAGE ||
renderMode == VectorTileRenderType.HYBRID,
28); // `renderMode` must be `'image'` or `'hybrid'`
renderMode == VectorTileRenderType.HYBRID ||
renderMode == VectorTileRenderType.VECTOR,
28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`.
/**
* @private
+6 -1
View File
@@ -11,9 +11,14 @@
* * `'hybrid'`: Polygon and line elements are rendered as images, so pixels
* are scaled during zoom animations. Point symbols and texts are accurately
* rendered as vectors and can stay upright on rotated views.
* * `'vector'`: Everything is rendered as vectors. Use this mode for improved
* performance on vector tile layers with only a few rendered features (e.g.
* for highlighting a subset of features of another layer with the same
* source).
* @api
*/
export default {
IMAGE: 'image',
HYBRID: 'hybrid'
HYBRID: 'hybrid',
VECTOR: 'vector'
};
+1 -1
View File
@@ -24,7 +24,7 @@ import Layer from './Layer.js';
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {import("../source/Vector.js").default} [source] Source.
* @property {boolean} [disableHitDetection] Setting this to true will provide a slight performance boost, but will
* @property {boolean} [disableHitDetection=false] Setting this to true will provide a slight performance boost, but will
* prevent all hit detection on the layer.
*/
+9
View File
@@ -74,6 +74,12 @@
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.ol-overlaycontainer, .ol-overlaycontainer-stopevent {
pointer-events: none;
}
.ol-overlaycontainer > *, .ol-overlaycontainer-stopevent > * {
pointer-events: auto;
}
.ol-selectable {
-webkit-touch-callout: default;
-webkit-user-select: text;
@@ -144,6 +150,9 @@
border: none;
padding: 0;
}
.ol-control button span {
pointer-events: none;
}
.ol-zoom-extent button {
line-height: 1.4em;
}
+5 -17
View File
@@ -216,15 +216,12 @@ export const checkFont = (function() {
* @return {boolean} Font with style and weight is available
*/
function isAvailable(fontStyle, fontWeight, fontFamily) {
const context = getMeasureContext();
let available = true;
for (let i = 0; i < len; ++i) {
const referenceFont = referenceFonts[i];
context.font = fontStyle + ' ' + fontWeight + ' ' + size + referenceFont;
referenceWidth = context.measureText(text).width;
referenceWidth = measureTextWidth(fontStyle + ' ' + fontWeight + ' ' + size + referenceFont, text);
if (fontFamily != referenceFont) {
context.font = fontStyle + ' ' + fontWeight + ' ' + size + fontFamily + ',' + referenceFont;
const width = context.measureText(text).width;
const width = measureTextWidth(fontStyle + ' ' + fontWeight + ' ' + size + fontFamily + ',' + referenceFont, text);
// If width and referenceWidth are the same, then the fallback was used
// instead of the font we wanted, so the font is not available.
available = available && width != referenceWidth;
@@ -284,17 +281,6 @@ export const checkFont = (function() {
})();
/**
* @return {CanvasRenderingContext2D} Measure context.
*/
function getMeasureContext() {
if (!measureContext) {
measureContext = createCanvasContext2D(1, 1);
}
return measureContext;
}
/**
* @param {string} font Font to use for measuring.
* @return {import("../size.js").Size} Measurement.
@@ -333,7 +319,9 @@ export const measureTextHeight = (function() {
* @return {number} Width.
*/
export function measureTextWidth(font, text) {
const measureContext = getMeasureContext();
if (!measureContext) {
measureContext = createCanvasContext2D(1, 1);
}
if (font != measureFont) {
measureContext.font = font;
measureFont = measureContext.font;
+2 -13
View File
@@ -18,8 +18,7 @@ import {
} from '../../transform.js';
import {createCanvasContext2D} from '../../dom.js';
import {labelCache, defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
import Disposable from '../../Disposable.js';
import RBush from 'rbush';
import RBush from 'rbush/rbush.js';
/**
@@ -52,7 +51,7 @@ const p3 = [];
const p4 = [];
class Executor extends Disposable {
class Executor {
/**
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
@@ -60,7 +59,6 @@ class Executor extends Disposable {
* @param {SerializableInstructions} instructions The serializable instructions
*/
constructor(resolution, pixelRatio, overlaps, instructions) {
super();
/**
* @protected
@@ -156,15 +154,6 @@ class Executor extends Disposable {
this.widths_ = {};
}
/**
* @inheritDoc
*/
disposeInternal() {
labelCache.release(this);
super.disposeInternal();
}
/**
* @param {string} text Text.
* @param {string} textKey Text style key.
+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.
+2 -51
View File
@@ -1,4 +1,3 @@
import {getUid} from '../../util.js';
import LRUCache from '../../structs/LRUCache.js';
/**
@@ -11,59 +10,11 @@ import LRUCache from '../../structs/LRUCache.js';
*/
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:
expireCache() {
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];
}
this.pop();
}
}
/**
* @param {import("./Executor.js").default} consumer Label consumer.
*/
release(consumer) {
delete this.consumers[getUid(consumer)];
}
}
export default LabelCache;
+2 -3
View File
@@ -131,8 +131,6 @@ class CanvasTextBuilder extends CanvasBuilder {
* @type {string}
*/
this.strokeKey_ = '';
labelCache.prune();
}
/**
@@ -140,6 +138,7 @@ class CanvasTextBuilder extends CanvasBuilder {
*/
finish() {
const instructions = super.finish();
labelCache.expireCache();
instructions.textStates = this.textStates;
instructions.fillStates = this.fillStates;
instructions.strokeStates = this.strokeStates;
@@ -461,7 +460,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))) :
'';
+9 -6
View File
@@ -33,15 +33,18 @@ 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) / featureCount);
const indexFactor = Math.ceil((256 * 256 * 256 - 1) / featureCount);
const featuresByZIndex = {};
for (let i = 0; i < featureCount; ++i) {
const feature = features[i];
for (let i = 1; i <= featureCount; ++i) {
const feature = features[i - 1];
const featureStyleFunction = feature.getStyleFunction() || styleFunction;
if (!styleFunction) {
continue;
}
let styles = featureStyleFunction(feature, resolution);
if (!styles) {
continue;
}
if (!Array.isArray(styles)) {
styles = [styles];
}
@@ -138,9 +141,9 @@ 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) / features.length);
if (i % indexFactor === 0) {
resultFeatures.push(features[i / indexFactor]);
const indexFactor = Math.ceil((256 * 256 * 256 - 1) / features.length);
if (i && i % indexFactor === 0) {
resultFeatures.push(features[i / indexFactor - 1]);
}
}
return resultFeatures;
+1
View File
@@ -74,6 +74,7 @@ class CompositeMapRenderer extends MapRenderer {
disposeInternal() {
unlistenByKey(this.labelCacheKey_);
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
+39 -43
View File
@@ -232,49 +232,45 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
getFeatures(pixel) {
return new Promise(function(resolve, reject) {
if (!this.hitDetectionImageData_ && !this.animatingOrInteracting_) {
requestAnimationFrame(function() {
const size = [this.context.canvas.width, this.context.canvas.height];
apply(this.pixelTransform, size);
const center = this.renderedCenter_;
const resolution = this.renderedResolution_;
const rotation = this.renderedRotation_;
const projection = this.renderedProjection_;
const extent = this.renderedExtent_;
const layer = this.getLayer();
const transforms = [];
const width = size[0] / 2;
const height = size[1] / 2;
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, 0).slice());
const source = layer.getSource();
const projectionExtent = projection.getExtent();
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
let startX = extent[0];
const worldWidth = getWidth(projectionExtent);
let world = 0;
let offsetX;
while (startX < projectionExtent[0]) {
--world;
offsetX = worldWidth * world;
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, offsetX).slice());
startX += worldWidth;
}
world = 0;
startX = extent[2];
while (startX > projectionExtent[2]) {
++world;
offsetX = worldWidth * world;
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, offsetX).slice());
startX -= worldWidth;
}
const size = [this.context.canvas.width, this.context.canvas.height];
apply(this.pixelTransform, size);
const center = this.renderedCenter_;
const resolution = this.renderedResolution_;
const rotation = this.renderedRotation_;
const projection = this.renderedProjection_;
const extent = this.renderedExtent_;
const layer = this.getLayer();
const transforms = [];
const width = size[0] / 2;
const height = size[1] / 2;
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, 0).slice());
const source = layer.getSource();
const projectionExtent = projection.getExtent();
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
let startX = extent[0];
const worldWidth = getWidth(projectionExtent);
let world = 0;
let offsetX;
while (startX < projectionExtent[0]) {
--world;
offsetX = worldWidth * world;
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, offsetX).slice());
startX += worldWidth;
}
world = 0;
startX = extent[2];
while (startX > projectionExtent[2]) {
++world;
offsetX = worldWidth * world;
transforms.push(this.getRenderTransform(center, resolution, rotation, 0.5, width, height, offsetX).slice());
startX -= worldWidth;
}
}
this.hitDetectionImageData_ = createHitDetectionImageData(size, transforms,
this.renderedFeatures_, layer.getStyleFunction(), extent, resolution, rotation);
resolve(hitDetect(pixel, this.renderedFeatures_, this.hitDetectionImageData_));
}.bind(this));
} else {
resolve(hitDetect(pixel, this.renderedFeatures_, this.hitDetectionImageData_));
this.hitDetectionImageData_ = createHitDetectionImageData(size, transforms,
this.renderedFeatures_, layer.getStyleFunction(), extent, resolution, rotation);
}
resolve(hitDetect(pixel, this.renderedFeatures_, this.hitDetectionImageData_));
}.bind(this));
}
@@ -333,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];
@@ -388,9 +387,6 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
return true;
}
if (this.replayGroup_) {
this.replayGroup_.dispose();
}
this.replayGroup_ = null;
this.dirty_ = false;
+23 -30
View File
@@ -33,7 +33,8 @@ import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdet
const IMAGE_REPLAYS = {
'image': [ReplayType.POLYGON, ReplayType.CIRCLE,
ReplayType.LINE_STRING, ReplayType.IMAGE, ReplayType.TEXT],
'hybrid': [ReplayType.POLYGON, ReplayType.LINE_STRING]
'hybrid': [ReplayType.POLYGON, ReplayType.LINE_STRING],
'vector': []
};
@@ -42,7 +43,8 @@ const IMAGE_REPLAYS = {
*/
const VECTOR_REPLAYS = {
'image': [ReplayType.DEFAULT],
'hybrid': [ReplayType.IMAGE, ReplayType.TEXT, ReplayType.DEFAULT]
'hybrid': [ReplayType.IMAGE, ReplayType.TEXT, ReplayType.DEFAULT],
'vector': [ReplayType.POLYGON, ReplayType.CIRCLE, ReplayType.LINE_STRING, ReplayType.IMAGE, ReplayType.TEXT, ReplayType.DEFAULT]
};
@@ -115,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];
@@ -137,11 +138,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
* @inheritDoc
*/
getTile(z, x, y, frameState) {
const tile = /** @type {import("../../VectorRenderTile.js").default} */ (super.getTile(z, x, y, frameState));
const pixelRatio = frameState.pixelRatio;
const viewState = frameState.viewState;
const resolution = viewState.resolution;
const projection = viewState.projection;
const layer = this.getLayer();
const tile = layer.getSource().getTile(z, x, y, pixelRatio, projection);
if (tile.getState() < TileState.LOADED) {
tile.wantedResolution = resolution;
const tileUid = getUid(tile);
@@ -156,18 +158,19 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
tile.wantedResolution = resolution;
}
const render = this.prepareTile(tile, pixelRatio, projection, false);
if (render) {
if (render && layer.getRenderMode() !== VectorTileRenderType.VECTOR) {
this.renderTileImage_(tile, frameState);
}
}
return tile;
return super.getTile(z, x, y, frameState);
}
/**
* @inheritdoc
*/
isDrawableTile(tile) {
return super.isDrawableTile(tile) && tile.hasContext(this.getLayer());
const layer = this.getLayer();
return super.isDrawableTile(tile) && layer.getRenderMode() === VectorTileRenderType.VECTOR || tile.hasContext(layer);
}
/**
@@ -215,13 +218,7 @@ 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();
}
}
tile.hitDetectionImageData = null;
delete tile.hitDetectionImageData[layerUid];
tile.executorGroups[layerUid] = [];
for (let t = 0, tt = sourceTiles.length; t < tt; ++t) {
const sourceTile = sourceTiles[t];
@@ -267,7 +264,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
}
const executorGroupInstructions = builderGroup.finish();
// no need to clip when the render tile is covered by a single source tile
const replayExtent = layer.getDeclutter() && sourceTiles.length === 1 ?
const replayExtent = layer.getRenderMode() !== VectorTileRenderType.VECTOR && layer.getDeclutter() && sourceTiles.length === 1 ?
null :
sharedExtent;
const renderingReplayGroup = new CanvasExecutorGroup(replayExtent, resolution,
@@ -341,6 +338,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
getFeatures(pixel) {
return new Promise(function(resolve, reject) {
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
const layerUid = getUid(layer);
const source = layer.getSource();
const projection = this.renderedProjection;
const projectionExtent = projection.getExtent();
@@ -364,7 +362,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
tile = undefined;
}
}
if (!tile) {
if (!tile || tile.loadingSourceTiles > 0) {
resolve([]);
return;
}
@@ -377,7 +375,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
const features = tile.getSourceTiles().reduce(function(accumulator, sourceTile) {
return accumulator.concat(sourceTile.getFeatures());
}, []);
if (!tile.hitDetectionImageData) {
let hitDetectionImageData = tile.hitDetectionImageData[layerUid];
if (!hitDetectionImageData && !this.animatingOrInteracting_) {
const tileSize = toSize(tileGrid.getTileSize(tileGrid.getZForResolution(resolution)));
const size = [tileSize[0] / 2, tileSize[1] / 2];
const rotation = this.renderedRotation_;
@@ -385,16 +384,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
this.getRenderTransform(tileGrid.getTileCoordCenter(tile.wrappedTileCoord),
resolution, 0, 0.5, size[0], size[1], 0)
];
requestAnimationFrame(function() {
tile.hitDetectionImageData = createHitDetectionImageData(tileSize, transforms,
features, layer.getStyleFunction(),
tileGrid.getTileCoordExtent(tile.wrappedTileCoord),
tile.getReplayState(layer).renderedResolution, rotation);
resolve(hitDetect(tilePixel, features, tile.hitDetectionImageData));
});
} else {
resolve(hitDetect(tilePixel, features, tile.hitDetectionImageData));
hitDetectionImageData = createHitDetectionImageData(tileSize, transforms,
features, layer.getStyleFunction(),
tileGrid.getTileCoordExtent(tile.wrappedTileCoord),
tile.getReplayState(layer).renderedResolution, rotation);
tile.hitDetectionImageData[layerUid] = hitDetectionImageData;
}
resolve(hitDetect(tilePixel, features, hitDetectionImageData));
}.bind(this));
}
@@ -465,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];
-8
View File
@@ -143,10 +143,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
options.vertexShader
);
if (this.getShaderCompileErrors()) {
throw new Error(this.getShaderCompileErrors());
}
/**
* @type {boolean}
* @private
@@ -158,10 +154,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
options.hitVertexShader
);
if (this.getShaderCompileErrors()) {
throw new Error(this.getShaderCompileErrors());
}
const customAttributes = options.attributes ?
options.attributes.map(function(attribute) {
return {
-10
View File
@@ -202,16 +202,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.
+8 -5
View File
@@ -143,7 +143,8 @@ export class CustomTile extends Tile {
* The tile data is requested if not yet loaded.
*/
forDataAtCoordinate(coordinate, callback, opt_request) {
if (this.state == TileState.IDLE && opt_request === true) {
if (this.state == TileState.EMPTY && opt_request === true) {
this.state = TileState.IDLE;
listenOnce(this, EventType.CHANGE, function(e) {
callback(this.getData(coordinate));
}, this);
@@ -186,7 +187,7 @@ export class CustomTile extends Tile {
this.keys_ = json['keys'];
this.data_ = json['data'];
this.state = TileState.EMPTY;
this.state = TileState.LOADED;
this.changed();
}
@@ -248,6 +249,8 @@ export class CustomTile extends Tile {
load() {
if (this.preemptive_) {
this.loadInternal_();
} else {
this.setState(TileState.EMPTY);
}
}
}
@@ -258,9 +261,9 @@ export class CustomTile extends Tile {
* @property {boolean} [preemptive=true]
* If `true` the UTFGrid source loads the tiles based on their "visibility".
* This improves the speed of response, but increases traffic.
* Note that if set to `false`, you need to pass `true` as `opt_request`
* to the `forDataAtCoordinateAndResolution` method otherwise no data
* will ever be loaded.
* Note that if set to `false` (lazy loading), you need to pass `true` as
* `opt_request` to the `forDataAtCoordinateAndResolution` method otherwise no
* data will ever be loaded.
* @property {boolean} [jsonp=false] Use JSONP with callback to load the TileJSON.
* Useful when the server does not support CORS..
* @property {import("./TileJSON.js").Config} [tileJSON] TileJSON configuration for this source.
+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) {
+80 -66
View File
@@ -7,13 +7,13 @@ import VectorRenderTile from '../VectorRenderTile.js';
import Tile from '../VectorTile.js';
import {toSize} from '../size.js';
import UrlTile from './UrlTile.js';
import {getKeyZXY, getKey} from '../tilecoord.js';
import {getKeyZXY, fromKey} from '../tilecoord.js';
import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid.js';
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
@@ -52,6 +52,17 @@ import {listen, unlistenByKey} from '../events.js';
* });
* }
* ```
* If you do not need extent, resolution and projection to get the features for a tile (e.g.
* for GeoJSON tiles), your `tileLoadFunction` does not need a `setLoader()` call. Only make sure
* to call `setFeatures()` on the tile:
* ```js
* const format = new GeoJSON({featureProjection: map.getView().getProjection()});
* async function tileLoadFunction(tile, url) {
* const response = await fetch(url);
* const data = await response.json();
* tile.setFeatures(format.readFeatures(data));
* }
* ```
* @property {import("../Tile.js").UrlFunction} [tileUrlFunction] Optional function to get tile URL given a tile coordinate and the projection.
* @property {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
@@ -129,15 +140,9 @@ class VectorTile extends UrlTile {
/**
* @private
* @type {Object<string, import("../VectorTile.js").default>}
* @type {TileCache}
*/
this.sourceTileByCoordKey_ = {};
/**
* @private
* @type {Object<string, Array<import("../VectorTile.js").default>>}
*/
this.sourceTilesByTileKey_ = {};
this.sourceTileCache = new TileCache(this.tileCache.highWaterMark);
/**
* @private
@@ -159,6 +164,51 @@ class VectorTile extends UrlTile {
}
/**
* Get features whose bounding box intersects the provided extent. Only features for cached
* tiles for the last rendered zoom level are available in the source. So this method is only
* suitable for requesting tiles for extents that are currently rendered.
*
* Features are returned in random tile order and as they are included in the tiles. This means
* they can be clipped, duplicated across tiles, and simplified to the render resolution.
*
* @param {import("../extent.js").Extent} extent Extent.
* @return {Array<import("../Feature.js").FeatureLike>} Features.
* @api
*/
getFeaturesInExtent(extent) {
const features = [];
const tileCache = this.tileCache;
if (tileCache.getCount() === 0) {
return features;
}
const z = fromKey(tileCache.peekFirstKey())[0];
const tileGrid = this.tileGrid;
tileCache.forEach(function(tile) {
if (tile.tileCoord[0] !== z || tile.getState() !== TileState.LOADED) {
return;
}
const sourceTiles = tile.getSourceTiles();
for (let i = 0, ii = sourceTiles.length; i < ii; ++i) {
const sourceTile = sourceTiles[i];
const tileCoord = sourceTile.tileCoord;
if (intersects(extent, tileGrid.getTileCoordExtent(tileCoord))) {
const tileFeatures = sourceTile.getFeatures();
if (tileFeatures) {
for (let j = 0, jj = tileFeatures.length; j < jj; ++j) {
const candidate = tileFeatures[j];
const geometry = candidate.getGeometry();
if (intersects(extent, geometry.getExtent())) {
features.push(candidate);
}
}
}
}
}
});
return features;
}
/**
* @return {boolean} The source can have overlapping geometries.
*/
@@ -172,8 +222,7 @@ class VectorTile extends UrlTile {
*/
clear() {
this.tileCache.clear();
this.sourceTileByCoordKey_ = {};
this.sourceTilesByTileKey_ = {};
this.sourceTileCache.clear();
}
/**
@@ -198,7 +247,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;
@@ -211,41 +260,39 @@ class VectorTile extends UrlTile {
--loadedZ;
covered = true;
sourceTileGrid.forEachTileCoord(extent, loadedZ, function(sourceTileCoord) {
const coordKey = getKey(sourceTileCoord);
const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
let sourceTile;
if (coordKey in this.sourceTileByCoordKey_) {
sourceTile = this.sourceTileByCoordKey_[coordKey];
const state = sourceTile.getState();
if (state === TileState.LOADED || state === TileState.ERROR || state === TileState.EMPTY) {
sourceTiles.push(sourceTile);
return;
}
} else if (loadedZ === sourceZ) {
const tileUrl = this.tileUrlFunction(sourceTileCoord, pixelRatio, projection);
if (tileUrl !== undefined) {
if (tileUrl !== undefined) {
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);
return;
}
} else if (loadedZ === sourceZ) {
sourceTile = new this.tileClass(sourceTileCoord, TileState.IDLE, tileUrl,
this.format_, this.tileLoadFunction);
sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
sourceTile.projection = projection;
sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
this.sourceTileByCoordKey_[coordKey] = sourceTile;
this.sourceTileCache.set(tileUrl, sourceTile);
sourceTile.addEventListener(EventType.CHANGE, this.handleTileChange.bind(this));
sourceTile.load();
}
}
covered = false;
covered = covered && sourceTile && sourceTile.getState() === TileState.LOADED;
if (!sourceTile) {
return;
}
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) {
@@ -259,7 +306,6 @@ class VectorTile extends UrlTile {
}
}
});
tile.sourceTileListenerKeys.push(key);
}
}.bind(this));
if (!covered) {
@@ -277,43 +323,13 @@ 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;
}
}
this.sourceTileCache.expireCache({});
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.sourceTileByCoordKey_[getKey(sourceTile.tileCoord)];
}
}
}
delete this.sourceTilesByTileKey_[tileKey];
}
/**
* @inheritDoc
*/
@@ -355,9 +371,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
@@ -152,7 +152,7 @@ class WMTS extends TileImage {
/**
* Set the URLs to use for requests.
* URLs may contain OCG conform URL Template Variables: {TileMatrix}, {TileRow}, {TileCol}.
* URLs may contain OGC conform URL Template Variables: {TileMatrix}, {TileRow}, {TileCol}.
* @override
*/
setUrls(urls) {
+1 -1
View File
@@ -2,7 +2,7 @@
* @module ol/structs/RBush
*/
import {getUid} from '../util.js';
import RBush_ from 'rbush';
import RBush_ from 'rbush/rbush.js';
import {createOrUpdate, equals} from '../extent.js';
import {isEmpty} from '../obj.js';
+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.
+1 -1
View File
@@ -10,7 +10,7 @@
* Default null; if null, the Canvas/renderer default black will be used.
* @property {CanvasLineCap} [lineCap='round'] Line cap style: `butt`, `round`, or `square`.
* @property {CanvasLineJoin} [lineJoin='round'] Line join style: `bevel`, `round`, or `miter`.
* @property {Array<number>} [lineDash] Line dash pattern. Default is `undefined` (no dash).
* @property {Array<number>} [lineDash] Line dash pattern. Default is `null` (no dash).
* Please note that Internet Explorer 10 and lower do not support the `setLineDash` method on
* the `CanvasRenderingContext2D` and therefore this option will have no visual effect in these browsers.
* @property {number} [lineDashOffset=0] Line dash offset.
+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 -3
View File
@@ -25,17 +25,23 @@ const tmpTileCoord = [0, 0, 0];
* `origins` are configured, the `origin` will be set to the top-left corner of the extent.
* @property {number} [minZoom=0] Minimum zoom.
* @property {import("../coordinate.js").Coordinate} [origin] The tile grid origin, i.e. where the `x`
* and `y` axes meet (`[z, 0, 0]`). Tile coordinates increase left to right and upwards. If not
* and `y` axes meet (`[z, 0, 0]`). Tile coordinates increase left to right and downwards. If not
* specified, `extent` or `origins` must be provided.
* @property {Array<import("../coordinate.js").Coordinate>} [origins] Tile grid origins, i.e. where
* the `x` and `y` axes meet (`[z, 0, 0]`), for each zoom level. If given, the array length
* should match the length of the `resolutions` array, i.e. each resolution can have a different
* origin. Tile coordinates increase left to right and upwards. If not specified, `extent` or
* origin. Tile coordinates increase left to right and downwards. If not specified, `extent` or
* `origin` must be provided.
* @property {!Array<number>} resolutions Resolutions. The array index of each resolution needs
* to match the zoom level. This means that even if a `minZoom` is configured, the resolutions
* array will have a length of `maxZoom + 1`.
* @property {Array<import("../size.js").Size>} [sizes] Sizes.
* @property {Array<import("../size.js").Size>} [sizes] Number of tile rows and columns
* of the grid for each zoom level. If specified the values
* define each zoom level's extent together with the `origin` or `origins`.
* A grid `extent` can be configured in addition, and will further limit the extent
* for which tile requests are made by sources. If the bottom-left corner of
* an extent is used as `origin` or `origins`, then the `y` value must be
* negative because OpenLayers tile coordinates use the top left as the origin.
* @property {number|import("../size.js").Size} [tileSize] Tile size.
* Default is `[256, 256]`.
* @property {Array<import("../size.js").Size>} [tileSizes] Tile sizes. If given, the array length
+5 -10
View File
@@ -15,12 +15,12 @@ import TileGrid from './TileGrid.js';
* top-left corner of the extent.
* @property {import("../coordinate.js").Coordinate} [origin] The tile grid origin, i.e.
* where the `x` and `y` axes meet (`[z, 0, 0]`). Tile coordinates increase left
* to right and upwards. If not specified, `extent` or `origins` must be provided.
* to right and downwards. If not specified, `extent` or `origins` must be provided.
* @property {Array<import("../coordinate.js").Coordinate>} [origins] Tile grid origins,
* i.e. where the `x` and `y` axes meet (`[z, 0, 0]`), for each zoom level. If
* given, the array length should match the length of the `resolutions` array, i.e.
* each resolution can have a different origin. Tile coordinates increase left to
* right and upwards. If not specified, `extent` or `origin` must be provided.
* right and downwards. If not specified, `extent` or `origin` must be provided.
* @property {!Array<number>} resolutions Resolutions. The array index of each
* resolution needs to match the zoom level. This means that even if a `minZoom`
* is configured, the resolutions array will have a length of `maxZoom + 1`
@@ -29,19 +29,14 @@ import TileGrid from './TileGrid.js';
* @property {Array<import("../size.js").Size>} [sizes] Number of tile rows and columns
* of the grid for each zoom level. The values here are the `TileMatrixWidth` and
* `TileMatrixHeight` advertised in the GetCapabilities response of the WMTS, and
* define the grid's extent together with the `origin`.
* An `extent` can be configured in addition, and will further limit the extent for
* define each zoom level's extent together with the `origin` or `origins`.
* A grid `extent` can be configured in addition, and will further limit the extent for
* which tile requests are made by sources. If the bottom-left corner of
* the `extent` is used as `origin` or `origins`, then the `y` value must be
* an extent is used as `origin` or `origins`, then the `y` value must be
* negative because OpenLayers tile coordinates use the top left as the origin.
* @property {number|import("../size.js").Size} [tileSize] Tile size.
* @property {Array<import("../size.js").Size>} [tileSizes] Tile sizes. The length of
* this array needs to match the length of the `resolutions` array.
* @property {Array<number>} [widths] Number of tile columns that cover the grid's
* extent for each zoom level. Only required when used with a source that has `wrapX`
* set to `true`, and only when the grid's origin differs from the one of the
* projection's extent. The array length has to match the length of the `resolutions`
* array, i.e. each resolution will have a matching entry here.
*/
+19
View File
@@ -8,6 +8,7 @@ import {assert} from './asserts.js';
* An array representing an affine 2d transformation for use with
* {@link module:ol/transform} functions. The array has 6 elements.
* @typedef {!Array<number>} Transform
* @api
*/
@@ -210,6 +211,24 @@ export function compose(transform, dx1, dy1, sx, sy, angle, dx2, dy2) {
return transform;
}
/**
* Creates a composite transform given an initial translation, scale, rotation, and
* final translation (in that order only, not commutative). The resulting transform
* string can be applied as `transform` porperty of an HTMLElement's style.
* @param {number} dx1 Initial translation x.
* @param {number} dy1 Initial translation y.
* @param {number} sx Scale factor x.
* @param {number} sy Scale factor y.
* @param {number} angle Rotation (in counter-clockwise radians).
* @param {number} dx2 Final translation x.
* @param {number} dy2 Final translation y.
* @return {string} The composite css transform.
* @api
*/
export function composeCssTransform(dx1, dy1, sx, sy, angle, dx2, dy2) {
return toString(compose(create(), dx1, dy1, sx, sy, angle, dx2, dy2));
}
/**
* Invert the given transform.
+12 -12
View File
@@ -24,7 +24,6 @@ const DEFAULT_FRAGMENT_SHADER = `
uniform sampler2D u_image;
varying vec2 v_texCoord;
varying vec2 v_screenCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
@@ -89,7 +88,6 @@ const DEFAULT_FRAGMENT_SHADER = `
* uniform sampler2D u_image;
*
* varying vec2 v_texCoord;
* varying vec2 v_screenCoord;
*
* void main() {
* gl_FragColor = texture2D(u_image, v_texCoord);
@@ -176,17 +174,19 @@ class WebGLPostProcessingPass {
*/
init(frameState) {
const gl = this.getGL();
const canvas = gl.canvas;
const size = frameState.size;
const textureSize = [
gl.drawingBufferWidth * this.scaleRatio_,
gl.drawingBufferHeight * this.scaleRatio_
];
// rendering goes to my buffer
gl.bindFramebuffer(gl.FRAMEBUFFER, this.getFrameBuffer());
gl.viewport(0, 0, canvas.width * this.scaleRatio_, canvas.height * this.scaleRatio_);
gl.viewport(0, 0, textureSize[0], textureSize[1]);
// if size has changed: adjust canvas & render target texture
if (!this.renderTargetTextureSize_ ||
this.renderTargetTextureSize_[0] !== size[0] || this.renderTargetTextureSize_[1] !== size[1]) {
this.renderTargetTextureSize_ = size;
this.renderTargetTextureSize_[0] !== textureSize[0] || this.renderTargetTextureSize_[1] !== textureSize[1]) {
this.renderTargetTextureSize_ = textureSize;
// create a new texture
const level = 0;
@@ -197,8 +197,8 @@ class WebGLPostProcessingPass {
const data = null;
gl.bindTexture(gl.TEXTURE_2D, this.renderTargetTexture_);
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
canvas.width * this.scaleRatio_, canvas.height * this.scaleRatio_, border,
format, type, data);
textureSize[0], textureSize[1],
border, format, type, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
@@ -217,7 +217,7 @@ class WebGLPostProcessingPass {
*/
apply(frameState, nextPass) {
const gl = this.getGL();
const canvas = gl.canvas;
const size = frameState.size;
gl.bindFramebuffer(gl.FRAMEBUFFER, nextPass ? nextPass.getFrameBuffer() : null);
gl.activeTexture(gl.TEXTURE0);
@@ -228,14 +228,14 @@ class WebGLPostProcessingPass {
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
gl.bindBuffer(gl.ARRAY_BUFFER, this.renderTargetVerticesBuffer_);
gl.useProgram(this.renderTargetProgram_);
gl.enableVertexAttribArray(this.renderTargetAttribLocation_);
gl.vertexAttribPointer(this.renderTargetAttribLocation_, 2, gl.FLOAT, false, 0, 0);
gl.uniform2f(this.renderTargetUniformLocation_, canvas.width, canvas.height);
gl.uniform2f(this.renderTargetUniformLocation_, size[0], size[1]);
gl.uniform1i(this.renderTargetTextureLocation_, 0);
this.applyUniforms(frameState);
+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_;
}