Merge branch 'master' into patch-4
This commit is contained in:
+78
-95
@@ -37,15 +37,21 @@ import {containsExtent} from './extent.js';
|
||||
* container as that of the controls (see the `stopEvent` option) you will
|
||||
* probably set `insertFirst` to `true` so the overlay is displayed below the
|
||||
* controls.
|
||||
* @property {boolean} [autoPan=false] If set to `true` the map is panned when
|
||||
* calling `setPosition`, so that the overlay is entirely visible in the current
|
||||
* viewport.
|
||||
* @property {PanOptions} [autoPanAnimation] The
|
||||
* animation options used to pan the overlay into view. This animation is only
|
||||
* used when `autoPan` is enabled. A `duration` and `easing` may be provided to
|
||||
* customize the animation.
|
||||
* @property {PanIntoViewOptions|boolean} [autoPan=false] Pan the map when calling
|
||||
* `setPosition`, so that the overlay is entirely visible in the current viewport?
|
||||
* If `true` (deprecated), then `autoPanAnimation` and `autoPanMargin` will be
|
||||
* used to determine the panning parameters; if an object is supplied then other
|
||||
* parameters are ignored.
|
||||
* @property {PanOptions} [autoPanAnimation] The animation options used to pan
|
||||
* the overlay into view. This animation is only used when `autoPan` is enabled.
|
||||
* A `duration` and `easing` may be provided to customize the animation.
|
||||
* Deprecated and ignored if `autoPan` is supplied as an object.
|
||||
* @property {number} [autoPanMargin=20] The margin (in pixels) between the
|
||||
* overlay and the borders of the map when autopanning.
|
||||
* overlay and the borders of the map when autopanning. Deprecated and ignored
|
||||
* if `autoPan` is supplied as an object.
|
||||
* @property {PanIntoViewOptions} [autoPanOptions] The options to use for the
|
||||
* autoPan. This is only used when `autoPan` is enabled and has preference over
|
||||
* the individual `autoPanMargin` and `autoPanOptions`.
|
||||
* @property {string} [className='ol-overlay-container ol-selectable'] CSS class
|
||||
* name.
|
||||
*/
|
||||
@@ -60,6 +66,12 @@ import {containsExtent} from './extent.js';
|
||||
* Default is {@link module:ol/easing~inAndOut}.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PanIntoViewOptions
|
||||
* @property {PanOptions} [animation={}] The animation parameters for the pan
|
||||
* @property {number} [margin=20] The margin (in pixels) between the
|
||||
* overlay and the borders of the map when panning into view.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @enum {string}
|
||||
@@ -137,38 +149,26 @@ class Overlay extends BaseObject {
|
||||
options.className : 'ol-overlay-container ' + CLASS_SELECTABLE;
|
||||
this.element.style.position = 'absolute';
|
||||
|
||||
let autoPan = options.autoPan;
|
||||
if (autoPan && ('object' !== typeof autoPan)) {
|
||||
autoPan = {
|
||||
animation: options.autoPanAnimation,
|
||||
margin: options.autoPanMargin
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
* @type {PanIntoViewOptions|false}
|
||||
*/
|
||||
this.autoPan = options.autoPan !== undefined ? options.autoPan : false;
|
||||
this.autoPan = /** @type {PanIntoViewOptions} */(autoPan) || false;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {PanOptions}
|
||||
*/
|
||||
this.autoPanAnimation = options.autoPanAnimation || /** @type {PanOptions} */ ({});
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {number}
|
||||
*/
|
||||
this.autoPanMargin = options.autoPanMargin !== undefined ?
|
||||
options.autoPanMargin : 20;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {{bottom_: string,
|
||||
* left_: string,
|
||||
* right_: string,
|
||||
* top_: string,
|
||||
* @type {{transform_: string,
|
||||
* visible: boolean}}
|
||||
*/
|
||||
this.rendered = {
|
||||
bottom_: '',
|
||||
left_: '',
|
||||
right_: '',
|
||||
top_: '',
|
||||
transform_: '',
|
||||
visible: true
|
||||
};
|
||||
|
||||
@@ -300,6 +300,7 @@ class Overlay extends BaseObject {
|
||||
} else {
|
||||
container.appendChild(this.element);
|
||||
}
|
||||
this.performAutoPan();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,9 +323,7 @@ class Overlay extends BaseObject {
|
||||
*/
|
||||
handlePositionChanged() {
|
||||
this.updatePixelPosition();
|
||||
if (this.get(Property.POSITION) && this.autoPan) {
|
||||
this.panIntoView();
|
||||
}
|
||||
this.performAutoPan();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,14 +377,26 @@ class Overlay extends BaseObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pan the map so that the overlay is entirely visible in the current viewport
|
||||
* (if necessary).
|
||||
* Pan the map so that the overlay is entirely visisble in the current viewport
|
||||
* (if necessary) using the configured autoPan parameters
|
||||
* @protected
|
||||
*/
|
||||
panIntoView() {
|
||||
performAutoPan() {
|
||||
if (this.autoPan) {
|
||||
this.panIntoView(this.autoPan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pan the map so that the overlay is entirely visible in the current viewport
|
||||
* (if necessary).
|
||||
* @param {PanIntoViewOptions|undefined} panIntoViewOptions Options for the pan action
|
||||
* @api
|
||||
*/
|
||||
panIntoView(panIntoViewOptions) {
|
||||
const map = this.getMap();
|
||||
|
||||
if (!map || !map.getTargetElement()) {
|
||||
if (!map || !map.getTargetElement() || !this.get(Property.POSITION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -393,7 +404,7 @@ class Overlay extends BaseObject {
|
||||
const element = this.getElement();
|
||||
const overlayRect = this.getRect(element, [outerWidth(element), outerHeight(element)]);
|
||||
|
||||
const margin = this.autoPanMargin;
|
||||
const myMargin = (panIntoViewOptions.margin === undefined) ? 20 : panIntoViewOptions.margin;
|
||||
if (!containsExtent(mapRect, overlayRect)) {
|
||||
// the overlay is not completely inside the viewport, so pan the map
|
||||
const offsetLeft = overlayRect[0] - mapRect[0];
|
||||
@@ -404,17 +415,17 @@ class Overlay extends BaseObject {
|
||||
const delta = [0, 0];
|
||||
if (offsetLeft < 0) {
|
||||
// move map to the left
|
||||
delta[0] = offsetLeft - margin;
|
||||
delta[0] = offsetLeft - myMargin;
|
||||
} else if (offsetRight < 0) {
|
||||
// move map to the right
|
||||
delta[0] = Math.abs(offsetRight) + margin;
|
||||
delta[0] = Math.abs(offsetRight) + myMargin;
|
||||
}
|
||||
if (offsetTop < 0) {
|
||||
// move map up
|
||||
delta[1] = offsetTop - margin;
|
||||
delta[1] = offsetTop - myMargin;
|
||||
} else if (offsetBottom < 0) {
|
||||
// move map down
|
||||
delta[1] = Math.abs(offsetBottom) + margin;
|
||||
delta[1] = Math.abs(offsetBottom) + myMargin;
|
||||
}
|
||||
|
||||
if (delta[0] !== 0 || delta[1] !== 0) {
|
||||
@@ -425,10 +436,11 @@ class Overlay extends BaseObject {
|
||||
centerPx[1] + delta[1]
|
||||
];
|
||||
|
||||
const panOptions = panIntoViewOptions.animation || {};
|
||||
map.getView().animateInternal({
|
||||
center: map.getCoordinateFromPixelInternal(newCenterPx),
|
||||
duration: this.autoPanAnimation.duration,
|
||||
easing: this.autoPanAnimation.easing
|
||||
duration: panOptions.duration,
|
||||
easing: panOptions.easing
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -506,63 +518,34 @@ class Overlay extends BaseObject {
|
||||
|
||||
this.setVisible(true);
|
||||
|
||||
let offsetX = offset[0];
|
||||
let offsetY = offset[1];
|
||||
const x = Math.round(pixel[0] + offset[0]) + 'px';
|
||||
const y = Math.round(pixel[1] + offset[1]) + 'px';
|
||||
let posX = '0%';
|
||||
let posY = '0%';
|
||||
if (positioning == OverlayPositioning.BOTTOM_RIGHT ||
|
||||
positioning == OverlayPositioning.CENTER_RIGHT ||
|
||||
positioning == OverlayPositioning.TOP_RIGHT) {
|
||||
if (this.rendered.left_ !== '') {
|
||||
this.rendered.left_ = '';
|
||||
style.left = '';
|
||||
}
|
||||
const right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px';
|
||||
if (this.rendered.right_ != right) {
|
||||
this.rendered.right_ = right;
|
||||
style.right = right;
|
||||
}
|
||||
} else {
|
||||
if (this.rendered.right_ !== '') {
|
||||
this.rendered.right_ = '';
|
||||
style.right = '';
|
||||
}
|
||||
if (positioning == OverlayPositioning.BOTTOM_CENTER ||
|
||||
positioning == OverlayPositioning.CENTER_CENTER ||
|
||||
positioning == OverlayPositioning.TOP_CENTER) {
|
||||
offsetX -= this.element.offsetWidth / 2;
|
||||
}
|
||||
const left = Math.round(pixel[0] + offsetX) + 'px';
|
||||
if (this.rendered.left_ != left) {
|
||||
this.rendered.left_ = left;
|
||||
style.left = left;
|
||||
}
|
||||
posX = '-100%';
|
||||
} else if (positioning == OverlayPositioning.BOTTOM_CENTER ||
|
||||
positioning == OverlayPositioning.CENTER_CENTER ||
|
||||
positioning == OverlayPositioning.TOP_CENTER) {
|
||||
posX = '-50%';
|
||||
}
|
||||
if (positioning == OverlayPositioning.BOTTOM_LEFT ||
|
||||
positioning == OverlayPositioning.BOTTOM_CENTER ||
|
||||
positioning == OverlayPositioning.BOTTOM_RIGHT) {
|
||||
if (this.rendered.top_ !== '') {
|
||||
this.rendered.top_ = '';
|
||||
style.top = '';
|
||||
}
|
||||
const bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px';
|
||||
if (this.rendered.bottom_ != bottom) {
|
||||
this.rendered.bottom_ = bottom;
|
||||
style.bottom = bottom;
|
||||
}
|
||||
} else {
|
||||
if (this.rendered.bottom_ !== '') {
|
||||
this.rendered.bottom_ = '';
|
||||
style.bottom = '';
|
||||
}
|
||||
if (positioning == OverlayPositioning.CENTER_LEFT ||
|
||||
positioning == OverlayPositioning.CENTER_CENTER ||
|
||||
positioning == OverlayPositioning.CENTER_RIGHT) {
|
||||
offsetY -= this.element.offsetHeight / 2;
|
||||
}
|
||||
const top = Math.round(pixel[1] + offsetY) + 'px';
|
||||
if (this.rendered.top_ != top) {
|
||||
this.rendered.top_ = 'top';
|
||||
style.top = top;
|
||||
}
|
||||
posY = '-100%';
|
||||
} else if (positioning == OverlayPositioning.CENTER_LEFT ||
|
||||
positioning == OverlayPositioning.CENTER_CENTER ||
|
||||
positioning == OverlayPositioning.CENTER_RIGHT) {
|
||||
posY = '-50%';
|
||||
}
|
||||
const transform = `translate(${posX}, ${posY}) translate(${x}, ${y})`;
|
||||
if (this.rendered.transform_ != transform) {
|
||||
this.rendered.transform_ = transform;
|
||||
style.transform = transform;
|
||||
// @ts-ignore IE9
|
||||
style.msTransform = transform;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-22
@@ -12,7 +12,7 @@ import MapProperty from './MapProperty.js';
|
||||
import RenderEventType from './render/EventType.js';
|
||||
import BaseObject, {getChangeEventType} from './Object.js';
|
||||
import ObjectEventType from './ObjectEventType.js';
|
||||
import TileQueue from './TileQueue.js';
|
||||
import TileQueue, {getTilePriority} from './TileQueue.js';
|
||||
import View from './View.js';
|
||||
import ViewHint from './ViewHint.js';
|
||||
import {assert} from './asserts.js';
|
||||
@@ -24,7 +24,6 @@ import {TRUE} from './functions.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';
|
||||
import {create as createTransform, apply as applyTransform} from './transform.js';
|
||||
import {toUserCoordinate, fromUserCoordinate} from './proj.js';
|
||||
|
||||
@@ -891,26 +890,7 @@ class PluggableMap extends BaseObject {
|
||||
* @return {number} Tile priority.
|
||||
*/
|
||||
getTilePriority(tile, tileSourceKey, tileCenter, tileResolution) {
|
||||
// Filter out tiles at higher zoom levels than the current zoom level, or that
|
||||
// are outside the visible extent.
|
||||
const frameState = this.frameState_;
|
||||
if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
|
||||
return DROP;
|
||||
}
|
||||
if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
|
||||
return DROP;
|
||||
}
|
||||
// Prioritize the highest zoom level tiles closest to the focus.
|
||||
// Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
|
||||
// Within a zoom level, tiles are prioritized by the distance in pixels between
|
||||
// the center of the tile and the center of the viewport. The factor of 65536
|
||||
// means that the prioritization should behave as desired for tiles up to
|
||||
// 65536 * Math.log(2) = 45426 pixels from the focus.
|
||||
const center = frameState.viewState.center;
|
||||
const deltaX = tileCenter[0] - center[0];
|
||||
const deltaY = tileCenter[1] - center[1];
|
||||
return 65536 * Math.log(tileResolution) +
|
||||
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
|
||||
return getTilePriority(this.frameState_, tile, tileSourceKey, tileCenter, tileResolution);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+32
-1
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import TileState from './TileState.js';
|
||||
import EventType from './events/EventType.js';
|
||||
import PriorityQueue from './structs/PriorityQueue.js';
|
||||
import PriorityQueue, {DROP} from './structs/PriorityQueue.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -119,3 +119,34 @@ class TileQueue extends PriorityQueue {
|
||||
|
||||
|
||||
export default TileQueue;
|
||||
|
||||
|
||||
/**
|
||||
* @param {import('./PluggableMap.js').FrameState} frameState Frame state.
|
||||
* @param {import("./Tile.js").default} tile Tile.
|
||||
* @param {string} tileSourceKey Tile source key.
|
||||
* @param {import("./coordinate.js").Coordinate} tileCenter Tile center.
|
||||
* @param {number} tileResolution Tile resolution.
|
||||
* @return {number} Tile priority.
|
||||
*/
|
||||
export function getTilePriority(frameState, tile, tileSourceKey, tileCenter, tileResolution) {
|
||||
// Filter out tiles at higher zoom levels than the current zoom level, or that
|
||||
// are outside the visible extent.
|
||||
if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
|
||||
return DROP;
|
||||
}
|
||||
if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
|
||||
return DROP;
|
||||
}
|
||||
// Prioritize the highest zoom level tiles closest to the focus.
|
||||
// Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
|
||||
// Within a zoom level, tiles are prioritized by the distance in pixels between
|
||||
// the center of the tile and the center of the viewport. The factor of 65536
|
||||
// means that the prioritization should behave as desired for tiles up to
|
||||
// 65536 * Math.log(2) = 45426 pixels from the focus.
|
||||
const center = frameState.viewState.center;
|
||||
const deltaX = tileCenter[0] - center[0];
|
||||
const deltaY = tileCenter[1] - center[1];
|
||||
return 65536 * Math.log(tileResolution) +
|
||||
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
import {modulo} from './math.js';
|
||||
import {padNumber} from './string.js';
|
||||
import {getWidth} from './extent.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -402,3 +403,23 @@ export function toStringHDMS(coordinate, opt_fractionDigits) {
|
||||
export function toStringXY(coordinate, opt_fractionDigits) {
|
||||
return format(coordinate, '{x}, {y}', opt_fractionDigits);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the provided coordinate in-place to be within the real world
|
||||
* extent. The lower projection extent boundary is inclusive, the upper one
|
||||
* exclusive.
|
||||
*
|
||||
* @param {Coordinate} coordinate Coordinate.
|
||||
* @param {import("./proj/Projection.js").default} projection Projection
|
||||
* @return {Coordinate} The coordinate within the real world extent.
|
||||
*/
|
||||
export function wrapX(coordinate, projection) {
|
||||
const projectionExtent = projection.getExtent();
|
||||
if (projection.canWrapX() && (coordinate[0] < projectionExtent[0] || coordinate[0] >= projectionExtent[2])) {
|
||||
const worldWidth = getWidth(projectionExtent);
|
||||
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
|
||||
coordinate[0] -= (worldsAway * worldWidth);
|
||||
}
|
||||
return coordinate;
|
||||
}
|
||||
|
||||
+48
-34
@@ -4,9 +4,13 @@
|
||||
|
||||
/**
|
||||
* @typedef {Object} FontParameters
|
||||
* @property {Array<string>} families
|
||||
* @property {string} style
|
||||
* @property {string} variant
|
||||
* @property {string} weight
|
||||
* @property {string} size
|
||||
* @property {string} lineHeight
|
||||
* @property {string} family
|
||||
* @property {Array<string>} families
|
||||
*/
|
||||
|
||||
|
||||
@@ -64,42 +68,52 @@ export const CLASS_CONTROL = 'ol-control';
|
||||
*/
|
||||
export const CLASS_COLLAPSED = 'ol-collapsed';
|
||||
|
||||
/**
|
||||
* From http://stackoverflow.com/questions/10135697/regex-to-parse-any-css-font
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const fontRegEx = new RegExp([
|
||||
'^\\s*(?=(?:(?:[-a-z]+\\s*){0,2}(italic|oblique))?)',
|
||||
'(?=(?:(?:[-a-z]+\\s*){0,2}(small-caps))?)',
|
||||
'(?=(?:(?:[-a-z]+\\s*){0,2}(bold(?:er)?|lighter|[1-9]00 ))?)',
|
||||
'(?:(?:normal|\\1|\\2|\\3)\\s*){0,3}((?:xx?-)?',
|
||||
'(?:small|large)|medium|smaller|larger|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx]))',
|
||||
'(?:\\s*\\/\\s*(normal|[\\.\\d]+(?:\\%|in|[cem]m|ex|p[ctx])?))',
|
||||
'?\\s*([-,\\"\\\'\\sa-z]+?)\\s*$'
|
||||
].join(''), 'i');
|
||||
const fontRegExMatchIndex = [
|
||||
'style',
|
||||
'variant',
|
||||
'weight',
|
||||
'size',
|
||||
'lineHeight',
|
||||
'family'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the list of font families from a font spec. Note that this doesn't work
|
||||
* for font families that have commas in them.
|
||||
* @param {string} The CSS font property.
|
||||
* @return {FontParameters} The font families (or null if the input spec is invalid).
|
||||
* @param {string} fontSpec The CSS font property.
|
||||
* @return {FontParameters} The font parameters (or null if the input spec is invalid).
|
||||
*/
|
||||
export const getFontParameters = (function() {
|
||||
/**
|
||||
* @type {CSSStyleDeclaration}
|
||||
*/
|
||||
let style;
|
||||
/**
|
||||
* @type {Object<string, FontParameters>}
|
||||
*/
|
||||
const cache = {};
|
||||
return function(font) {
|
||||
if (!style) {
|
||||
style = document.createElement('div').style;
|
||||
export const getFontParameters = function(fontSpec) {
|
||||
const match = fontSpec.match(fontRegEx);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const style = /** @type {FontParameters} */ ({
|
||||
lineHeight: 'normal',
|
||||
size: '1.2em',
|
||||
style: 'normal',
|
||||
weight: 'normal',
|
||||
variant: 'normal'
|
||||
});
|
||||
for (let i = 0, ii = fontRegExMatchIndex.length; i < ii; ++i) {
|
||||
const value = match[i + 1];
|
||||
if (value !== undefined) {
|
||||
style[fontRegExMatchIndex[i]] = value;
|
||||
}
|
||||
if (!(font in cache)) {
|
||||
style.font = font;
|
||||
const family = style.fontFamily;
|
||||
const fontWeight = style.fontWeight;
|
||||
const fontStyle = style.fontStyle;
|
||||
style.font = '';
|
||||
if (!family) {
|
||||
return null;
|
||||
}
|
||||
const families = family.split(/,\s?/);
|
||||
cache[font] = {
|
||||
families: families,
|
||||
weight: fontWeight,
|
||||
style: fontStyle
|
||||
};
|
||||
}
|
||||
return cache[font];
|
||||
};
|
||||
})();
|
||||
}
|
||||
style.families = style.family.split(/,\s?/);
|
||||
return style;
|
||||
};
|
||||
|
||||
+9
-2
@@ -1,8 +1,11 @@
|
||||
import {WORKER_OFFSCREEN_CANVAS} from './has.js';
|
||||
|
||||
/**
|
||||
* @module ol/dom
|
||||
*/
|
||||
|
||||
|
||||
//FIXME Move this function to the canvas module
|
||||
/**
|
||||
* Create an html canvas element and returns its 2d context.
|
||||
* @param {number=} opt_width Canvas width.
|
||||
@@ -12,14 +15,18 @@
|
||||
*/
|
||||
export function createCanvasContext2D(opt_width, opt_height, opt_canvasPool) {
|
||||
const canvas = opt_canvasPool && opt_canvasPool.length ?
|
||||
opt_canvasPool.shift() : document.createElement('canvas');
|
||||
opt_canvasPool.shift() :
|
||||
WORKER_OFFSCREEN_CANVAS ?
|
||||
new OffscreenCanvas(opt_width || 300, opt_height || 300) :
|
||||
document.createElement('canvas');
|
||||
if (opt_width) {
|
||||
canvas.width = opt_width;
|
||||
}
|
||||
if (opt_height) {
|
||||
canvas.height = opt_height;
|
||||
}
|
||||
return canvas.getContext('2d');
|
||||
//FIXME Allow OffscreenCanvasRenderingContext2D as return type
|
||||
return /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -295,6 +295,18 @@ export function equals(extent1, extent2) {
|
||||
extent1[1] == extent2[1] && extent1[3] == extent2[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if two extents are approximately equivalent.
|
||||
* @param {Extent} extent1 Extent 1.
|
||||
* @param {Extent} extent2 Extent 2.
|
||||
* @param {number} tolerance Tolerance in extent coordinate units.
|
||||
* @return {boolean} The two extents differ by less than the tolerance.
|
||||
*/
|
||||
export function approximatelyEquals(extent1, extent2, tolerance) {
|
||||
return Math.abs(extent1[0] - extent2[0]) < tolerance && Math.abs(extent1[2] - extent2[2]) < tolerance &&
|
||||
Math.abs(extent1[1] - extent2[1]) < tolerance && Math.abs(extent1[3] - extent2[3]) < tolerance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modify an extent to include another extent.
|
||||
@@ -813,3 +825,25 @@ export function applyTransform(extent, transformFn, opt_extent, opt_stops) {
|
||||
}
|
||||
return _boundingExtentXYs(xs, ys, opt_extent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the provided extent in-place to be within the real world
|
||||
* extent.
|
||||
*
|
||||
* @param {Extent} extent Extent.
|
||||
* @param {import("./proj/Projection.js").default} projection Projection
|
||||
* @return {Extent} The extent within the real world extent.
|
||||
*/
|
||||
export function wrapX(extent, projection) {
|
||||
const projectionExtent = projection.getExtent();
|
||||
const center = getCenter(extent);
|
||||
if (projection.canWrapX() && (center[0] < projectionExtent[0] || center[0] >= projectionExtent[2])) {
|
||||
const worldWidth = getWidth(projectionExtent);
|
||||
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
|
||||
const offset = (worldsAway * worldWidth);
|
||||
extent[0] -= offset;
|
||||
extent[2] -= offset;
|
||||
}
|
||||
return extent;
|
||||
}
|
||||
|
||||
+140
-48
@@ -918,11 +918,7 @@ function createNameStyleFunction(foundStyle, name) {
|
||||
|
||||
const nameStyle = new Style({
|
||||
image: imageStyle,
|
||||
text: textStyle,
|
||||
// although nameStyle will be used only for Point geometries
|
||||
// fill and stroke are included to assist writing of MultiGeometry styles
|
||||
fill: foundStyle.getFill(),
|
||||
stroke: foundStyle.getStroke()
|
||||
text: textStyle
|
||||
});
|
||||
return nameStyle;
|
||||
}
|
||||
@@ -953,7 +949,7 @@ function createFeatureStyleFunction(style, styleUrl, defaultStyle, sharedStyles,
|
||||
if (geometry) {
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
multiGeometryPoints = geometry.getGeometriesArray().filter(function(geometry) {
|
||||
multiGeometryPoints = geometry.getGeometriesArrayRecursive().filter(function(geometry) {
|
||||
const type = geometry.getType();
|
||||
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
|
||||
});
|
||||
@@ -1171,6 +1167,7 @@ function readStyleMapValue(node, objectStack) {
|
||||
const ICON_STYLE_PARSERS = makeStructureNS(
|
||||
NAMESPACE_URIS, {
|
||||
'Icon': makeObjectPropertySetter(readIcon),
|
||||
'color': makeObjectPropertySetter(readColor),
|
||||
'heading': makeObjectPropertySetter(readDecimal),
|
||||
'hotSpot': makeObjectPropertySetter(readVec2),
|
||||
'scale': makeObjectPropertySetter(readScale)
|
||||
@@ -1252,6 +1249,9 @@ function iconStyleParser(node, objectStack) {
|
||||
let scale = /** @type {number|undefined} */
|
||||
(object['scale']);
|
||||
|
||||
const color = /** @type {Array<number>|undefined} */
|
||||
(object['color']);
|
||||
|
||||
if (drawIcon) {
|
||||
if (src == DEFAULT_IMAGE_STYLE_SRC) {
|
||||
size = DEFAULT_IMAGE_STYLE_SIZE;
|
||||
@@ -1271,7 +1271,8 @@ function iconStyleParser(node, objectStack) {
|
||||
rotation: rotation,
|
||||
scale: scale,
|
||||
size: size,
|
||||
src: src
|
||||
src: src,
|
||||
color: color
|
||||
});
|
||||
styleObject['imageStyle'] = imageStyle;
|
||||
} else {
|
||||
@@ -1806,7 +1807,7 @@ function readStyle(node, objectStack) {
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
return new GeometryCollection(
|
||||
geometry.getGeometriesArray().filter(function(geometry) {
|
||||
geometry.getGeometriesArrayRecursive().filter(function(geometry) {
|
||||
const type = geometry.getType();
|
||||
return type !== GeometryType.POLYGON && type !== GeometryType.MULTI_POLYGON;
|
||||
})
|
||||
@@ -1827,7 +1828,7 @@ function readStyle(node, objectStack) {
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
return new GeometryCollection(
|
||||
geometry.getGeometriesArray().filter(function(geometry) {
|
||||
geometry.getGeometriesArrayRecursive().filter(function(geometry) {
|
||||
const type = geometry.getType();
|
||||
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
|
||||
})
|
||||
@@ -2447,7 +2448,7 @@ function writeIcon(node, icon, objectStack) {
|
||||
// @ts-ignore
|
||||
const ICON_STYLE_SEQUENCE = makeStructureNS(
|
||||
NAMESPACE_URIS, [
|
||||
'scale', 'heading', 'Icon', 'hotSpot'
|
||||
'scale', 'heading', 'Icon', 'color', 'hotSpot'
|
||||
]);
|
||||
|
||||
|
||||
@@ -2459,6 +2460,7 @@ const ICON_STYLE_SEQUENCE = makeStructureNS(
|
||||
const ICON_STYLE_SERIALIZERS = makeStructureNS(
|
||||
NAMESPACE_URIS, {
|
||||
'Icon': makeChildAppender(writeIcon),
|
||||
'color': makeChildAppender(writeColorTextNode),
|
||||
'heading': makeChildAppender(writeDecimalTextNode),
|
||||
'hotSpot': makeChildAppender(writeVec2),
|
||||
'scale': makeChildAppender(writeScaleTextNode)
|
||||
@@ -2514,6 +2516,11 @@ function writeIconStyle(node, style, objectStack) {
|
||||
properties['heading'] = rotation; // 0-360
|
||||
}
|
||||
|
||||
const color = style.getColor();
|
||||
if (color) {
|
||||
properties['color'] = color;
|
||||
}
|
||||
|
||||
const parentNode = objectStack[objectStack.length - 1].node;
|
||||
const orderedKeys = ICON_STYLE_SEQUENCE[parentNode.namespaceURI];
|
||||
const values = makeSequence(properties, orderedKeys);
|
||||
@@ -2703,20 +2710,35 @@ function writeMultiGeometry(node, geometry, objectStack) {
|
||||
const context = {node: node};
|
||||
const type = geometry.getType();
|
||||
/** @type {Array<import("../geom/Geometry.js").default>} */
|
||||
let geometries;
|
||||
let geometries = [];
|
||||
/** @type {function(*, Array<*>, string=): (Node|undefined)} */
|
||||
let factory;
|
||||
if (type == GeometryType.GEOMETRY_COLLECTION) {
|
||||
geometries = /** @type {GeometryCollection} */ (geometry).getGeometries();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
/** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().forEach(function(geometry) {
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.MULTI_POINT) {
|
||||
geometries = geometries.concat(/** @type {MultiPoint} */ (geometry).getPoints());
|
||||
} else if (type === GeometryType.MULTI_LINE_STRING) {
|
||||
geometries = geometries.concat(/** @type {MultiLineString} */ (geometry).getLineStrings());
|
||||
} else if (type === GeometryType.MULTI_POLYGON) {
|
||||
geometries = geometries.concat(/** @type {MultiPolygon} */ (geometry).getPolygons());
|
||||
} else if (type === GeometryType.POINT
|
||||
|| type === GeometryType.LINE_STRING
|
||||
|| type === GeometryType.POLYGON) {
|
||||
geometries.push(geometry);
|
||||
} else {
|
||||
assert(false, 39); // Unknown geometry type
|
||||
}
|
||||
});
|
||||
factory = GEOMETRY_NODE_FACTORY;
|
||||
} else if (type == GeometryType.MULTI_POINT) {
|
||||
} else if (type === GeometryType.MULTI_POINT) {
|
||||
geometries = /** @type {MultiPoint} */ (geometry).getPoints();
|
||||
factory = POINT_NODE_FACTORY;
|
||||
} else if (type == GeometryType.MULTI_LINE_STRING) {
|
||||
} else if (type === GeometryType.MULTI_LINE_STRING) {
|
||||
geometries =
|
||||
(/** @type {MultiLineString} */ (geometry)).getLineStrings();
|
||||
factory = LINE_STRING_NODE_FACTORY;
|
||||
} else if (type == GeometryType.MULTI_POLYGON) {
|
||||
} else if (type === GeometryType.MULTI_POLYGON) {
|
||||
geometries =
|
||||
(/** @type {MultiPolygon} */ (geometry)).getPolygons();
|
||||
factory = POLYGON_NODE_FACTORY;
|
||||
@@ -2831,13 +2853,61 @@ function writePlacemark(node, feature, objectStack) {
|
||||
// resolution-independent here
|
||||
const styles = styleFunction(feature, 0);
|
||||
if (styles) {
|
||||
const style = Array.isArray(styles) ? styles[0] : styles;
|
||||
if (this.writeStyles_) {
|
||||
properties['Style'] = style;
|
||||
const styleArray = Array.isArray(styles) ? styles : [styles];
|
||||
let pointStyles = styleArray;
|
||||
if (feature.getGeometry()) {
|
||||
pointStyles = styleArray.filter(function(style) {
|
||||
const geometry = style.getGeometryFunction()(feature);
|
||||
if (geometry) {
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
|
||||
const type = geometry.getType();
|
||||
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
|
||||
}).length;
|
||||
}
|
||||
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
|
||||
}
|
||||
});
|
||||
}
|
||||
const textStyle = style.getText();
|
||||
if (textStyle) {
|
||||
properties['name'] = textStyle.getText();
|
||||
if (this.writeStyles_) {
|
||||
let lineStyles = styleArray;
|
||||
let polyStyles = styleArray;
|
||||
if (feature.getGeometry()) {
|
||||
lineStyles = styleArray.filter(function(style) {
|
||||
const geometry = style.getGeometryFunction()(feature);
|
||||
if (geometry) {
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
|
||||
const type = geometry.getType();
|
||||
return type === GeometryType.LINE_STRING || type === GeometryType.MULTI_LINE_STRING;
|
||||
}).length;
|
||||
}
|
||||
return type === GeometryType.LINE_STRING || type === GeometryType.MULTI_LINE_STRING;
|
||||
}
|
||||
});
|
||||
polyStyles = styleArray.filter(function(style) {
|
||||
const geometry = style.getGeometryFunction()(feature);
|
||||
if (geometry) {
|
||||
const type = geometry.getType();
|
||||
if (type === GeometryType.GEOMETRY_COLLECTION) {
|
||||
return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
|
||||
const type = geometry.getType();
|
||||
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
|
||||
}).length;
|
||||
}
|
||||
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
|
||||
}
|
||||
});
|
||||
}
|
||||
properties['Style'] = {pointStyles: pointStyles, lineStyles: lineStyles, polyStyles: polyStyles};
|
||||
}
|
||||
if (pointStyles.length && properties['name'] === undefined) {
|
||||
const textStyle = pointStyles[0].getText();
|
||||
if (textStyle) {
|
||||
properties['name'] = textStyle.getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2913,6 +2983,17 @@ function writePrimitiveGeometry(node, geometry, objectStack) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {Object<string, Array<string>>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const POLY_STYLE_SEQUENCE = makeStructureNS(
|
||||
NAMESPACE_URIS, [
|
||||
'color', 'fill', 'outline'
|
||||
]);
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {Object<string, Object<string, import("../xml.js").Serializer>>}
|
||||
@@ -2972,27 +3053,31 @@ function writePolygon(node, polygon, objectStack) {
|
||||
// @ts-ignore
|
||||
const POLY_STYLE_SERIALIZERS = makeStructureNS(
|
||||
NAMESPACE_URIS, {
|
||||
'color': makeChildAppender(writeColorTextNode)
|
||||
'color': makeChildAppender(writeColorTextNode),
|
||||
'fill': makeChildAppender(writeBooleanTextNode),
|
||||
'outline': makeChildAppender(writeBooleanTextNode)
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* A factory for creating coordinates nodes.
|
||||
* @const
|
||||
* @type {function(*, Array<*>, string=): (Node|undefined)}
|
||||
*/
|
||||
const COLOR_NODE_FACTORY = makeSimpleNodeFactory('color');
|
||||
|
||||
|
||||
/**
|
||||
* @param {Node} node Node.
|
||||
* @param {Fill} style Style.
|
||||
* @param {Style} style Style.
|
||||
* @param {Array<*>} objectStack Object stack.
|
||||
*/
|
||||
function writePolyStyle(node, style, objectStack) {
|
||||
const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node};
|
||||
const fill = style.getFill();
|
||||
const stroke = style.getStroke();
|
||||
const properties = {
|
||||
'color': fill ? fill.getColor() : undefined,
|
||||
'fill': fill ? undefined : false,
|
||||
'outline': stroke ? undefined : false
|
||||
};
|
||||
const parentNode = objectStack[objectStack.length - 1].node;
|
||||
const orderedKeys = POLY_STYLE_SEQUENCE[parentNode.namespaceURI];
|
||||
const values = makeSequence(properties, orderedKeys);
|
||||
pushSerializeAndPop(context, POLY_STYLE_SERIALIZERS,
|
||||
COLOR_NODE_FACTORY, [style.getColor()], objectStack);
|
||||
OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
|
||||
}
|
||||
|
||||
|
||||
@@ -3034,27 +3119,34 @@ const STYLE_SERIALIZERS = makeStructureNS(
|
||||
|
||||
/**
|
||||
* @param {Node} node Node.
|
||||
* @param {Style} style Style.
|
||||
* @param {Object<string, Array<Style>>} styles Styles.
|
||||
* @param {Array<*>} objectStack Object stack.
|
||||
*/
|
||||
function writeStyle(node, style, objectStack) {
|
||||
function writeStyle(node, styles, objectStack) {
|
||||
const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node};
|
||||
const properties = {};
|
||||
const fillStyle = style.getFill();
|
||||
const strokeStyle = style.getStroke();
|
||||
const imageStyle = style.getImage();
|
||||
const textStyle = style.getText();
|
||||
if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') {
|
||||
properties['IconStyle'] = imageStyle;
|
||||
if (styles.pointStyles.length) {
|
||||
const textStyle = styles.pointStyles[0].getText();
|
||||
if (textStyle) {
|
||||
properties['LabelStyle'] = textStyle;
|
||||
}
|
||||
const imageStyle = styles.pointStyles[0].getImage();
|
||||
if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') {
|
||||
properties['IconStyle'] = imageStyle;
|
||||
}
|
||||
}
|
||||
if (textStyle) {
|
||||
properties['LabelStyle'] = textStyle;
|
||||
if (styles.lineStyles.length) {
|
||||
const strokeStyle = styles.lineStyles[0].getStroke();
|
||||
if (strokeStyle) {
|
||||
properties['LineStyle'] = strokeStyle;
|
||||
}
|
||||
}
|
||||
if (strokeStyle) {
|
||||
properties['LineStyle'] = strokeStyle;
|
||||
}
|
||||
if (fillStyle) {
|
||||
properties['PolyStyle'] = fillStyle;
|
||||
if (styles.polyStyles.length) {
|
||||
const strokeStyle = styles.polyStyles[0].getStroke();
|
||||
if (strokeStyle && !properties['LineStyle']) {
|
||||
properties['LineStyle'] = strokeStyle;
|
||||
}
|
||||
properties['PolyStyle'] = styles.polyStyles[0];
|
||||
}
|
||||
const parentNode = objectStack[objectStack.length - 1].node;
|
||||
const orderedKeys = STYLE_SEQUENCE[parentNode.namespaceURI];
|
||||
|
||||
@@ -126,6 +126,23 @@ class GeometryCollection extends Geometry {
|
||||
return this.geometries_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Array<Geometry>} Geometries.
|
||||
*/
|
||||
getGeometriesArrayRecursive() {
|
||||
/** @type {Array<Geometry>} */
|
||||
let geometriesArray = [];
|
||||
const geometries = this.geometries_;
|
||||
for (let i = 0, ii = geometries.length; i < ii; ++i) {
|
||||
if (geometries[i].getType() === this.getType()) {
|
||||
geometriesArray = geometriesArray.concat(/** @type {GeometryCollection} */ (geometries[i]).getGeometriesArrayRecursive());
|
||||
} else {
|
||||
geometriesArray.push(geometries[i]);
|
||||
}
|
||||
}
|
||||
return geometriesArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
+9
-1
@@ -37,7 +37,15 @@ export const MAC = ua.indexOf('macintosh') !== -1;
|
||||
* @type {number}
|
||||
* @api
|
||||
*/
|
||||
export const DEVICE_PIXEL_RATIO = window.devicePixelRatio || 1;
|
||||
export const DEVICE_PIXEL_RATIO = typeof devicePixelRatio !== 'undefined' ? devicePixelRatio : 1;
|
||||
|
||||
/**
|
||||
* The execution context is a worker with OffscreenCanvas available.
|
||||
* @const
|
||||
* @type {boolean}
|
||||
*/
|
||||
export const WORKER_OFFSCREEN_CANVAS = typeof WorkerGlobalScope !== 'undefined' && typeof OffscreenCanvas !== 'undefined' &&
|
||||
self instanceof WorkerGlobalScope; //eslint-disable-line
|
||||
|
||||
/**
|
||||
* Image.prototype.decode() is supported.
|
||||
|
||||
@@ -14,13 +14,14 @@ import {always, primaryAction, altKeyOnly, singleClick} from '../events/conditio
|
||||
import {boundingExtent, buffer as bufferExtent, createOrUpdateFromCoordinate as createExtent} from '../extent.js';
|
||||
import GeometryType from '../geom/GeometryType.js';
|
||||
import Point from '../geom/Point.js';
|
||||
import {fromCircle} from '../geom/Polygon.js';
|
||||
import PointerInteraction from './Pointer.js';
|
||||
import VectorLayer from '../layer/Vector.js';
|
||||
import VectorSource from '../source/Vector.js';
|
||||
import VectorEventType from '../source/VectorEventType.js';
|
||||
import RBush from '../structs/RBush.js';
|
||||
import {createEditingStyle} from '../style/Style.js';
|
||||
import {fromUserExtent, toUserExtent, fromUserCoordinate, toUserCoordinate} from '../proj.js';
|
||||
import {getUserProjection, fromUserExtent, toUserExtent, fromUserCoordinate, toUserCoordinate} from '../proj.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -657,7 +658,14 @@ class Modify extends PointerInteraction {
|
||||
centerSegmentData.featureSegments = featureSegments;
|
||||
circumferenceSegmentData.featureSegments = featureSegments;
|
||||
this.rBush_.insert(createExtent(coordinates), centerSegmentData);
|
||||
this.rBush_.insert(geometry.getExtent(), circumferenceSegmentData);
|
||||
let circleGeometry = /** @type {import("../geom/Geometry.js").default} */ (geometry);
|
||||
const userProjection = getUserProjection();
|
||||
if (userProjection && this.getMap()) {
|
||||
const projection = this.getMap().getView().getProjection();
|
||||
circleGeometry = circleGeometry.clone().transform(userProjection, projection);
|
||||
circleGeometry = fromCircle(/** @type {import("../geom/Circle.js").default} */ (circleGeometry)).transform(projection, userProjection);
|
||||
}
|
||||
this.rBush_.insert(circleGeometry.getExtent(), circumferenceSegmentData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -785,7 +793,16 @@ class Modify extends PointerInteraction {
|
||||
this.changingFeature_ = false;
|
||||
} else { // We're dragging the circle's circumference:
|
||||
this.changingFeature_ = true;
|
||||
geometry.setRadius(coordinateDistance(geometry.getCenter(), vertex));
|
||||
const projection = evt.map.getView().getProjection();
|
||||
let radius = coordinateDistance(fromUserCoordinate(geometry.getCenter(), projection),
|
||||
fromUserCoordinate(vertex, projection));
|
||||
const userProjection = getUserProjection();
|
||||
if (userProjection) {
|
||||
const circleGeometry = geometry.clone().transform(userProjection, projection);
|
||||
circleGeometry.setRadius(radius);
|
||||
radius = circleGeometry.transform(projection, userProjection).getRadius();
|
||||
}
|
||||
geometry.setRadius(radius);
|
||||
this.changingFeature_ = false;
|
||||
}
|
||||
break;
|
||||
@@ -898,7 +915,14 @@ class Modify extends PointerInteraction {
|
||||
circumferenceSegmentData.segment[0] = coordinates;
|
||||
circumferenceSegmentData.segment[1] = coordinates;
|
||||
this.rBush_.update(createExtent(coordinates), centerSegmentData);
|
||||
this.rBush_.update(geometry.getExtent(), circumferenceSegmentData);
|
||||
let circleGeometry = geometry;
|
||||
const userProjection = getUserProjection();
|
||||
if (userProjection) {
|
||||
const projection = evt.map.getView().getProjection();
|
||||
circleGeometry = circleGeometry.clone().transform(userProjection, projection);
|
||||
circleGeometry = fromCircle(circleGeometry).transform(projection, userProjection);
|
||||
}
|
||||
this.rBush_.update(circleGeometry.getExtent(), circumferenceSegmentData);
|
||||
} else {
|
||||
this.rBush_.update(boundingExtent(segmentData.segment), segmentData);
|
||||
}
|
||||
@@ -1249,11 +1273,15 @@ function projectedDistanceToSegmentDataSquared(pointCoordinates, segmentData, pr
|
||||
const geometry = segmentData.geometry;
|
||||
|
||||
if (geometry.getType() === GeometryType.CIRCLE) {
|
||||
const circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry);
|
||||
let circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry);
|
||||
|
||||
if (segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) {
|
||||
const userProjection = getUserProjection();
|
||||
if (userProjection) {
|
||||
circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection));
|
||||
}
|
||||
const distanceToCenterSquared =
|
||||
squaredCoordinateDistance(circleGeometry.getCenter(), pointCoordinates);
|
||||
squaredCoordinateDistance(circleGeometry.getCenter(), fromUserCoordinate(pointCoordinates, projection));
|
||||
const distanceToCircumference =
|
||||
Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius();
|
||||
return distanceToCircumference * distanceToCircumference;
|
||||
@@ -1280,7 +1308,12 @@ function closestOnSegmentData(pointCoordinates, segmentData, projection) {
|
||||
const geometry = segmentData.geometry;
|
||||
|
||||
if (geometry.getType() === GeometryType.CIRCLE && segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) {
|
||||
return geometry.getClosestPoint(pointCoordinates);
|
||||
let circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry);
|
||||
const userProjection = getUserProjection();
|
||||
if (userProjection) {
|
||||
circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection));
|
||||
}
|
||||
return toUserCoordinate(circleGeometry.getClosestPoint(fromUserCoordinate(pointCoordinates, projection)), projection);
|
||||
}
|
||||
const coordinate = fromUserCoordinate(pointCoordinates, projection);
|
||||
tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection);
|
||||
|
||||
@@ -167,6 +167,16 @@ class Select extends Interaction {
|
||||
|
||||
const options = opt_options ? opt_options : {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.boundAddFeature_ = this.addFeature_.bind(this);
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.boundRemoveFeature_ = this.removeFeature_.bind(this);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {import("../events/condition.js").Condition}
|
||||
@@ -249,10 +259,6 @@ class Select extends Interaction {
|
||||
* @type {Object<string, import("../layer/Layer.js").default>}
|
||||
*/
|
||||
this.featureLayerAssociation_ = {};
|
||||
|
||||
const features = this.getFeatures();
|
||||
features.addEventListener(CollectionEventType.ADD, this.addFeature_.bind(this));
|
||||
features.addEventListener(CollectionEventType.REMOVE, this.removeFeature_.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,8 +326,16 @@ class Select extends Interaction {
|
||||
this.features_.forEach(this.restorePreviousStyle_.bind(this));
|
||||
}
|
||||
super.setMap(map);
|
||||
if (map && this.style_) {
|
||||
this.features_.forEach(this.applySelectedStyle_.bind(this));
|
||||
if (map) {
|
||||
this.features_.addEventListener(CollectionEventType.ADD, this.boundAddFeature_);
|
||||
this.features_.addEventListener(CollectionEventType.REMOVE, this.boundRemoveFeature_);
|
||||
|
||||
if (this.style_) {
|
||||
this.features_.forEach(this.applySelectedStyle_.bind(this));
|
||||
}
|
||||
} else {
|
||||
this.features_.removeEventListener(CollectionEventType.ADD, this.boundAddFeature_);
|
||||
this.features_.removeEventListener(CollectionEventType.REMOVE, this.boundRemoveFeature_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import PointerInteraction from './Pointer.js';
|
||||
import {getValues} from '../obj.js';
|
||||
import VectorEventType from '../source/VectorEventType.js';
|
||||
import RBush from '../structs/RBush.js';
|
||||
import {fromUserCoordinate, toUserCoordinate} from '../proj.js';
|
||||
import {getUserProjection, fromUserCoordinate, toUserCoordinate} from '../proj.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -431,8 +431,13 @@ class Snap extends PointerInteraction {
|
||||
} else if (this.edge_) {
|
||||
const isCircle = closestSegmentData.feature.getGeometry().getType() === GeometryType.CIRCLE;
|
||||
if (isCircle) {
|
||||
vertex = closestOnCircle(pixelCoordinate,
|
||||
/** @type {import("../geom/Circle.js").default} */ (closestSegmentData.feature.getGeometry()));
|
||||
let circleGeometry = closestSegmentData.feature.getGeometry();
|
||||
const userProjection = getUserProjection();
|
||||
if (userProjection) {
|
||||
circleGeometry = circleGeometry.clone().transform(userProjection, projection);
|
||||
}
|
||||
vertex = toUserCoordinate(closestOnCircle(projectedCoordinate,
|
||||
/** @type {import("../geom/Circle.js").default} */ (circleGeometry)), projection);
|
||||
} else {
|
||||
tempSegment[0] = fromUserCoordinate(closestSegment[0], projection);
|
||||
tempSegment[1] = fromUserCoordinate(closestSegment[1], projection);
|
||||
@@ -482,7 +487,16 @@ class Snap extends PointerInteraction {
|
||||
* @private
|
||||
*/
|
||||
writeCircleGeometry_(feature, geometry) {
|
||||
const polygon = fromCircle(geometry);
|
||||
const projection = this.getMap().getView().getProjection();
|
||||
let circleGeometry = geometry;
|
||||
const userProjection = getUserProjection();
|
||||
if (userProjection) {
|
||||
circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection));
|
||||
}
|
||||
const polygon = fromCircle(circleGeometry);
|
||||
if (userProjection) {
|
||||
polygon.transform(projection, userProjection);
|
||||
}
|
||||
const coordinates = polygon.getCoordinates()[0];
|
||||
for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) {
|
||||
const segment = coordinates.slice(i, i + 2);
|
||||
|
||||
+144
-77
@@ -17,13 +17,16 @@ import {
|
||||
import {
|
||||
applyTransform,
|
||||
containsCoordinate,
|
||||
containsExtent,
|
||||
equals,
|
||||
approximatelyEquals,
|
||||
getCenter,
|
||||
getHeight,
|
||||
getIntersection,
|
||||
getWidth,
|
||||
intersects,
|
||||
isEmpty
|
||||
isEmpty,
|
||||
wrapX as wrapExtentX
|
||||
} from '../extent.js';
|
||||
import {clamp} from '../math.js';
|
||||
import Style from '../style/Style.js';
|
||||
@@ -478,11 +481,21 @@ class Graticule extends VectorLayer {
|
||||
* @return {Array<import("../extent.js").Extent>} Extents.
|
||||
*/
|
||||
strategyFunction(extent, resolution) {
|
||||
if (this.loadedExtent_ && !equals(this.loadedExtent_, extent)) {
|
||||
// we should not keep track of loaded extents
|
||||
this.getSource().removeLoadedExtent(this.loadedExtent_);
|
||||
// extents may be passed in different worlds, to avoid endless loop we use only one
|
||||
let realWorldExtent = extent.slice();
|
||||
if (this.projection_ && this.getSource().getWrapX()) {
|
||||
wrapExtentX(realWorldExtent, this.projection_);
|
||||
}
|
||||
return [extent];
|
||||
if (this.loadedExtent_) {
|
||||
if (approximatelyEquals(this.loadedExtent_, realWorldExtent, resolution)) {
|
||||
// make sure result is exactly equal to previous extent
|
||||
realWorldExtent = this.loadedExtent_.slice();
|
||||
} else {
|
||||
// we should not keep track of loaded extents
|
||||
this.getSource().removeLoadedExtent(this.loadedExtent_);
|
||||
}
|
||||
}
|
||||
return [realWorldExtent];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -622,9 +635,9 @@ class Graticule extends VectorLayer {
|
||||
drawLabels_(event) {
|
||||
const rotation = event.frameState.viewState.rotation;
|
||||
const extent = event.frameState.extent;
|
||||
let rotationCenter, rotationExtent;
|
||||
const rotationCenter = getCenter(extent);
|
||||
let rotationExtent = extent;
|
||||
if (rotation) {
|
||||
rotationCenter = getCenter(extent);
|
||||
const width = getWidth(extent);
|
||||
const height = getHeight(extent);
|
||||
const cr = Math.abs(Math.cos(rotation));
|
||||
@@ -637,42 +650,60 @@ class Graticule extends VectorLayer {
|
||||
];
|
||||
}
|
||||
|
||||
const vectorContext = getVectorContext(event);
|
||||
let poolIndex = this.meridians_.length + this.parallels_.length;
|
||||
let feature, index, l, textPoint;
|
||||
|
||||
if (this.meridiansLabels_) {
|
||||
for (index = 0, l = this.meridiansLabels_.length; index < l; ++index) {
|
||||
const lineString = this.meridians_[index];
|
||||
if (!rotation) {
|
||||
textPoint = this.getMeridianPoint_(lineString, extent, index);
|
||||
} else {
|
||||
const clone = lineString.clone();
|
||||
clone.rotate(-rotation, rotationCenter);
|
||||
textPoint = this.getMeridianPoint_(clone, rotationExtent, index);
|
||||
textPoint.rotate(rotation, rotationCenter);
|
||||
}
|
||||
feature = this.featurePool_[poolIndex++];
|
||||
feature.setGeometry(textPoint);
|
||||
feature.set('graticule_label', this.meridiansLabels_[index].text);
|
||||
vectorContext.drawFeature(feature, this.lonLabelStyle_(feature));
|
||||
}
|
||||
let startWorld = 0;
|
||||
let endWorld = 0;
|
||||
let labelsAtStart = this.latLabelPosition_ < 0.5;
|
||||
const projectionExtent = this.projection_.getExtent();
|
||||
const worldWidth = getWidth(projectionExtent);
|
||||
if (this.getSource().getWrapX() && this.projection_.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
||||
startWorld = Math.floor((extent[0] - projectionExtent[0]) / worldWidth);
|
||||
endWorld = Math.ceil((extent[2] - projectionExtent[2]) / worldWidth);
|
||||
const inverted = Math.abs(rotation) > Math.PI / 2;
|
||||
labelsAtStart = labelsAtStart !== inverted;
|
||||
}
|
||||
if (this.parallelsLabels_) {
|
||||
for (index = 0, l = this.parallels_.length; index < l; ++index) {
|
||||
const lineString = this.parallels_[index];
|
||||
if (!rotation) {
|
||||
textPoint = this.getParallelPoint_(lineString, extent, index);
|
||||
} else {
|
||||
const clone = lineString.clone();
|
||||
clone.rotate(-rotation, rotationCenter);
|
||||
textPoint = this.getParallelPoint_(clone, rotationExtent, index);
|
||||
textPoint.rotate(rotation, rotationCenter);
|
||||
const vectorContext = getVectorContext(event);
|
||||
|
||||
for (let world = startWorld; world <= endWorld; ++world) {
|
||||
let poolIndex = this.meridians_.length + this.parallels_.length;
|
||||
let feature, index, l, textPoint;
|
||||
|
||||
if (this.meridiansLabels_) {
|
||||
for (index = 0, l = this.meridiansLabels_.length; index < l; ++index) {
|
||||
const lineString = this.meridians_[index];
|
||||
if (!rotation && world === 0) {
|
||||
textPoint = this.getMeridianPoint_(lineString, extent, index);
|
||||
} else {
|
||||
const clone = lineString.clone();
|
||||
clone.translate(world * worldWidth, 0);
|
||||
clone.rotate(-rotation, rotationCenter);
|
||||
textPoint = this.getMeridianPoint_(clone, rotationExtent, index);
|
||||
textPoint.rotate(rotation, rotationCenter);
|
||||
}
|
||||
feature = this.featurePool_[poolIndex++];
|
||||
feature.setGeometry(textPoint);
|
||||
feature.set('graticule_label', this.meridiansLabels_[index].text);
|
||||
vectorContext.drawFeature(feature, this.lonLabelStyle_(feature));
|
||||
}
|
||||
}
|
||||
if (this.parallelsLabels_) {
|
||||
if (world === startWorld && labelsAtStart || world === endWorld && !labelsAtStart) {
|
||||
for (index = 0, l = this.parallels_.length; index < l; ++index) {
|
||||
const lineString = this.parallels_[index];
|
||||
if (!rotation && world === 0) {
|
||||
textPoint = this.getParallelPoint_(lineString, extent, index);
|
||||
} else {
|
||||
const clone = lineString.clone();
|
||||
clone.translate(world * worldWidth, 0);
|
||||
clone.rotate(-rotation, rotationCenter);
|
||||
textPoint = this.getParallelPoint_(clone, rotationExtent, index);
|
||||
textPoint.rotate(rotation, rotationCenter);
|
||||
}
|
||||
feature = this.featurePool_[poolIndex++];
|
||||
feature.setGeometry(textPoint);
|
||||
feature.set('graticule_label', this.parallelsLabels_[index].text);
|
||||
vectorContext.drawFeature(feature, this.latLabelStyle_(feature));
|
||||
}
|
||||
}
|
||||
feature = this.featurePool_[poolIndex++];
|
||||
feature.setGeometry(textPoint);
|
||||
feature.set('graticule_label', this.parallelsLabels_[index].text);
|
||||
vectorContext.drawFeature(feature, this.latLabelStyle_(feature));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -698,6 +729,18 @@ class Graticule extends VectorLayer {
|
||||
return;
|
||||
}
|
||||
|
||||
let wrapX = false;
|
||||
const projectionExtent = this.projection_.getExtent();
|
||||
const worldWidth = getWidth(projectionExtent);
|
||||
if (this.getSource().getWrapX() && this.projection_.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
||||
if (getWidth(extent) >= worldWidth) {
|
||||
extent[0] = projectionExtent[0];
|
||||
extent[2] = projectionExtent[2];
|
||||
} else {
|
||||
wrapX = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Constrain the center to fit into the extent available to the graticule
|
||||
|
||||
const validCenterP = [
|
||||
@@ -721,44 +764,56 @@ class Graticule extends VectorLayer {
|
||||
|
||||
// Limit the extent to fit into the extent available to the graticule
|
||||
|
||||
const validExtentP = [
|
||||
clamp(extent[0], this.minX_, this.maxX_),
|
||||
clamp(extent[1], this.minY_, this.maxY_),
|
||||
clamp(extent[2], this.minX_, this.maxX_),
|
||||
clamp(extent[3], this.minY_, this.maxY_)
|
||||
];
|
||||
let validExtentP = extent;
|
||||
if (!wrapX) {
|
||||
validExtentP = [
|
||||
clamp(extent[0], this.minX_, this.maxX_),
|
||||
clamp(extent[1], this.minY_, this.maxY_),
|
||||
clamp(extent[2], this.minX_, this.maxX_),
|
||||
clamp(extent[3], this.minY_, this.maxY_)
|
||||
];
|
||||
}
|
||||
|
||||
// Transform the extent to get the lon lat ranges for the edges of the extent
|
||||
|
||||
const validExtent = applyTransform(validExtentP, this.toLonLatTransform_, undefined, 8);
|
||||
|
||||
// Check if extremities of the world extent lie inside the extent
|
||||
// (for example the pole in a polar projection)
|
||||
// and extend the extent as appropriate
|
||||
let maxLat = validExtent[3];
|
||||
let maxLon = validExtent[2];
|
||||
let minLat = validExtent[1];
|
||||
let minLon = validExtent[0];
|
||||
|
||||
if (containsCoordinate(validExtentP, this.bottomLeft_)) {
|
||||
validExtent[0] = this.minLon_;
|
||||
validExtent[1] = this.minLat_;
|
||||
}
|
||||
if (containsCoordinate(validExtentP, this.bottomRight_)) {
|
||||
validExtent[2] = this.maxLon_;
|
||||
validExtent[1] = this.minLat_;
|
||||
}
|
||||
if (containsCoordinate(validExtentP, this.topLeft_)) {
|
||||
validExtent[0] = this.minLon_;
|
||||
validExtent[3] = this.maxLat_;
|
||||
}
|
||||
if (containsCoordinate(validExtentP, this.topRight_)) {
|
||||
validExtent[2] = this.maxLon_;
|
||||
validExtent[3] = this.maxLat_;
|
||||
}
|
||||
if (!wrapX) {
|
||||
|
||||
// The transformed center may also extend the lon lat ranges used for rendering
|
||||
// Check if extremities of the world extent lie inside the extent
|
||||
// (for example the pole in a polar projection)
|
||||
// and extend the extent as appropriate
|
||||
|
||||
const maxLat = clamp(validExtent[3], centerLat, this.maxLat_);
|
||||
const maxLon = clamp(validExtent[2], centerLon, this.maxLon_);
|
||||
const minLat = clamp(validExtent[1], this.minLat_, centerLat);
|
||||
const minLon = clamp(validExtent[0], this.minLon_, centerLon);
|
||||
if (containsCoordinate(validExtentP, this.bottomLeft_)) {
|
||||
minLon = this.minLon_;
|
||||
minLat = this.minLat_;
|
||||
}
|
||||
if (containsCoordinate(validExtentP, this.bottomRight_)) {
|
||||
maxLon = this.maxLon_;
|
||||
minLat = this.minLat_;
|
||||
}
|
||||
if (containsCoordinate(validExtentP, this.topLeft_)) {
|
||||
minLon = this.minLon_;
|
||||
maxLat = this.maxLat_;
|
||||
}
|
||||
if (containsCoordinate(validExtentP, this.topRight_)) {
|
||||
maxLon = this.maxLon_;
|
||||
maxLat = this.maxLat_;
|
||||
}
|
||||
|
||||
// The transformed center may also extend the lon lat ranges used for rendering
|
||||
|
||||
maxLat = clamp(maxLat, centerLat, this.maxLat_);
|
||||
maxLon = clamp(maxLon, centerLon, this.maxLon_);
|
||||
minLat = clamp(minLat, this.minLat_, centerLat);
|
||||
minLon = clamp(minLon, this.minLon_, centerLon);
|
||||
|
||||
}
|
||||
|
||||
// Create meridians
|
||||
|
||||
@@ -768,17 +823,29 @@ class Graticule extends VectorLayer {
|
||||
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);
|
||||
|
||||
cnt = 0;
|
||||
while (lon != this.minLon_ && cnt++ < maxLines) {
|
||||
lon = Math.max(lon - interval, this.minLon_);
|
||||
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
|
||||
if (wrapX) {
|
||||
while ((lon -= interval) >= minLon && cnt++ < maxLines) {
|
||||
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
|
||||
}
|
||||
} else {
|
||||
while (lon != this.minLon_ && cnt++ < maxLines) {
|
||||
lon = Math.max(lon - interval, this.minLon_);
|
||||
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
|
||||
}
|
||||
}
|
||||
|
||||
lon = clamp(centerLon, this.minLon_, this.maxLon_);
|
||||
|
||||
cnt = 0;
|
||||
while (lon != this.maxLon_ && cnt++ < maxLines) {
|
||||
lon = Math.min(lon + interval, this.maxLon_);
|
||||
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
|
||||
if (wrapX) {
|
||||
while ((lon += interval) <= maxLon && cnt++ < maxLines) {
|
||||
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
|
||||
}
|
||||
} else {
|
||||
while (lon != this.maxLon_ && cnt++ < maxLines) {
|
||||
lon = Math.min(lon + interval, this.maxLon_);
|
||||
idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
|
||||
}
|
||||
}
|
||||
|
||||
this.meridians_.length = idx;
|
||||
|
||||
+57
-19
@@ -6,6 +6,8 @@ import {createCanvasContext2D} from '../dom.js';
|
||||
import {clear} from '../obj.js';
|
||||
import BaseObject from '../Object.js';
|
||||
import EventTarget from '../events/Target.js';
|
||||
import {WORKER_OFFSCREEN_CANVAS} from '../has.js';
|
||||
import {toString} from '../transform.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -297,34 +299,40 @@ export const measureTextHeight = (function() {
|
||||
*/
|
||||
let div;
|
||||
const heights = textHeights;
|
||||
return function(font) {
|
||||
let height = heights[font];
|
||||
return function(fontSpec) {
|
||||
let height = heights[fontSpec];
|
||||
if (height == undefined) {
|
||||
if (!div) {
|
||||
div = document.createElement('div');
|
||||
div.innerHTML = 'M';
|
||||
div.style.margin = '0 !important';
|
||||
div.style.padding = '0 !important';
|
||||
div.style.position = 'absolute !important';
|
||||
div.style.left = '-99999px !important';
|
||||
if (WORKER_OFFSCREEN_CANVAS) {
|
||||
const font = getFontParameters(fontSpec);
|
||||
const metrics = measureText(fontSpec, 'Žg');
|
||||
const lineHeight = isNaN(Number(font.lineHeight)) ? 1.2 : Number(font.lineHeight);
|
||||
textHeights[fontSpec] = lineHeight * (metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent);
|
||||
} else {
|
||||
if (!div) {
|
||||
div = document.createElement('div');
|
||||
div.innerHTML = 'M';
|
||||
div.style.margin = '0 !important';
|
||||
div.style.padding = '0 !important';
|
||||
div.style.position = 'absolute !important';
|
||||
div.style.left = '-99999px !important';
|
||||
}
|
||||
div.style.font = fontSpec;
|
||||
document.body.appendChild(div);
|
||||
height = div.offsetHeight;
|
||||
heights[fontSpec] = height;
|
||||
document.body.removeChild(div);
|
||||
}
|
||||
div.style.font = font;
|
||||
document.body.appendChild(div);
|
||||
height = div.offsetHeight;
|
||||
heights[font] = height;
|
||||
document.body.removeChild(div);
|
||||
}
|
||||
return height;
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} font Font.
|
||||
* @param {string} text Text.
|
||||
* @return {number} Width.
|
||||
* @return {TextMetrics} Text metrics.
|
||||
*/
|
||||
export function measureTextWidth(font, text) {
|
||||
function measureText(font, text) {
|
||||
if (!measureContext) {
|
||||
measureContext = createCanvasContext2D(1, 1);
|
||||
}
|
||||
@@ -332,9 +340,17 @@ export function measureTextWidth(font, text) {
|
||||
measureContext.font = font;
|
||||
measureFont = measureContext.font;
|
||||
}
|
||||
return measureContext.measureText(text).width;
|
||||
return measureContext.measureText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} font Font.
|
||||
* @param {string} text Text.
|
||||
* @return {number} Width.
|
||||
*/
|
||||
export function measureTextWidth(font, text) {
|
||||
return measureText(font, text).width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure text width using a cache.
|
||||
@@ -432,9 +448,31 @@ function executeLabelInstructions(label, context) {
|
||||
const contextInstructions = label.contextInstructions;
|
||||
for (let i = 0, ii = contextInstructions.length; i < ii; i += 2) {
|
||||
if (Array.isArray(contextInstructions[i + 1])) {
|
||||
CanvasRenderingContext2D.prototype[contextInstructions[i]].apply(context, contextInstructions[i + 1]);
|
||||
context[contextInstructions[i]].apply(context, contextInstructions[i + 1]);
|
||||
} else {
|
||||
context[contextInstructions[i]] = contextInstructions[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {HTMLCanvasElement}
|
||||
* @private
|
||||
*/
|
||||
let createTransformStringCanvas = null;
|
||||
|
||||
/**
|
||||
* @param {import("../transform.js").Transform} transform Transform.
|
||||
* @return {string} CSS transform.
|
||||
*/
|
||||
export function createTransformString(transform) {
|
||||
if (WORKER_OFFSCREEN_CANVAS) {
|
||||
return toString(transform);
|
||||
} else {
|
||||
if (!createTransformStringCanvas) {
|
||||
createTransformStringCanvas = createCanvasContext2D(1, 1).canvas;
|
||||
}
|
||||
createTransformStringCanvas.style.transform = toString(transform);
|
||||
return createTransformStringCanvas.style.transform;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '../../transform.js';
|
||||
import {defaultTextAlign, measureTextHeight, measureAndCacheTextWidth, measureTextWidths} from '../canvas.js';
|
||||
import RBush from 'rbush/rbush.js';
|
||||
import {WORKER_OFFSCREEN_CANVAS} from '../../has.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -204,7 +205,9 @@ class Executor {
|
||||
contextInstructions.push('lineCap', strokeState.lineCap);
|
||||
contextInstructions.push('lineJoin', strokeState.lineJoin);
|
||||
contextInstructions.push('miterLimit', strokeState.miterLimit);
|
||||
if (CanvasRenderingContext2D.prototype.setLineDash) {
|
||||
// eslint-disable-next-line
|
||||
const Context = WORKER_OFFSCREEN_CANVAS ? OffscreenCanvasRenderingContext2D : CanvasRenderingContext2D;
|
||||
if (Context.prototype.setLineDash) {
|
||||
contextInstructions.push('setLineDash', [strokeState.lineDash]);
|
||||
contextInstructions.push('lineDashOffset', strokeState.lineDashOffset);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ export function createHitDetectionImageData(size, transforms, features, styleFun
|
||||
const image = originalStyle.getImage();
|
||||
if (image) {
|
||||
const imgSize = image.getImageSize();
|
||||
if (!imgSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = imgSize[0];
|
||||
canvas.height = imgSize[1];
|
||||
|
||||
+4
-10
@@ -9,6 +9,7 @@ import {inView} from '../layer/Layer.js';
|
||||
import {shared as iconImageCache} from '../style/IconImageCache.js';
|
||||
import {compose as composeTransform, makeInverse} from '../transform.js';
|
||||
import {renderDeclutterItems} from '../render.js';
|
||||
import {wrapX} from '../coordinate.js';
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
@@ -102,19 +103,12 @@ class MapRenderer extends Disposable {
|
||||
|
||||
const projection = viewState.projection;
|
||||
|
||||
let translatedCoordinate = coordinate;
|
||||
const translatedCoordinate = wrapX(coordinate.slice(), projection);
|
||||
const offsets = [[0, 0]];
|
||||
if (projection.canWrapX()) {
|
||||
if (projection.canWrapX() && checkWrapped) {
|
||||
const projectionExtent = projection.getExtent();
|
||||
const worldWidth = getWidth(projectionExtent);
|
||||
const x = coordinate[0];
|
||||
if (x < projectionExtent[0] || x > projectionExtent[2]) {
|
||||
const worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
|
||||
translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
|
||||
}
|
||||
if (checkWrapped) {
|
||||
offsets.push([-worldWidth, 0], [worldWidth, 0]);
|
||||
}
|
||||
offsets.push([-worldWidth, 0], [worldWidth, 0]);
|
||||
}
|
||||
|
||||
const layerStates = frameState.layerStatesArray;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {fromUserExtent} from '../../proj.js';
|
||||
import {getIntersection, isEmpty} from '../../extent.js';
|
||||
import CanvasLayerRenderer from './Layer.js';
|
||||
import {compose as composeTransform, makeInverse} from '../../transform.js';
|
||||
import {createTransformString} from '../../render/canvas.js';
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
@@ -55,16 +56,20 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
||||
}
|
||||
|
||||
if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) {
|
||||
let projection = viewState.projection;
|
||||
if (!ENABLE_RASTER_REPROJECTION) {
|
||||
const sourceProjection = imageSource.getProjection();
|
||||
if (sourceProjection) {
|
||||
projection = sourceProjection;
|
||||
if (imageSource) {
|
||||
let projection = viewState.projection;
|
||||
if (!ENABLE_RASTER_REPROJECTION) {
|
||||
const sourceProjection = imageSource.getProjection();
|
||||
if (sourceProjection) {
|
||||
projection = sourceProjection;
|
||||
}
|
||||
}
|
||||
}
|
||||
const image = imageSource.getImage(renderedExtent, viewResolution, pixelRatio, projection);
|
||||
if (image && this.loadImage(image)) {
|
||||
this.image_ = image;
|
||||
const image = imageSource.getImage(renderedExtent, viewResolution, pixelRatio, projection);
|
||||
if (image && this.loadImage(image)) {
|
||||
this.image_ = image;
|
||||
}
|
||||
} else {
|
||||
this.image_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +110,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
||||
);
|
||||
makeInverse(this.inversePixelTransform, this.pixelTransform);
|
||||
|
||||
const canvasTransform = this.createTransformString(this.pixelTransform);
|
||||
const canvasTransform = createTransformString(this.pixelTransform);
|
||||
|
||||
this.useContainer(target, canvasTransform, layerState.opacity);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import RenderEvent from '../../render/Event.js';
|
||||
import RenderEventType from '../../render/EventType.js';
|
||||
import {rotateAtOffset} from '../../render/canvas.js';
|
||||
import LayerRenderer from '../Layer.js';
|
||||
import {create as createTransform, apply as applyTransform, compose as composeTransform, toString} from '../../transform.js';
|
||||
import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js';
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
@@ -69,12 +69,6 @@ class CanvasLayerRenderer extends LayerRenderer {
|
||||
*/
|
||||
this.containerReused = false;
|
||||
|
||||
/**
|
||||
* @type {HTMLCanvasElement}
|
||||
* @private
|
||||
*/
|
||||
this.createTransformStringCanvas_ = createCanvasContext2D(1, 1).canvas;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,15 +263,7 @@ class CanvasLayerRenderer extends LayerRenderer {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../transform.js").Transform} transform Transform.
|
||||
* @return {string} CSS transform.
|
||||
*/
|
||||
createTransformString(transform) {
|
||||
this.createTransformStringCanvas_.style.transform = toString(transform);
|
||||
return this.createTransformStringCanvas_.style.transform;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CanvasLayerRenderer;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {createEmpty, equals, getIntersection, getTopLeft} from '../../extent.js'
|
||||
import CanvasLayerRenderer from './Layer.js';
|
||||
import {apply as applyTransform, compose as composeTransform, makeInverse} from '../../transform.js';
|
||||
import {numberSafeCompareFunction} from '../../array.js';
|
||||
import {createTransformString} from '../../render/canvas.js';
|
||||
import {assign} from '../../obj.js';
|
||||
|
||||
/**
|
||||
@@ -244,7 +245,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
-width / 2, -height / 2
|
||||
);
|
||||
|
||||
const canvasTransform = this.createTransformString(this.pixelTransform);
|
||||
const canvasTransform = createTransformString(this.pixelTransform);
|
||||
|
||||
this.useContainer(target, canvasTransform, layerState.opacity);
|
||||
const context = this.context;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
*/
|
||||
import {getUid} from '../../util.js';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import {buffer, createEmpty, containsExtent, getWidth, intersects as intersectsExtent} from '../../extent.js';
|
||||
import {buffer, createEmpty, containsExtent, getWidth, intersects as intersectsExtent, wrapX as wrapExtentX} from '../../extent.js';
|
||||
import {wrapX as wrapCoordinateX} from '../../coordinate.js';
|
||||
import {fromUserExtent, toUserExtent, getUserProjection, getTransformFromProjections} from '../../proj.js';
|
||||
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
||||
import ExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||
@@ -361,9 +362,10 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
const center = viewState.center.slice();
|
||||
const extent = buffer(frameStateExtent,
|
||||
vectorLayerRenderBuffer * resolution);
|
||||
const projectionExtent = viewState.projection.getExtent();
|
||||
const loadExtents = [extent.slice()];
|
||||
const projectionExtent = projection.getExtent();
|
||||
|
||||
if (vectorSource.getWrapX() && viewState.projection.canWrapX() &&
|
||||
if (vectorSource.getWrapX() && projection.canWrapX() &&
|
||||
!containsExtent(projectionExtent, frameState.extent)) {
|
||||
// For the replay group, we need an extent that intersects the real world
|
||||
// (-180° to +180°). To support geometries in a coordinate range from -540°
|
||||
@@ -374,8 +376,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
const gutter = Math.max(getWidth(extent) / 2, worldWidth);
|
||||
extent[0] = projectionExtent[0] - gutter;
|
||||
extent[2] = projectionExtent[2] + gutter;
|
||||
const worldsAway = Math.floor((center[0] - projectionExtent[0]) / worldWidth);
|
||||
center[0] -= (worldsAway * worldWidth);
|
||||
wrapCoordinateX(center, projection);
|
||||
const loadExtent = wrapExtentX(loadExtents[0], projection);
|
||||
// If the extent crosses the date line, we load data for both edges of the worlds
|
||||
if (loadExtent[0] < projectionExtent[0] && loadExtent[2] < projectionExtent[2]) {
|
||||
loadExtents.push([loadExtent[0] + worldWidth, loadExtent[1], loadExtent[2] + worldWidth, loadExtent[3]]);
|
||||
} else if (loadExtent[0] > projectionExtent[0] && loadExtent[2] > projectionExtent[2]) {
|
||||
loadExtents.push([loadExtent[0] - worldWidth, loadExtent[1], loadExtent[2] - worldWidth, loadExtent[3]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.dirty_ &&
|
||||
@@ -398,10 +406,14 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer {
|
||||
const userProjection = getUserProjection();
|
||||
let userTransform;
|
||||
if (userProjection) {
|
||||
vectorSource.loadFeatures(toUserExtent(extent, projection), resolution, userProjection);
|
||||
for (let i = 0, ii = loadExtents.length; i < ii; ++i) {
|
||||
vectorSource.loadFeatures(toUserExtent(loadExtents[i], projection), resolution, userProjection);
|
||||
}
|
||||
userTransform = getTransformFromProjections(userProjection, projection);
|
||||
} else {
|
||||
vectorSource.loadFeatures(extent, resolution, projection);
|
||||
for (let i = 0, ii = loadExtents.length; i < ii; ++i) {
|
||||
vectorSource.loadFeatures(loadExtents[i], resolution, projection);
|
||||
}
|
||||
}
|
||||
|
||||
const squaredTolerance = getSquaredRenderTolerance(resolution, pixelRatio);
|
||||
|
||||
@@ -6,7 +6,7 @@ import TileState from '../../TileState.js';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import {listen, unlistenByKey} from '../../events.js';
|
||||
import EventType from '../../events/EventType.js';
|
||||
import {buffer, containsCoordinate, equals, getIntersection, intersects, containsExtent, getWidth, getTopLeft} from '../../extent.js';
|
||||
import {buffer, containsCoordinate, equals, getIntersection, intersects, containsExtent, getTopLeft} from '../../extent.js';
|
||||
import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
|
||||
import ReplayType from '../../render/canvas/BuilderType.js';
|
||||
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||
import {clear} from '../../obj.js';
|
||||
import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdetect.js';
|
||||
import {wrapX} from '../../coordinate.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -353,9 +354,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
if (tile.getState() === TileState.LOADED && tile.hifi) {
|
||||
const extent = tileGrid.getTileCoordExtent(tile.tileCoord);
|
||||
if (source.getWrapX() && projection.canWrapX() && !containsExtent(projectionExtent, extent)) {
|
||||
const worldWidth = getWidth(projectionExtent);
|
||||
const worldsAway = Math.floor((coordinate[0] - projectionExtent[0]) / worldWidth);
|
||||
coordinate[0] -= (worldsAway * worldWidth);
|
||||
wrapX(coordinate, projection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class ReprojImage extends ImageBase {
|
||||
|
||||
const triangulation = new Triangulation(
|
||||
sourceProj, targetProj, limitedTargetExtent, maxSourceExtent,
|
||||
sourceResolution * errorThresholdInPixels);
|
||||
sourceResolution * errorThresholdInPixels, targetResolution);
|
||||
|
||||
const sourceExtent = triangulation.calculateSourceExtent();
|
||||
const sourceImage = getImageFunction(sourceExtent, sourceResolution, pixelRatio);
|
||||
|
||||
@@ -168,7 +168,7 @@ class ReprojTile extends Tile {
|
||||
*/
|
||||
this.triangulation_ = new Triangulation(
|
||||
sourceProj, targetProj, limitedTargetExtent, maxSourceExtent,
|
||||
sourceResolution * errorThresholdInPixels);
|
||||
sourceResolution * errorThresholdInPixels, targetResolution);
|
||||
|
||||
if (this.triangulation_.getTriangles().length === 0) {
|
||||
// no valid triangles -> EMPTY
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @module ol/reproj/Triangulation
|
||||
*/
|
||||
import {boundingExtent, createEmpty, extendCoordinate, getBottomLeft, getBottomRight,
|
||||
import {boundingExtent, createEmpty, extendCoordinate, getArea, getBottomLeft, getBottomRight,
|
||||
getTopLeft, getTopRight, getWidth, intersects} from '../extent.js';
|
||||
import {modulo} from '../math.js';
|
||||
import {getTransform} from '../proj.js';
|
||||
@@ -49,8 +49,9 @@ class Triangulation {
|
||||
* @param {import("../extent.js").Extent} targetExtent Target extent to triangulate.
|
||||
* @param {import("../extent.js").Extent} maxSourceExtent Maximal source extent that can be used.
|
||||
* @param {number} errorThreshold Acceptable error (in source units).
|
||||
* @param {?number} opt_destinationResolution The (optional) resolution of the destination.
|
||||
*/
|
||||
constructor(sourceProj, targetProj, targetExtent, maxSourceExtent, errorThreshold) {
|
||||
constructor(sourceProj, targetProj, targetExtent, maxSourceExtent, errorThreshold, opt_destinationResolution) {
|
||||
|
||||
/**
|
||||
* @type {import("../proj/Projection.js").default}
|
||||
@@ -138,11 +139,26 @@ class Triangulation {
|
||||
const sourceBottomRight = this.transformInv_(destinationBottomRight);
|
||||
const sourceBottomLeft = this.transformInv_(destinationBottomLeft);
|
||||
|
||||
/*
|
||||
* The maxSubdivision controls how many splittings of the target area can
|
||||
* be done. The idea here is to do a linear mapping of the target areas
|
||||
* but the actual overal reprojection (can be) extremely non-linear. The
|
||||
* default value of MAX_SUBDIVISION was chosen based on mapping a 256x256
|
||||
* tile size. However this function is also called to remap canvas rendered
|
||||
* layers which can be much larger. This calculation increases the maxSubdivision
|
||||
* value by the right factor so that each 256x256 pixel area has
|
||||
* MAX_SUBDIVISION divisions.
|
||||
*/
|
||||
const maxSubdivision = MAX_SUBDIVISION + (opt_destinationResolution ?
|
||||
Math.max(0, Math.ceil(Math.log2(getArea(targetExtent) /
|
||||
(opt_destinationResolution * opt_destinationResolution * 256 * 256))))
|
||||
: 0);
|
||||
|
||||
this.addQuad_(
|
||||
destinationTopLeft, destinationTopRight,
|
||||
destinationBottomRight, destinationBottomLeft,
|
||||
sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
|
||||
MAX_SUBDIVISION);
|
||||
maxSubdivision);
|
||||
|
||||
if (this.wrapsXInSource_) {
|
||||
let leftBound = Infinity;
|
||||
|
||||
@@ -15,7 +15,7 @@ import Source from './Source.js';
|
||||
/**
|
||||
* @enum {string}
|
||||
*/
|
||||
const ImageSourceEventType = {
|
||||
export const ImageSourceEventType = {
|
||||
|
||||
/**
|
||||
* Triggered when an image starts loading.
|
||||
|
||||
@@ -190,7 +190,7 @@ class VectorSource extends Source {
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;
|
||||
this.overlaps_ = options.overlaps === undefined ? true : options.overlaps;
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
||||
+20
-18
@@ -4,9 +4,8 @@
|
||||
|
||||
import {expandUrl, createFromTileUrlFunctions, nullTileUrlFunction} from '../tileurlfunction.js';
|
||||
import {find, findIndex, includes} from '../array.js';
|
||||
import {containsExtent} from '../extent.js';
|
||||
import {assign} from '../obj.js';
|
||||
import {get as getProjection, equivalent, transformExtent} from '../proj.js';
|
||||
import {get as getProjection, equivalent} from '../proj.js';
|
||||
import TileImage from './TileImage.js';
|
||||
import WMTSRequestEncoding from './WMTSRequestEncoding.js';
|
||||
import {createFromCapabilitiesMatrixSet} from '../tilegrid/WMTS.js';
|
||||
@@ -379,22 +378,25 @@ export function optionsFromCapabilities(wmtsCap, config) {
|
||||
}
|
||||
}
|
||||
|
||||
const wgs84BoundingBox = l['WGS84BoundingBox'];
|
||||
let extent, wrapX;
|
||||
if (wgs84BoundingBox !== undefined) {
|
||||
const wgs84ProjectionExtent = getProjection('EPSG:4326').getExtent();
|
||||
wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] &&
|
||||
wgs84BoundingBox[2] == wgs84ProjectionExtent[2]);
|
||||
extent = transformExtent(
|
||||
wgs84BoundingBox, 'EPSG:4326', projection);
|
||||
const projectionExtent = projection.getExtent();
|
||||
if (projectionExtent) {
|
||||
// If possible, do a sanity check on the extent - it should never be
|
||||
// bigger than the validity extent of the projection of a matrix set.
|
||||
if (!containsExtent(projectionExtent, extent)) {
|
||||
extent = undefined;
|
||||
}
|
||||
}
|
||||
const wrapX = false;
|
||||
|
||||
const matrix0 = matrixSetObj.TileMatrix[0];
|
||||
const resolution = matrix0.ScaleDenominator * 0.00028; // WMTS 1.0.0: standardized rendering pixel size
|
||||
const origin = projection === getProjection('EPSG:4326')
|
||||
? [matrix0.TopLeftCorner[1], matrix0.TopLeftCorner[0]]
|
||||
: matrix0.TopLeftCorner;
|
||||
const tileSpanX = matrix0.TileWidth * resolution;
|
||||
const tileSpanY = matrix0.TileHeight * resolution;
|
||||
|
||||
const extent = [
|
||||
origin[0],
|
||||
origin[1] - tileSpanY * matrix0.MatrixHeight,
|
||||
origin[0] + tileSpanX * matrix0.MatrixWidth,
|
||||
origin[1]
|
||||
];
|
||||
|
||||
if (projection.getExtent() === null) {
|
||||
projection.setExtent(extent);
|
||||
}
|
||||
|
||||
const tileGrid = createFromCapabilitiesMatrixSet(matrixSetObj, extent, matrixLimits);
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
* @enum {string}
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Anchor is a fraction
|
||||
* @api
|
||||
*/
|
||||
FRACTION: 'fraction',
|
||||
/**
|
||||
* Anchor is in pixels
|
||||
* @api
|
||||
*/
|
||||
PIXELS: 'pixels'
|
||||
};
|
||||
|
||||
@@ -85,15 +85,18 @@ class IconImage extends EventTarget {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {CanvasRenderingContext2D=} context A context with the image already drawn into.
|
||||
* @return {boolean} The image canvas is tainted.
|
||||
*/
|
||||
isTainted_() {
|
||||
isTainted_(context) {
|
||||
if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {
|
||||
this.tainted_ = false;
|
||||
const context = createCanvasContext2D(1, 1);
|
||||
try {
|
||||
if (!context) {
|
||||
context = createCanvasContext2D(1, 1);
|
||||
context.drawImage(this.image_, 0, 0);
|
||||
}
|
||||
try {
|
||||
context.getImageData(0, 0, 1, 1);
|
||||
this.tainted_ = false;
|
||||
} catch (e) {
|
||||
this.tainted_ = true;
|
||||
}
|
||||
@@ -203,7 +206,7 @@ class IconImage extends EventTarget {
|
||||
* @private
|
||||
*/
|
||||
replaceColor_() {
|
||||
if (!this.color_ || this.isTainted_()) {
|
||||
if (!this.color_) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,6 +216,25 @@ class IconImage extends EventTarget {
|
||||
const ctx = this.canvas_.getContext('2d');
|
||||
ctx.drawImage(this.image_, 0, 0);
|
||||
|
||||
if (this.isTainted_(ctx)) {
|
||||
// If reading from the canvas throws a SecurityError the same effect can be
|
||||
// achieved with globalCompositeOperation.
|
||||
// This could be used as the default, but it is not fully supported by all
|
||||
// browsers. E. g. Internet Explorer 11 does not support the multiply
|
||||
// operation and the resulting image shape will be completelly filled with
|
||||
// the provided color.
|
||||
// So this is only used as a fallback. It is still better than having no icon
|
||||
// at all.
|
||||
const c = this.color_;
|
||||
ctx.globalCompositeOperation = 'multiply';
|
||||
ctx.fillStyle = 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')';
|
||||
ctx.fillRect(0, 0, this.image_.width, this.image_.height);
|
||||
|
||||
ctx.globalCompositeOperation = 'destination-in';
|
||||
ctx.drawImage(this.image_, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
|
||||
const data = imgData.data;
|
||||
const r = this.color_[0] / 255.0;
|
||||
|
||||
@@ -7,8 +7,24 @@
|
||||
* @enum {string}
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Origin is at bottom left
|
||||
* @api
|
||||
*/
|
||||
BOTTOM_LEFT: 'bottom-left',
|
||||
/**
|
||||
* Origin is at bottom right
|
||||
* @api
|
||||
*/
|
||||
BOTTOM_RIGHT: 'bottom-right',
|
||||
/**
|
||||
* Origin is at top left
|
||||
* @api
|
||||
*/
|
||||
TOP_LEFT: 'top-left',
|
||||
/**
|
||||
* Origin is at top right
|
||||
* @api
|
||||
*/
|
||||
TOP_RIGHT: 'top-right'
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user