/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-types */ import { IS_NODE } from './env'; import Browser from '../Browser'; import { isString, isNil } from './common'; import Point from '../../geo/Point'; let requestAnimFrame: any, cancelAnimFrame: any; /* istanbul ignore next */ (function () { if (IS_NODE) { requestAnimFrame = function (fn) { return setTimeout(fn, 16); }; cancelAnimFrame = clearTimeout; return; } //why? package bundle tool will Object.freeze(exports) requestAnimFrame = function (callback: FrameRequestCallback): number { return requestAnimationFrame(callback); } cancelAnimFrame = function (handle: number) { return cancelAnimationFrame(handle); } })(); export { requestAnimFrame, cancelAnimFrame }; export function isSVG(url: string) { const prefix = 'data:image/svg+xml'; if (url.length > 4 && url.slice(-4) === '.svg') { return 1; } else if (url.slice(0, prefix.length) === prefix) { return 2; } return 0; } /** * Load a image, can be a remote one or a local file.
* If in node, a SVG image will be converted to a png file by [svg2img]{@link https://github.com/FuZhenn/node-svg2img}
* @param img - the image object to load. * @param imgDesc - image's descriptor, it's an array. imgUrl[0] is the url string, imgUrl[1] is the width, imgUrl[2] is the height. * @private * @memberOf Util */ export function loadImage(img: any, imgDesc: Object[]) { /* istanbul ignore next */ // @ts-expect-error if (IS_NODE && loadImage.node) { // @ts-expect-error loadImage.node(img, imgDesc); return; } img.src = imgDesc[0]; } let uid = 0; export function UID() { return uid++; } export const GUID = UID; /** * Parse a JSON string to a object * @param str - a JSON string * @return * @memberOf Util */ export function parseJSON(str: string) { if (!str || !isString(str)) { return str; } return JSON.parse(str); } export function pushIn>(...args: T[]) { const dest = args[0] for (let i = 1; i < args.length; i++) { const src = args[i]; if (src && src.length) { for (let ii = 0, ll = src.length; ii < ll; ii++) { dest.push(src[ii]); } } } return dest.length; } export function mergeArray>(...args: T[]) { const dest = []; let idx = -1; for (let i = 0; i < args.length; i++) { const src = args[i]; if (src && src.length) { for (let ii = 0, ll = src.length; ii < ll; ii++) { dest[++idx] = src[ii]; } } } return dest; } export function removeFromArray(obj: T, array: T[]) { const i = array.indexOf(obj); if (i > -1) { array.splice(i, 1); } } export function forEachCoord(arr: any[], fn: Function, context?: any) { if (!Array.isArray(arr)) { return context ? fn.call(context, arr) : fn(arr); } const result = []; let p, pp; for (let i = 0, len = arr.length; i < len; i++) { p = arr[i]; if (isNil(p)) { result.push(null); continue; } if (Array.isArray(p)) { result.push(forEachCoord(p, fn, context)); } else { pp = context ? fn.call(context, p) : fn(p); result.push(pp); } } return result; } export function getValueOrDefault(v: T, d: T) { return v === undefined ? d : v; } /** * Polyfill for Math.sign * @param x * @return * @memberOf Util */ /* istanbul ignore next */ export function sign(x: number) { if (Math.sign) { return Math.sign(x); } x = +x; // convert to a number if (x === 0 || isNaN(x)) { return Number(x); } return x > 0 ? 1 : -1; } export function log2(x: number) { if (Math.log2) { return Math.log2(x); } const v = Math.log(x) * Math.LOG2E; const rounded = Math.round(v); if (Math.abs(rounded - v) < 1E-14) { return rounded; } else { return v; } } /** * Interpolate between two number. * * @param from * @param to * @param t interpolation factor between 0 and 1 * @returns interpolated color */ export function interpolate(a: number, b: number, t: number) { return (a * (1 - t)) + (b * t); } /** * constrain n to the given range, via modular arithmetic * @param n value * @param min the minimum value to be returned, inclusive * @param max the maximum value to be returned, inclusive * @returns constrained number * @private */ export function wrap(n: number, min: number, max: number) { if (n === max || n === min) { return n; } const d = max - min; const w = ((n - min) % d + d) % d + min; return w; } /** * constrain n to the given range via min + max * * @param n value * @param min the minimum value to be returned * @param max the maximum value to be returned * @returns the clamped value * @private */ export function clamp(n: number, min: number, max: number) { return Math.min(max, Math.max(min, n)); } /** * Is object an array and not empty. * @param obj * @return true|false * @private * @memberOf Util */ export function isArrayHasData(obj: Object): boolean { return Array.isArray(obj) && obj.length > 0; } const urlPattern = /^([a-z][a-z\d+\-.]*:)?\/\//i; /** * Whether the input string is a valid url. * form: https://github.com/axios/axios/blob/master/lib/helpers/isAbsoluteURL.js * @param url - url to check * @return * @memberOf Util * @private */ export function isURL(url: string) { // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed // by any combination of letters, digits, plus, period, or hyphen. // eslint-disable-next-line no-useless-escape return urlPattern.test(url); } //改原先的regex名字为xWithQuote;不带引号的regex,/^url\(([^\'\"].*[^\'\"])\)$/i,为xWithoutQuote。然后在is函数里||测试,extract函数里if...else处理。没引号的匹配后,取matches[1] // match: url('x'), url("x"). // TODO: url(x) const cssUrlReWithQuote = /^url\((['"])(.+)\1\)$/i; const cssUrlRe = /^url\(([^'"].*[^'"])\)$/i; export function isCssUrl(str: string) { if (!isString(str)) { return 0; } if (cssUrlRe.test(str)) { return 1; } if (cssUrlReWithQuote.test(str)) { return 2; } return 3; } export function extractCssUrl(str: string) { const test = isCssUrl(str); let matches; if (test === 3) { return str; } else if (test === 1) { matches = cssUrlRe.exec(str); return matches[1]; } else if (test === 2) { matches = cssUrlReWithQuote.exec(str); return matches[2]; } else { // return as is if not an css url return str; } } const b64chrs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; /** * btoa or a polyfill in old browsers.
* Creates a base-64 encoded ASCII string from a String object in which each character in the string is treated as a byte of binary data.
* From https://github.com/davidchambers/Base64.js * @param input - input string to convert * @return ascii * @memberOf Util * @example * const encodedData = Util.btoa(stringToEncode); */ /* istanbul ignore next */ /* eslint-disable no-sequences */ export function btoa(input: string) { if (Browser.btoa) { return window.btoa(input); } const str = String(input); let output = ''; for ( // initialize result and counter let block, charCode, idx = 0, map = b64chrs; // if the next str index does not exist: // change the mapping table to "=" // check if d has no fractional digits str.charAt(idx | 0) || (map = '=', idx % 1); // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 output += map.charAt(63 & block >> 8 - idx % 1 * 8) ) { charCode = str.charCodeAt(idx += 3 / 4); if (charCode > 0xFF) { throw new Error('\'btoa\' failed: The string to be encoded contains characters outside of the Latin1 range.'); } block = block << 8 | charCode; } return output; } /* eslint-enable no-sequences */ export function b64toBlob(b64Data: string, contentType: string) { const byteCharacters = atob(b64Data); const arraybuffer = new ArrayBuffer(byteCharacters.length); const view = new Uint8Array(arraybuffer); for (let i = 0; i < byteCharacters.length; i++) { view[i] = byteCharacters.charCodeAt(i) & 0xff; } const blob = new Blob([arraybuffer], { type: contentType }); return blob; } /** * Compute degree bewteen 2 points. * @param x0 * @param y0 * @param x1 * @param y1 * @return degree between 2 points * @memberOf Util */ export function computeDegree(x0: number, y0: number, x1: number, y1: number) { const dx = x1 - x0; const dy = y1 - y0; return Math.atan2(dy, dx); } /** * Transparent 1X1 gif image * from https://css-tricks.com/snippets/html/base64-encode-of-1x1px-transparent-gif/ * @memberOf Util */ export const emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; /** * shallow equal * @param obj1 * @param obj2 * @return * @private * @memberOf Util */ export function equalMapView(obj1: Object, obj2: Object) { if (!obj1 && !obj2) { return true; } else if (!obj1 || !obj2) { return false; } for (const p in obj1) { if (p === 'center') { if (!obj2[p] || !approx(obj1[p][0], obj2[p][0]) || !approx(obj1[p][1], obj2[p][1])) { return false; } } else if (obj1[p] !== obj2[p]) { return false; } } return true; } function approx(val: number, expected: number, delta?: number) { if (delta == null) { delta = 1e-6; } return val >= expected - delta && val <= expected + delta; } /** * Flash something, show and hide by certain internal for times of count. * * @param interval - interval of flash, in millisecond (ms) * @param count - flash times * @param cb - callback function when flash ended * @param context - callback context * @return this * @private * @memberOf Util */ export function flash(interval: number = 100, count: number = 4, cb: Function = null, context: any = null) { if (!interval) { interval = 100; } if (!count) { count = 4; } // eslint-disable-next-line @typescript-eslint/no-this-alias const me = this; const initVisible = this.isVisible(); count *= 2; if (this._flashTimeout) { clearTimeout(this._flashTimeout); } function flashGeo() { if (count === 0) { if (initVisible) { me.show(); } else { me.hide(); } if (cb) { if (context) { cb.call(context); } else { cb(); } } return; } if (count % 2 === 0) { me.hide(); } else { me.show(); } count--; me._flashTimeout = setTimeout(flashGeo, interval); } this._flashTimeout = setTimeout(flashGeo, interval); return this; } export function _defaults(obj: any, defaults: any) { const keys = Object.getOwnPropertyNames(defaults); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; } export function getPointsResultPts(points: any[] = [], ptKey = '_pt') { const resultPoints = []; let idx = -1; const hasKey = `${ptKey}Has`; for (let i = 0, len = points.length; i < len; i++) { const point = points[i]; if (!point) { // resultPoints.push(null); resultPoints[++idx] = null; continue; } if (!point[hasKey]) { point[hasKey] = true; point[ptKey] = new Point(0, 0); } const pt = point[ptKey]; pt.x = 0; pt.y = 0; // resultPoints.push(pt); resultPoints[++idx] = pt; } return resultPoints; } // let BITMAP_CTX; // if (Browser.decodeImageInWorker) { // const canvas = document.createElement('canvas'); // canvas.width = 1; // canvas.height = 1; // BITMAP_CTX = canvas.getContext('2d'); // } export function getImageBitMap(data: { data: T }, cb: (d: T) => void | any) { cb(data.data); // const imageData = BITMAP_CTX.createImageData(data.width, data.height); // imageData.data.set(data.data); // createImageBitmap(imageData).then(bitmap => { // cb(bitmap); // }); } export function getAbsoluteURL(url: string) { if (url && url.indexOf('http://') === 0 || url.indexOf('https://') === 0) { return url; } let a = document.createElement('a'); a.href = url; url = a.href; a = null; return url; } const CANVAS_SIZE_TEMP = { cssWidth: '1px', cssHeight: '1px', width: 1, height: 1 }; export function calCanvasSize(size: { width: number, height: number }, devicePixelRatio = 1) { const { width, height } = size; CANVAS_SIZE_TEMP.cssWidth = width + 'px'; CANVAS_SIZE_TEMP.cssHeight = height + 'px'; CANVAS_SIZE_TEMP.width = Math.round(width * devicePixelRatio); CANVAS_SIZE_TEMP.height = Math.round(height * devicePixelRatio); return CANVAS_SIZE_TEMP; } export function isNoContentHttpCode(code: number) { // https://en.wikipedia.org/wiki/List_of_HTTP_status_codes // 501 Not Implemented return code === 204 || code >= 400 && code < 500 || code === 501; }