tg-map-vue3
Version:
封装 百度地图, Google地图, Here地图(未完成) 的Vue3组件库
1 lines • 643 kB
Source Map (JSON)
{"version":3,"file":"tg-map.cjs","sources":["../src/map/event.ts","../node_modules/.pnpm/coordtransform@https+++codeload.github.com+TranscodeGroup+coordtransform+tar.gz+fa1d037_757f26d75dca1d834394e233758fb888/node_modules/coordtransform/index.js","../src/utils/mapped-types.ts","../src/map/lat-lng.ts","../src/utils/baidu-utils.ts","../src/utils/elements.ts","../src/utils/arrays.ts","../src/map/event-target.ts","../src/utils/values.ts","../src/map/map/controls/control.ts","../src/map/map/map-type.ts","../src/map/map/controls/map-type.control.ts","../src/map/map/controls/scale.control.ts","../src/map/map/controls/street-view.control.ts","../src/map/map/controls/zoom.control.ts","../src/utils/google-utils.ts","../src/map/map/extra/autocomplete.ts","../node_modules/.pnpm/bmaplib.heatmap@https+++codeload.github.com+TranscodeGroup+BMapLib.Heatmap+tar.gz+d10d1_0e30a4b877f1ed58b5a4ffd60c1b8eff/node_modules/bmaplib.heatmap/index.js","../src/map/map/extra/heatmap.ts","../src/map/map/extra/map-urls.ts","../node_modules/.pnpm/@google+markerclusterer@2.0.9_patch_hash=d6cfd0dc21a1713018bb855d8df6ae47a22e54ed44990e06688f2eca4bc2ce4d/node_modules/@google/markerclusterer/dist/markerclusterer.esm.js","../node_modules/.pnpm/bmaplib.texticonoverlay@https+++codeload.github.com+TranscodeGroup+BMapLib.TextIconOver_17859eb7f20eb264a3c318a90f4a95ca/node_modules/bmaplib.texticonoverlay/index.js","../node_modules/.pnpm/bmaplib.markerclusterer@https+++codeload.github.com+TranscodeGroup+BMapLib.MarkerCluste_36776bff126a4f3a43f5ab71afdb5a8b/node_modules/bmaplib.markerclusterer/index.esm.js","../src/map/map/extra/marker-clusterer.ts","../src/map/map/extra/places-service.ts","../src/map/map/extra/search-box.ts","../src/map/map/map.ts","../node_modules/.pnpm/deepmerge@4.3.1/node_modules/deepmerge/dist/cjs.js","../node_modules/.pnpm/is-plain-object@3.0.1/node_modules/is-plain-object/index.es.js","../src/utils/objects.ts","../src/map/map/map-options.ts","../src/map/map/overlay/overlay.ts","../src/map/map/overlay/shape.ts","../src/map/map/overlay/circle.ts","../src/map/types.ts","../src/map/map/overlay/baidu-info-box.ts","../src/map/map/overlay/info-window.ts","../src/map/map/overlay/info-box.ts","../src/utils/strings.ts","../src/map/map/overlay/element-overlay.ts","../src/map/map/overlay/google-label.ts","../src/map/map/overlay/label.ts","../src/map/map/overlay/icon.ts","../src/map/map/overlay/marker-label.ts","../src/map/map/overlay/marker.ts","../src/map/map/overlay/polyline.ts","../src/map/map/overlay/polygon.ts","../src/map/map/overlay/rectangle.ts","../src/map/map/baidu-map.ts","../src/map/map/google-map.ts","../src/utils/here-utils.ts","../src/map/map-config.ts","../src/map/map/here-map.ts","../src/map/map-factory.ts","../src/utils/utils.ts","../src/map/map-loader.ts","../src/utils/vue-utils.ts","../src/components/map-mixin.ts","../src/components/TgMap.vue","../src/utils/formatter.ts","../src/components/TgMapWidget.vue","../src/components/controls/TgCustomControl.vue","../src/components/controls/TgMapTypeControl.vue","../src/components/controls/TgScaleControl.vue","../src/utils/hooks.ts","../src/components/map-hooks.ts","../src/components/controls/TgStreetViewControl.vue","../src/components/controls/TgZoomControl.vue","../src/components/extra/TgHeatmap.vue","../src/components/extra/TgMarkerClusterer.vue","../src/components/layers/TgTrafficLayer.vue","../src/components/overlays/TgCircle.vue","../src/components/overlays/TgElementOverlay.vue","../src/components/overlays/TgMarker.vue","../src/components/overlays/TgInfoBox.vue","../src/components/overlays/TgInfoWindow.vue","../src/components/overlays/TgLabel.vue","../src/components/overlays/TgPolygon.vue","../src/components/overlays/TgPolyline.vue","../src/components/overlays/TgRectangle.vue","../src/utils/lifecycle-log.ts","../src/utils/spherical-utils.ts","../src/components/index.ts"],"sourcesContent":["import type { LatLng } from './lat-lng'\n\n/**\n * ## 事件机制对比\n * - DOM: add/removeEventListener, Event {type, target}\n * - Vue: $on/$off, 没有Event, listener直接接收数据\n * - baidu: 模仿DOM\n * - here: 模仿DOM\n * - google: addListener+remove, 没有Event, listener直接接收数据\n *\n * 综合对比之后, 决定模仿DOM的事件机制\n */\nexport namespace Tg {\n export interface Event {\n type: string,\n target: EventTarget\n originalEvent?: any\n }\n export interface MouseEvent extends Event {\n position: LatLng\n }\n\n export interface EventListener {\n (this: EventTarget, event: Event): void\n /** 唯一id */\n __id__?: number\n }\n\n export interface EventTarget {\n addEventListener(type: string, listener: EventListener): void\n removeEventListener(type: string, listener: EventListener): void\n }\n\n /**\n * 拥有更详细的类型信息\n * @see EventTarget\n * */\n export interface EventTargetTyped<THIS extends EventTarget, MAP extends { [k: string]: Event }> extends EventTarget {\n addEventListener<K extends keyof MAP>(type: K, listener: (this: THIS, event: MAP[K]) => void): void\n removeEventListener<K extends keyof MAP>(type: K, listener: (this: THIS, event: MAP[K]) => void): void\n }\n}\n\nexport function bindEvents(listeners: Record<string, unknown>, target: Tg.EventTarget, excludes?: string[]) {\n Object.keys(listeners).forEach(eventName => {\n if (eventName.startsWith('update:')) {\n // `update:`开头的事件是sync事件, 应该排除\n return\n }\n if (excludes?.includes(eventName)) {\n // 需要排除掉一些事件\n return\n }\n target.addEventListener(eventName, (listeners[eventName] as any))\n })\n}\n\nlet eventListenerId = 1\nexport function generateNextEventListenerId() {\n return eventListenerId++\n}\n\n/**\n * JS的Map/object的key都不支持自定义hashCode()/equals(), 故只能生成字符串当作key\n */\nexport function generateEventKey(type: string, listener: Tg.EventListener) {\n if (!listener.__id__) {\n listener.__id__ = generateNextEventListenerId()\n }\n return `${type}::${listener.__id__}`\n}\n","/**\n * Created by Wandergis on 2015/7/8.\n * 提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换\n */\n//UMD魔法代码\n// if the module has no dependencies, the above pattern can be simplified to\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define([], factory);\n } else if (typeof module === 'object' && module.exports) {\n // Node. Does not work with strict CommonJS, but\n // only CommonJS-like environments that support module.exports,\n // like Node.\n module.exports = factory();\n } else {\n // Browser globals (root is window)\n root.coordtransform = factory();\n }\n}(this, function () {\n //定义一些常量\n var x_PI = 3.14159265358979324 * 3000.0 / 180.0;\n var PI = 3.1415926535897932384626;\n var a = 6378245.0;\n var ee = 0.00669342162296594323;\n\n function Rectangle(lng1, lat1, lng2, lat2) {\n this.west = Math.min(lng1, lng2);\n this.north = Math.max(lat1, lat2);\n this.east = Math.max(lng1, lng2);\n this.south = Math.min(lat1, lat2);\n }\n /**\n * @param {number} lon\n * @param {number} lat\n */\n Rectangle.prototype.contain = function (lon, lat) {\n return this.west <= lon && this.east >= lon && this.north >= lat && this.south <= lat;\n }\n\n // China region - raw data\n var region = [\n new Rectangle(79.446200, 49.220400, 96.330000, 42.889900),\n new Rectangle(109.687200, 54.141500, 135.000200, 39.374200),\n new Rectangle(73.124600, 42.889900, 124.143255, 29.529700),\n new Rectangle(82.968400, 29.529700, 97.035200, 26.718600),\n new Rectangle(97.025300, 29.529700, 124.367395, 20.414096),\n new Rectangle(107.975793, 20.414096, 111.744104, 17.871542)\n ];\n // China excluded region - raw data\n var exclude = [\n new Rectangle(119.921265, 25.398623, 122.497559, 21.785006),\n new Rectangle(101.865200, 22.284000, 106.665000, 20.098800),\n new Rectangle(106.452500, 21.542200, 108.051000, 20.487800),\n new Rectangle(109.032300, 55.817500, 119.127000, 50.325700),\n new Rectangle(127.456800, 55.817500, 137.022700, 49.557400),\n new Rectangle(131.266200, 44.892200, 137.022700, 42.569200),\n new Rectangle(73.124600, 35.398637, 77.948114, 29.529700),\n ]\n // China excluded region 2, Hongkong etc.\n var EXCLUDE_REGIN_2 = [\n new Rectangle(114.505238, 22.138258, 113.845000, 22.428903),\n new Rectangle(113.97000, 22.507833, 114.450000, 22.428903),\n ];\n\n function isInChina(lon, lat, excludeRegin2 = true) {\n for (var i = 0; i < region.length; i++) {\n if (region[i].contain(lon, lat)) {\n for (var j = 0; j < exclude.length; j++) {\n if (exclude[j].contain(lon, lat)) {\n return false;\n }\n }\n if (excludeRegin2) {\n for (var j = 0; j < EXCLUDE_REGIN_2.length; j++) {\n if (EXCLUDE_REGIN_2[j].contain(lon, lat)) {\n return false;\n }\n }\n }\n return true;\n }\n }\n return false;\n }\n\n /**\n * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换\n * 即 百度 转 谷歌、高德\n * @param {number} bd_lon\n * @param {number} bd_lat\n * @returns {*[]}\n */\n function bd09togcj02(bd_lon, bd_lat) {\n var x = bd_lon - 0.0065;\n var y = bd_lat - 0.006;\n var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);\n var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);\n var gg_lng = z * Math.cos(theta);\n var gg_lat = z * Math.sin(theta);\n return [gg_lng, gg_lat]\n };\n\n /**\n * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换\n * 即谷歌、高德 转 百度\n * @param {number} lng\n * @param {number} lat\n * @returns {*[]}\n */\n function gcj02tobd09(lng, lat) {\n var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);\n var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);\n var bd_lng = z * Math.cos(theta) + 0.0065;\n var bd_lat = z * Math.sin(theta) + 0.006;\n return [bd_lng, bd_lat]\n };\n\n /**\n * WGS84转GCj02\n * @param {number} lng\n * @param {number} lat\n * @returns {*[]}\n */\n function wgs84togcj02(lng, lat) {\n\n return transform(lng, lat)\n };\n\n /**\n * GCJ02 转换为 WGS84\n * @param {number} lng\n * @param {number} lat\n * @returns {*[]}\n */\n function gcj02towgs84(lng, lat) {\n var out = transform(lng, lat)\n return [lng * 2 - out[0], lat * 2 - out[1]]\n };\n\n function transform(lng, lat) {\n var dlat = transformlat(lng - 105.0, lat - 35.0);\n var dlng = transformlng(lng - 105.0, lat - 35.0);\n var radlat = lat / 180.0 * PI;\n var magic = Math.sin(radlat);\n magic = 1 - ee * magic * magic;\n var sqrtmagic = Math.sqrt(magic);\n dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI);\n dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI);\n var mglat = lat + dlat;\n var mglng = lng + dlng;\n return [mglng, mglat]\n }\n\n function transformlat(lng, lat) {\n var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));\n ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;\n ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;\n return ret\n };\n\n function transformlng(lng, lat) {\n var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));\n ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;\n ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;\n ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0;\n return ret\n };\n\n return {\n // 原始的转换方法\n rawBd09ToGcj02: bd09togcj02,\n rawGcj02ToBd09: gcj02tobd09,\n rawWgs84ToGcj02: wgs84togcj02,\n rawGcj02ToWgs84: gcj02towgs84,\n // 判断了中国范围的转换方法\n bd09ToGcj02: bd09togcj02,\n gcj02ToBd09: gcj02tobd09,\n wgs84ToGcj02: function (lng, lat) {\n if (!isInChina(lng, lat)) {\n return [lng, lat]\n }\n return wgs84togcj02(lng, lat)\n },\n gcj02ToWgs84: function (lng, lat) {\n if (!isInChina(lng, lat)) {\n return [lng, lat]\n }\n return gcj02towgs84(lng, lat)\n },\n bd09ToWgs84: function (lng, lat) {\n if (!isInChina(lng, lat, false)) {\n return [lng, lat]\n }\n var gcj02 = bd09togcj02(lng, lat)\n return gcj02towgs84(gcj02[0], gcj02[1])\n },\n wgs84ToBd09: function (lng, lat) {\n if (!isInChina(lng, lat, false)) {\n return [lng, lat]\n }\n var gcj02 = wgs84togcj02(lng, lat)\n return gcj02tobd09(gcj02[0], gcj02[1])\n }\n }\n}));\n","// [PrimitiveToWrapper]就是用来转换这两种类型的\n/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport type { Component, ComponentPublicInstance } from 'vue'\n\n/** deep版的 `Partial` */\nexport type DeepPartial<T> = {\n [U in keyof T]?: T[U] extends object\n ? DeepPartial<T[U]>\n : T[U]\n}\n\n/** 表示一个函数 */\nexport type AnyFunction = (...args: any[]) => any\n\n/**\n * 表示一个构造器\n * {@link import('vue/types/options').Constructor}\n * */\nexport type Constructor<T = {}> = new (...args: any[]) => T\n\n/**\n * 抽象构造器类型, 相比{@link Constructor}, 不能直接用new创建对象\n * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-2.html#abstract-construct-signatures\n */\nexport type AbstractConstructor<T = {}> = abstract new (...args: any[]) => T\n\n/** 原始类型=>包装类型 */\nexport type PrimitiveToWrapper<T> =\n T extends boolean ? boolean :\n T extends string ? string :\n T extends number ? number :\n T\n\n/** 包装类型=>原始类型 */\nexport type WrapperToPrimitive<T> =\n T extends boolean ? boolean :\n T extends string ? string :\n T extends number ? number :\n T\n\n/**\n * `vue instanceof VueConstructor`时`vue`并不会自动转换成`VueConstructor`的子类, 用该方法可以实现这种转换\n */\nexport function isInstanceOf<T>(obj: any, constructor: Constructor<T>): obj is T {\n return obj instanceof constructor\n}\n\n/** @see isInstanceOf */\nexport function safeAs<T>(obj: any, constructor: Constructor<T>): T | undefined {\n if (obj instanceof constructor) {\n return obj\n }\n return undefined\n}\n\n/** 判断{@link instance}是否是{@link component}的实例 */\nexport function isComponentByType<C extends Component & AbstractConstructor>(\n instance: ComponentPublicInstance | null,\n component: C,\n): instance is InstanceType<C> {\n // TODO: 2023/03/15 ipcjs 看有没有其他更好的方式判断组件类型\n return instance?.$options.name === component.name\n}\n\n/** 安全转换组件类型, 若不能转换会返回`undefined` */\nexport function safeAsComponent<C extends Component & AbstractConstructor>(\n instance: ComponentPublicInstance | null,\n component: C,\n): InstanceType<C> | undefined {\n return isComponentByType(instance, component) ? instance : undefined\n}\n\n/**\n * 查找[startInstance, endComponent)之间的第一个类型为{@link component}的父组件\n * @param component 查找的组件\n * @param startInstance 查找的开始实例, 包含\n * @param endComponent 查找的终止组件, 不包含\n * @returns\n */\nexport function findAncestorComponentByType<C extends Component & AbstractConstructor>(\n component: C,\n startInstance: ComponentPublicInstance | null,\n endComponent?: Component & AbstractConstructor,\n): InstanceType<C> | undefined {\n let instance = startInstance\n while (instance) {\n if (isComponentByType(instance, component)) {\n return instance\n } else if (endComponent && isComponentByType(instance, endComponent)) {\n return undefined\n } else {\n instance = instance.$parent\n }\n }\n return undefined\n}\n\n/** {@link NonNullable} 的反义词 */\nexport type Nullable<T> = T | null | undefined\n\n/** T的所有value的联合类型 */\nexport type ValueOf<T> = T[keyof T]\n\n/**\n * 提取出T中value为V的子类的所有key\n * @see https://stackoverflow.com/questions/54520676/in-typescript-how-to-specify-only-keys-of-a-generic-object-whose-values-are-stri\n * */\nexport type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T]\n\n/**\n * 并不存在可以精确表示一个枚举类型, 该类只能近似表示一个字符枚举\n * */\nexport type StringEnumLike = { [k: string]: string }\n\n/**\n * String枚举的值, 包括`string`类型\n * @see https://stackoverflow.com/questions/52393730/typescript-string-literal-union-type-from-enum\n */\nexport type StringEnumValue<E extends StringEnumLike> = ValueOf<E> | `${ValueOf<E>}`\n\n/**\n * 包装`T`中的部分枚举字段的值为{@link StringEnumValue}\n *\n * 会校验{@link EnumObjects}中的类型是否书写正确, 若写错, 会推断成`never`\n * */\nexport type WrapStringEnumValue<T, EnumObjects extends { [k in keyof T]?: StringEnumLike } > = {\n [P in keyof T]: P extends keyof EnumObjects\n ? EnumObjects[P] extends StringEnumLike\n ? T[P] extends ValueOf<EnumObjects[P]> | undefined\n ? StringEnumValue<EnumObjects[P]>\n : never\n : never\n : T[P]\n}\n\n/** 取消包装`T`中的{@link StringEnumValue}字段 */\nexport function unwrapStringEnumValue<T extends object>(value: T): {\n [K in keyof T]: T[K] extends StringEnumValue<infer Enum>\n ? StringEnumLike extends Enum\n // 若Enum推断成StringEnumLike, 说明Enum不是真的枚举, 直接返回原始的类型T[K]\n ? T[K]\n // 否则, Enum则时真正的枚举, 返回枚举的类型\n : Enum[keyof Enum]\n : Exclude<T[K], undefined> extends StringEnumValue<infer Enum>\n ? StringEnumLike extends Enum\n // 同上\n ? T[K]\n : Enum[keyof Enum] | undefined\n : T[K]\n}\n\nexport function unwrapStringEnumValue<E extends StringEnumLike>(value: StringEnumValue<E>, enumObject?: E): E[keyof E]\nexport function unwrapStringEnumValue<E extends StringEnumLike>(value: StringEnumValue<E> | undefined, enumObject?: E): E[keyof E] | undefined\n/**\n * 取消包装`StringEnumValue`, 将其还原成枚举, 和{@link WrapStringEnumValue}对应\n *\n * 整个方法没有逻辑, 只是做类型校验; 重载中的`enumObject`也仅用与辅助推理`E`的类型, 并没有实际作用;\n */\nexport function unwrapStringEnumValue(value: any): any {\n return value\n}\n\n/**\n * 非空的值\n *\n * 类型`{}`也表示\"任何非空的值\", 但由于它看起来像空对象, 所以一般更推荐用Thing\n *\n * @see https://github.com/microsoft/TypeScript/issues/7648#issuecomment-541625573\n * @see https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492\n */\nexport type Thing = string | number | boolean | bigint | object | symbol\n\n/** 除T指定的类型外, 其他参数 */\nexport type TypedAny<T> = T & { [k: string]: any }\n","import coordtransform, { type LngLatTuple } from 'coordtransform'\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport { assert, noop, throwError, type Arrays } from 'tg-commons'\nimport { unwrapStringEnumValue, type StringEnumValue } from '../utils/mapped-types'\n\nexport enum CoordType {\n wgs84 = 'wgs84',\n gcj02 = 'gcj02',\n bd09 = 'bd09',\n}\n\nconst COORD_TYPE_DEFAULT = CoordType.wgs84\n\nexport interface CoordTypeSupplier {\n readonly coordType: CoordType\n}\n\nexport interface LatLngLiteral {\n readonly lat: number\n readonly lng: number\n readonly coord?: CoordType\n}\n\nexport interface WeightedLatLng {\n position: LatLng\n weight: number\n}\n\nexport class LatLng {\n static ZERO = LatLng.create(0, 0)\n /**\n * {@template latlng_normalize}\n *\n * 对经纬度进行标准化, 参考google的实现\n *\n * ## 行为不一致\n * - google: google.maps.LatLng的构造器会对经纬度进行标准化, 所有点都能正常显示\n * - baidu: BMap.Point的构造器不会对经纬度进行标准化, 超出范围的点显示到(0, 0)位置\n *\n * {@endtemplate}\n */\n /* private */ static _normalize(lng: number, lat: number) {\n // lat限制在[-90, 90]范围内\n const normalizedLat = lat > 90\n ? 90\n : lat < -90\n ? -90\n : lat\n // lng转换到[-180, 180]范围内\n const normalizedLng = lng > 180 //\n ? (lng + 180) % 360 - 180\n : lng < -180\n ? (lng - 180) % 360 + 180\n : lng\n return [normalizedLng, normalizedLat]\n }\n\n /** @param coord Baidu Map的坐标一般都是bd09 */\n static fromBaidu(point: BMap.Point, coord: CoordType) {\n return new LatLng(point.lat, point.lng, coord)\n }\n /** @param coord Google Map的坐标依据地图类型不同是不一样的 */\n static fromGoogle(latlng: google.maps.LatLng, coord: CoordType) {\n return new LatLng(latlng.lat(), latlng.lng(), coord)\n }\n static fromHere(point: H.geo.IPoint, coord: CoordType) {\n return new LatLng(point.lat, point.lng, coord)\n }\n\n static fromLiteral(latlng: LatLngLiteral) {\n return new LatLng(latlng.lat, latlng.lng, latlng.coord || COORD_TYPE_DEFAULT)\n }\n\n static fromLngLat(lng: number, lat: number, coord: CoordType = COORD_TYPE_DEFAULT) {\n return new LatLng(lat, lng, coord)\n }\n\n static create(lat: number, lng: number, coord: CoordType = COORD_TYPE_DEFAULT) {\n return new LatLng(lat, lng, coord)\n }\n\n constructor(\n /**\n * 纬度, 分南北, [-90, 90], 0点为赤道\n */\n public readonly lat: number,\n /**\n * 经度, 分东西, [-180, 180], 0点为本初子午线\n */\n public readonly lng: number,\n /** 坐标系 */\n public readonly coord: CoordType,\n ) {\n noop()\n }\n\n /** 转换坐标到指定坐标系 */\n to(coordType: StringEnumValue<typeof CoordType>) {\n const coord = unwrapStringEnumValue(coordType, CoordType)\n if (this.coord === coord) {\n return this\n }\n const [lng, lat] = this._convert(coord)\n return new LatLng(lat, lng, coord)\n }\n\n /**\n * TODO: normalize要不要默认设为true, 和google的行为保持一致?\n * 转换成baidu的经纬度\n * @param normalize 转换时是否进行标准化, 默认false\n * @see _normalize {@macro latlng_normalize}\n */\n toBaidu(coord: CoordType, normalize = false) {\n let lat = this.lat\n let lng = this.lng\n if (this.coord !== coord) {\n [lng, lat] = this._convert(coord)\n }\n if (normalize) {\n [lng, lat] = LatLng._normalize(lng, lat)\n }\n return new BMap.Point(lng, lat)\n }\n\n /**\n * 转换成google的经纬度, 内部会自动进行标准化\n * @see _normalize {@macro latlng_normalize}\n */\n toGoogle(coord: CoordType) {\n let lat = this.lat\n let lng = this.lng\n if (this.coord !== coord) {\n [lng, lat] = this._convert(coord)\n }\n return new google.maps.LatLng(lat, lng)\n }\n\n toHere(coord: CoordType) {\n let lat = this.lat\n let lng = this.lng\n if (this.coord !== coord) {\n [lng, lat] = this._convert(coord)\n }\n return new H.geo.Point(lat, lng)\n }\n\n /**\n * 该方法之所以设计的这么不友好, 是为了防止不必要的开销(大概有点影响😅)\n *\n * 私有方法, 外部需要转换坐标系, 请使用{@link to}/toBaidu/Google/Here, 之所以不添加private关键字, 是因为设成私有后, 有的地方会报类型错误...\n *\n * @returns `[lng, lat]` 注意不要取错顺序\n * @throws 若当前coord和targetCoord相同, 会抛异常, 故请在外部判断\n */\n /* private */ _convert(targetCoord: CoordType): LngLatTuple {\n switch (this.coord) {\n case CoordType.bd09:\n if (targetCoord === CoordType.gcj02) {\n return coordtransform.bd09ToGcj02(this.lng, this.lat)\n } else {\n return coordtransform.bd09ToWgs84(this.lng, this.lat)\n }\n\n case CoordType.gcj02:\n if (targetCoord === CoordType.wgs84) {\n return coordtransform.gcj02ToWgs84(this.lng, this.lat)\n } else {\n return coordtransform.gcj02ToBd09(this.lng, this.lat)\n }\n\n case CoordType.wgs84:\n if (targetCoord === CoordType.bd09) {\n return coordtransform.wgs84ToBd09(this.lng, this.lat)\n } else {\n return coordtransform.wgs84ToGcj02(this.lng, this.lat)\n }\n }\n\n throw new Error(`不支持的坐标转换: ${this.coord}=>${targetCoord}`)\n }\n\n /**\n * 标准化, 返回合法的坐标点\n * @see _normalize {@macro latlng_normalize}\n */\n normalize() {\n const [lng, lat] = LatLng._normalize(this.lng, this.lat)\n if (lat === this.lat && lng === this.lng) {\n return this\n }\n return new LatLng(lat, lng, this.coord)\n }\n\n equals(other: LatLng) {\n if (this === other) {\n return true\n }\n return this.lat === other.lat && this.lng === other.lng && this.coord === other.coord\n }\n\n toString() {\n return JSON.stringify(this)\n }\n}\n\n/** 少数接口同时支持对象形式和字面量形式 */\nexport type LatLngType = LatLng | LatLngLiteral\n\n/**\n * 反编译自{@link com.google.android.gms.maps.model.LatLngBounds}\n */\nexport class LatLngBounds {\n static EMPTY = new LatLngBounds(LatLng.ZERO, LatLng.ZERO)\n static isBetweenLng(minLng: number, maxLng: number, lng: number) {\n return minLng <= maxLng\n ? minLng <= lng && lng <= maxLng\n // 例如: 10->181的情况, 会被表示成10->-179, 导致minLag比maxLng数值上更大\n : minLng <= lng || lng <= maxLng\n }\n /**\n * 从数组创建bounds\n *\n * 只有{@link positions}为空时, 才会返回`undefined`, 可以借助{@link Arrays.isNotEmpty}协助进行类型推断\n */\n static fromArray(positions: readonly []): undefined\n /** @see Arrays.isNotEmpty */\n static fromArray(positions: readonly [LatLng, ...LatLng[]]): LatLngBounds\n static fromArray(positions: readonly LatLng[]): LatLngBounds | undefined\n static fromArray(positions: readonly LatLng[]): LatLngBounds | undefined {\n if (positions.length === 0) {\n return undefined\n }\n const builder = new LatLngBounds.Builder(positions[0])\n for (let i = 1; i < positions.length; i++) {\n builder.include(positions[i])\n }\n return builder.build()\n }\n\n static from(...positions: LatLng[]): LatLngBounds {\n return this.fromArray(positions) ?? throwError('positions中没有点')\n }\n\n static fromGoogle(bounds: google.maps.LatLngBounds, coord: CoordType) {\n return new LatLngBounds(\n LatLng.fromGoogle(bounds.getSouthWest(), coord),\n LatLng.fromGoogle(bounds.getNorthEast(), coord),\n )\n }\n\n static fromBaidu(bounds: BMap.Bounds, coord: CoordType) {\n return new LatLngBounds(\n LatLng.fromBaidu(bounds.getSouthWest(), coord),\n LatLng.fromBaidu(bounds.getNorthEast(), coord),\n )\n }\n\n static create(sw: LatLng, ne: LatLng) {\n return this.from(sw, ne)\n }\n\n coord: CoordType\n /**\n * 不推荐直接创建LatLngBounds, sw和ne的大小关系可能没法保证, 推荐用LatLngBounds.Builder或者LatLngBounds.from等静态方法来创建\n * @param sw 西南, lat/lng更小\n * @param ne 东北, lat/lng更大\n */\n constructor(\n public sw: LatLng,\n public ne: LatLng,\n ) {\n assert(sw.coord === ne.coord, 'sw和ne的coord应该相同')\n assert(Number.isFinite(sw.lat) && Number.isFinite(sw.lng) && Number.isFinite(ne.lat) && Number.isFinite(ne.lng), `sw(${sw})和ne(${ne})不能包含NaN或Infinite`)\n assert(sw.lat <= ne.lat, `sw.lat(${sw.lat})应该比ne.lat(${ne.lat})小`)\n this.coord = sw.coord\n }\n\n isEmpty() {\n return this.sw.equals(this.ne)\n }\n\n contains(position: LatLng) {\n const { lat, lng } = position.to(this.coord)\n return this.sw.lat <= lat && lat <= this.ne.lat &&\n LatLngBounds.isBetweenLng(this.sw.lng, this.ne.lng, lng)\n }\n\n getCenter(): LatLng {\n if (this.sw.equals(this.ne)) {\n return this.sw\n }\n const lat = (this.sw.lat + this.ne.lat) / 2\n const lng = (this.sw.lng + this.ne.lng + (this.sw.lng < this.ne.lng ? 0 : 360)) / 2\n return LatLng.create(lat, lng, this.coord)\n }\n\n toString() {\n return JSON.stringify(this)\n }\n\n equals(other: LatLngBounds) {\n return this.sw.equals(other.sw) && this.ne.equals(other.ne)\n }\n\n /**\n * 矩形的四个点数组\n * 顺时针: sw(西南) -> nw(西北) -> ne(东北) -> se(东南)\n * */\n toRectArray() {\n const coord = this.sw.coord\n return [\n this.sw,\n LatLng.fromLngLat(this.sw.lng, this.ne.lat, coord),\n this.ne,\n LatLng.fromLngLat(this.ne.lng, this.sw.lat, coord),\n ]\n }\n\n toGoogle(coord: CoordType) {\n return new google.maps.LatLngBounds(this.sw.toGoogle(coord), this.ne.toGoogle(coord))\n }\n toBaidu(coord: CoordType) {\n return new BMap.Bounds(this.sw.toBaidu(coord), this.ne.toBaidu(coord))\n }\n toBaiduArray(coord: CoordType) {\n return [this.sw.toBaidu(coord), this.ne.toBaidu(coord)]\n }\n}\n\nexport namespace LatLngBounds {\n export class Builder {\n private coord: CoordType\n private minLat: number\n private minLng: number\n private maxLat: number\n private maxLng: number\n\n constructor(start: LatLng) {\n this.coord = start.coord\n this.minLat = this.maxLat = start.lat\n this.minLng = this.maxLng = start.lng\n }\n\n build() {\n return new LatLngBounds(\n // sw对应min\n LatLng.create(this.minLat, this.minLng, this.coord),\n // ne对应max\n LatLng.create(this.maxLat, this.maxLng, this.coord),\n )\n }\n\n include(position: LatLng) {\n const { lat, lng } = position.to(this.coord)\n // lat的范围是[-90, 90], 是\"线性\"的, 可以直接比较大小\n this.minLat = Math.min(this.minLat, lat)\n this.maxLat = Math.max(this.maxLat, lat)\n // lng的范围是[-180, 180], 是\"一个圆\", 大于181将被表示成-179\n if (!LatLngBounds.isBetweenLng(this.minLng, this.maxLng, lng)) {\n // 比较minLng和maxLng, 谁离lng更近, 谁更近minLng->maxLng的范围往谁身上扩展\n if (this.delta(this.minLng, lng) < this.delta(lng, this.maxLng)) {\n this.minLng = lng\n } else {\n this.maxLng = lng\n }\n }\n return this\n }\n private delta(end: number, start: number) {\n return (end - start + 360) % 360\n }\n }\n}\n/** 在导航/搜索等接口中使用的 位置 */\nexport type Location = string | LatLng\n\nexport namespace Location {\n export function toString(place: Location | undefined, coordType: CoordType) {\n if (place == null) {\n return undefined\n }\n if (typeof place === 'string') {\n return place\n }\n const ll = place.to(coordType)\n return `${ll.lat},${ll.lng}`\n }\n export function getCoordType(place: Location | undefined) {\n return typeof place === 'string' ? undefined : place?.coord\n }\n}\n","/**\n * 3.0版的Overlay是类\n * @see http://lbsyun.baidu.com/cms/jsapi/reference/jsapi_reference_3_0.html#a3b0\n */\nexport function ensureBaiduV3() {\n if (BMap.OverlayV3) {\n return\n }\n BMap.OverlayV3 = (BMap as any).Overlay\n}\n\n/**\n * 尝试修复baidu内存占用会越来越大的问题\n *\n * 当前版本的baidu源码中, 存在如下代码:\n * ```\n * A.lang.qx = function(a) {\n * var b = window[A.da]; // A.da等于'$BAIDU$'\n * b.kT && delete b.kT[a] // kT属性不存在, 故永远无法移除成功\n * }\n * ```\n * 最终会导致`$BAIDU$._instances`对象不断增长, 故我们把`$BAIDU$`对象的`kT`属性重定向到`_instances`\n * 保证该方法能够执行成功\n */\nexport function tryFixBaiduOutOfMemory() {\n const baidu = (window as any).$BAIDU$\n if ('kT' in baidu) {\n return\n }\n Object.defineProperty(baidu, 'kT', {\n get: () => baidu._instances,\n enumerable: true,\n configurable: true,\n })\n}\n\nexport const BMapPoints = {\n /** {@link BMap.Point.equals} 无效, 故需要自己写一个 */\n equals(a: BMap.Point, b: BMap.Point) {\n return a.lat === b.lat && a.lng === b.lng\n },\n}\n","export namespace Elements {\n /** @see https://stackoverflow.com/questions/4880381/check-whether-html-element-has-scrollbars */\n export function isScrollable(ele: Element) {\n return ele.scrollWidth > ele.clientWidth || ele.scrollHeight > ele.clientHeight\n }\n}\n","import type { AnyFunction, Constructor } from './mapped-types'\n\nexport namespace Arrays {\n export function findByType<T>(arr: any[], constructor: Constructor<T>): T | undefined {\n for (const item of arr) {\n if (item instanceof constructor) {\n return item\n }\n }\n return undefined\n }\n\n /** 自动推断item的类型的{@link Array.includes} */\n export function includesTyped<T extends E, E>(arr: T[], item: E): item is T {\n return (arr as E[]).includes(item)\n }\n\n /** 设置 指定位置的值 */\n export function at<T>(arr: T[], index: number, value: T): void\n /** 获取 指定位置的值 */\n export function at<T>(arr: T[], index: number): T | undefined\n /**\n * @param index 可以是负数, `-1`表示倒数第一个元素\n * @see RelativeIndexable.at\n */\n export function at<T>(arr: T[], index: number, value?: T) {\n const i = index >= 0 ? index : arr.length + index\n // 需要严格判定undefined\n if (value === undefined) {\n return arr[i]\n }\n arr[i] = value\n }\n}\n\nexport namespace MVCArrays {\n export function indexOf<T>(arr: google.maps.MVCArray<T>, item: T): number {\n const length = arr.getLength()\n for (let i = 0; i < length; i++) {\n if (arr.getAt(i) === item) {\n return i\n }\n }\n return -1\n }\n export function remove<T>(arr: google.maps.MVCArray<T>, item: T): T | undefined {\n const index = indexOf(arr, item)\n if (index !== -1) {\n return arr.removeAt(index)\n }\n return undefined\n }\n /**\n * 监听数组内容变化\n * @see https://developers.google.com/maps/documentation/javascript/shapes#editable_events\n * */\n export function listenChanged<T>(path: google.maps.MVCArray<T>, handler: AnyFunction) {\n return [\n path.addListener('insert_at', handler),\n path.addListener('remove_at', handler),\n path.addListener('set_at', handler),\n ]\n }\n}\n","// cSpell:ignore maptypeid, maptypechange\nimport { lateinit, Arrays as TgArrays } from 'tg-commons'\nimport type { AbstractMap, AbstractMapEventMap } from '../components'\nimport { Arrays } from '../utils/arrays'\nimport { generateEventKey, type Tg } from './event'\nimport { LatLng, type CoordTypeSupplier } from './lat-lng'\nimport type { UnionBaiduEventTarget, UnionGoogleEventTarget } from './unions'\n\n/**\n * 移除事件监听的函数\n *\n * 实现{@link EventTarget}有点复杂, 对于简单的情况, 实现一个设置监听的函数+返回{@link RemoveEventListenerFunction}就够了\n * @see addBaiduEventListener\n * @see addGoogleEventListener\n * */\nexport interface RemoveEventListenerFunction {\n (): void\n}\nexport function addGoogleEventListener(target: UnionGoogleEventTarget, type: string, listener: (...args: any[]) => void): RemoveEventListenerFunction {\n return target.addListener(type, listener).remove\n}\n\nexport function addBaiduEventListener(target: UnionBaiduEventTarget, type: string, listener: (...args: any[]) => void): RemoveEventListenerFunction {\n target.addEventListener(type, listener)\n return () => target.removeEventListener(type, listener)\n}\n\nexport function addHtmlEventListener<K extends keyof EventMap, EventMap=HTMLElementEventMap, T extends EventTarget=HTMLElement>(\n target: T,\n type: K,\n listener: (this: T, ev: EventMap[K]) => any,\n options?: AddEventListenerOptions | boolean,\n): RemoveEventListenerFunction {\n target.addEventListener(type as any, listener as any, options)\n return () => target.removeEventListener(type as any, listener as any, options)\n}\n\nexport function addTgEventListener<K extends keyof EventMap, EventMap=AbstractMapEventMap, T extends Tg.EventTarget=AbstractMap>(\n target: T,\n type: K,\n listener: (this: T, ev: EventMap[K]) => any,\n): RemoveEventListenerFunction {\n target.addEventListener(type as any, listener as any)\n return () => target.removeEventListener(type as any, listener as any)\n}\n\nexport interface EventTargetDelegate extends Tg.EventTarget {\n setTarget(target: Tg.EventTarget): this\n}\n\n/**\n * ## Delegate(代理) VS Mixin(混合)\n * Delegate相对于Mixin的优点是调用路径更明确, 缺点是js/ts原生不支持, 需要写样板代码\n */\nexport abstract class AbstractEventTargetDelegate<T = any> implements EventTargetDelegate {\n protected eventMap = new Map<string, any>()\n protected target = lateinit<Tg.EventTarget>()\n constructor(\n protected inner: T,\n protected coordTypeSupplier: CoordTypeSupplier,\n ) {\n // 绑定this, 防止this指向错误\n this.addEventListener = this.addEventListener.bind(this)\n this.removeEventListener = this.removeEventListener.bind(this)\n }\n setTarget(target: Tg.EventTarget) {\n this.target = target\n return this\n }\n abstract addEventListener(type: string, listener: Tg.EventListener): void\n abstract removeEventListener(type: string, listener: Tg.EventListener): void\n}\n\nexport class EventHubEventTargetDelegate<EventTypes extends string=string> implements EventTargetDelegate {\n private eventHub = new EventHub()\n constructor(\n private delegate: EventTargetDelegate,\n private options: {\n /** 由{@link eventHub}手动处理的事件 */\n types: EventTypes[],\n /** 用户开始监听{@link type} */\n onListen: (type: EventTypes) => void,\n /** 用户结束监听{@link type} */\n onCancel: (type: EventTypes) => void,\n },\n ) { }\n\n notify(event: Tg.Event) {\n this.eventHub.notify(event)\n }\n\n hasListener(type: EventTypes) {\n return this.eventHub.hasListener(type)\n }\n\n setTarget(target: Tg.EventTarget): this {\n this.delegate.setTarget(target)\n return this\n }\n\n addEventListener(type: string, listener: Tg.EventListener): void {\n if (Arrays.includesTyped(this.options.types, type)) {\n // 第一次添加\n if (!this.eventHub.hasListener(type)) {\n this.options.onListen(type)\n }\n this.eventHub.addEventListener(type, listener)\n return\n }\n this.delegate.addEventListener(type, listener)\n }\n removeEventListener(type: string, listener: Tg.EventListener): void {\n if (Arrays.includesTyped(this.options.types, type)) {\n this.eventHub.removeEventListener(type, listener)\n // 最后一次移除\n if (!this.eventHub.hasListener(type)) {\n this.options.onCancel(type)\n }\n return\n }\n this.delegate.removeEventListener(type, listener)\n }\n}\n\nexport class GoogleEventTargetDelegate<T extends UnionGoogleEventTarget = UnionGoogleEventTarget> extends AbstractEventTargetDelegate<T> {\n // 事件类型映射: tg => google\n private static EVENT_TYPE_MAP: { [k: string]: string } = {\n 'center-changed': 'center_changed',\n 'zoom-changed': 'zoom_changed',\n 'radius-changed': 'radius_changed',\n 'bounds-changed': 'bounds_changed',\n 'map-type-changed': 'maptypeid_changed',\n // google地图的overlay会拦截地图的事件, 导致在map上设置的监听缺少一部分事件, baidu地图没有这个问题\n // 规避方式一:\n // 在map监听事件的同时, 在overlay上使用`map-*`监听相同的事件..., 具体使用方式参考主项目中的`SearchInRectTool2`\n // 规避方式二: (推荐)\n // 给overlay设置`clickable=\"false\"`, 不让它接收事件\n 'map-mousedown': 'mousedown',\n 'map-mousemove': 'mousemove',\n 'map-mouseup': 'mouseup',\n 'map-dblclick': 'dblclick',\n }\n\n override addEventListener(type: string, listener: Tg.EventListener): void {\n const key = generateEventKey(type, listener)\n if (this.eventMap.get(key)) {\n console.warn('listener已经添加过了', key, listener)\n return\n }\n const removable = this.inner.addListener(GoogleEventTargetDelegate.EVENT_TYPE_MAP[type] || type, (event?: { latLng?: google.maps.LatLng }) => {\n const ev: Tg.Event = {\n type: type,\n target: this.target,\n originalEvent: event,\n }\n if (event?.latLng) { // google.maps.MouseEvent\n (ev as Tg.MouseEvent).position = LatLng.fromGoogle(event.latLng, this.coordTypeSupplier.coordType)\n }\n listener.call(this.target, ev)\n })\n this.eventMap.set(key, removable)\n }\n override removeEventListener(type: string, listener: Tg.EventListener): void {\n const key = generateEventKey(type, listener)\n const removable: google.maps.MapsEventListener = this.eventMap.get(key)\n if (removable) {\n removable.remove()\n this.eventMap.delete(key)\n } else {\n console.warn('移除失败, 不存在该listener', key, listener)\n }\n }\n}\nexport class BaiduEventTargetDelegate<T extends UnionBaiduEventTarget = UnionBaiduEventTarget> extends AbstractEventTargetDelegate<T> {\n // cSpell:words clickclose\n // 事件类型映射, tg => baidu\n private static EVENT_TYPE_MAP: { [k: string]: string } = {\n drag: 'dragging',\n closeclick: 'clickclose',\n // baidu不支持contextmenu事件, 将其映射为rightclick\n contextmenu: 'rightclick',\n 'zoom-changed': 'zoomend',\n 'map-type-changed': 'maptypechange',\n }\n\n override addEventListener(type: string, listener: Tg.EventListener): void {\n const key = generateEventKey(type, listener)\n if (this.eventMap.get(key)) {\n console.warn('listener已经添加过了', key, listener)\n return\n }\n const actualListener = (event: { point?: BMap.Point }) => {\n const ev: Tg.Event = {\n type: type,\n target: this.target,\n originalEvent: event,\n }\n if (event.point) {\n (ev as Tg.MouseEvent).position = LatLng.fromBaidu(event.point, this.coordTypeSupplier.coordType)\n }\n listener.call(this.target, ev)\n }\n this.inner.addEventListener(BaiduEventTargetDelegate.EVENT_TYPE_MAP[type] || type, actualListener)\n this.eventMap.set(key, actualListener)\n }\n override removeEventListener(type: string, listener: Tg.EventListener): void {\n const key = generateEventKey(type, listener)\n const actualListener: BMap.Callback = this.eventMap.get(key)\n if (actualListener) {\n this.inner.removeEventListener(BaiduEventTargetDelegate.EVENT_TYPE_MAP[type] || type, actualListener)\n this.eventMap.delete(key)\n } else {\n console.warn('移除失败, 不存在该listener', key, listener)\n }\n }\n}\n\n/** 手动维护事件 */\nexport class EventHub implements Tg.EventTarget {\n private typeMap = new Map<string, Tg.EventListener[]>()\n\n addEventListener(type: string, listener: Tg.EventListener): void {\n let listeners = this.typeMap.get(type)\n if (!listeners) {\n listeners = []\n this.typeMap.set(type, listeners)\n }\n if (listeners.includes(listener)) {\n return\n }\n listeners.push(listener)\n }\n removeEventListener(type: string, listener: Tg.EventListener): void {\n const listeners = this.typeMap.get(type)\n if (!listeners) {\n return\n }\n TgArrays.remove(listeners, listener)\n }\n hasListener(type: string) {\n const listeners = this.typeMap.get(type)\n return listeners != null && listeners.length > 0\n }\n notify(event: Tg.Event) {\n const listeners = this.typeMap.get(event.type)\n if (listeners) {\n listeners.forEach(it => it.call(event.target, event))\n }\n }\n}\n","import { assert, noop, throwError } from 'tg-commons'\n\n/** 键值对 */\nexport class KeyValue<K, V> {\n private keyToValue = new Map<K, V>()\n private valueToKey = new Map<V, K>()\n /**\n * `keys`和`values`数组中可以有相同的值, 若存在相同值, 则index越小的优先级越高\n */\n constructor(keys: K[], values: V[]) {\n assert(keys.length === values.length)\n // 倒序遍历, 确保index越小, 优先级越高\n for (let i = keys.length - 1; i >= 0; i--) {\n this.keyToValue.set(keys[i], values[i])\n this.valueToKey.set(values[i], keys[i])\n }\n }\n toKey(value: V): K {\n return this.valueToKey.get(value) ?? throwError()\n }\n toValue(key: K): V {\n return this.keyToValue.get(key) ?? throwError()\n }\n}\n\nexport type KeySelector<T, K> = (this: T) => K\n\n/** 用key快速查找元素的数组 */\nexport class FastFindValues<T> {\n private map = new Map<KeySelector<T, any>, Map<any, T>>()\n constructor(\n public values: readonly T[],\n private maxSize4Debug = 10,\n ) {\n noop()\n }\n\n /** 获取保存key-value映射的Map */\n getValueMap<K>(keySelector: KeySelector<T, K>): Map<K, T> {\n let valueMap = this.map.get(keySelector)\n if (!valueMap) {\n valueMap = new Map<K, T>()\n // 倒序遍历, 保证index越小, 优先级越高\n for (let i = this.values.length - 1; i >= 0; i--) {\n const value = this.values[i]\n valueMap.set(keySelector.call(value), value)\n }\n this.map.set(keySelector, valueMap)\n if (process.env.NODE_ENV !== 'production' && this.map.size > this.maxSize4Debug) {\n console.warn(`this.map的大小(${this.map.size})超过了设置的最大大小(${this.maxSize4Debug}), 这可能是由于你每次都创建新的keySelector造成的, 请保证相同功能的keySelector只创建一个对象`)\n }\n }\n return valueMap\n }\n\n /**\n * 直接用key获取value\n * @param keySelector 从T中读取key的方法, 会用它当key, 缓存结果, 故多次调用需要使用相同的keySelector对象, 不要每次创建新的, 推荐直接使用T.prototype.getXxx作为keySelector\n */\n getOrNull<K>(keySelector: KeySelector<T, K>, key: K): T | undefined {\n return this.getValueMap(keySelector).get(key)\n }\n\n get<K>(keySelector: KeySelector<T, K>, key: K): T {\n return this.getOrNull(keySelector, key) ?? throwError(`key(${key})不存在与valueMap(${this.getValueMap(keySelector)})中`)\n }\n}\n\n/** 缓存的值 */\nexport class CachedValue<T> {\n private value: T | undefined\n constructor(private creator: () => T) { noop() }\n get(): T {\n if (this.value === undefined) {\n this.value = this.creator()\n }\n return this.value\n }\n}\n","import { noop } from 'tg-commons'\nimport { KeyValue } from '../../../utils/values'\nimport type { UnionControl } from '../../unions'\nimport type { BaiduMap } from '../baidu-map'\nimport type { GoogleMap } from '../google-map'\nimport type { AbstractMap } from '../map'\n\nexport enum ControlPosition {\n /** 左上 */\n TOP_LEFT = 'top-left',\n TOP_CENTER = 'top-center',\n /** 右上 */\n TOP_RIGHT = 'top-right',\n BOTTOM_LEFT = 'bottom-left',\n BOTTOM_CENTER = 'bottom-center',\n BOTTOM_RIGHT = 'bottom-right',\n LEFT_TOP = 'left-top',\n LEFT_CENTER = 'left-center',\n /** 左下 */\n LEFT_BOTTOM = 'left-bottom',\n RIGHT_TOP = 'right-top',\n RIGHT_CENTER = 'right-center',\n /** 右下 */\n RIGHT_BOTTOM = 'right-bottom',\n}\n\nexport namespace ControlPositionConverter {\n let baiduKeyValue: KeyValue<ControlPosition, BMap.ControlAnchor> | undefined\n let googleKeyValue: KeyValue<ControlPosition, google.maps.ControlPosition> | undefined\n const controlPositionValues = [\n ControlPosition.TOP_LEFT,\n ControlPosition.TOP_CENTER,\n ControlPosition.TOP_RIGHT,\n ControlPosition.BOTTOM_LEFT,\n ControlPosition.BOTTOM_CENTER,\n ControlPosition.BOTTOM_RIGHT,\n ControlPosition.LEFT_TOP,\n ControlPosition.LEFT_CENTER,\n ControlPosition.LEFT_BOTTOM,\n ControlPosition.RIGHT_TOP,\n ControlPosition.RIGHT_CENTER,\n ControlPosition.RIGHT_BOTTOM,\n ]\n export function getBaidu() {\n if (!baiduKeyValue) {\n // 延迟初始化, 防止取不到`BMAP_*`的值\n // 反向映射存在一对多的情况, 取index最小的那个值\n baiduKeyValue = new KeyValue(controlPositionValues, [\n /* | @tg/map |名字相同| baidu | */\n /* TOP_LEFT = */ BMAP_ANCHOR_TOP_LEFT,\n /* TOP_CENTER */ BMAP_ANCHOR_TOP_LEFT,\n /* TOP_RIGHT = */ BMAP_ANCHOR_TOP_RIGHT,\n /* BOTTOM_LEFT = */ BMAP_ANCHOR_BOTTOM_LEFT,\n /* BOTTOM_CENTER */ BMAP_ANCHOR_BOTTOM_LEFT,\n /* BOTTOM_RIGHT = */ BMAP_ANCHOR_BOTTOM_RIGHT,\n /* LEFT_TOP */ BMAP_ANCHOR_TOP_LEFT,\n /* LEFT_CENTER */ BMAP_ANCHOR_BOTTOM_LEFT,\n /* LEFT_BOTTOM */ BMAP_ANCHOR_BOTTOM_LEFT,\n /* RIGHT_TOP */ BMAP_ANCHOR_TOP_RIGHT,\n /* RIGHT_CENTER */ BMAP_ANCHOR_BOTTOM_RIGHT,\n /* RIGHT_BOTTOM */ BMAP_ANCHOR_BOTTOM_RIGHT,\n ])\n }\n return baiduKeyValue\n }\n export function getGoogle() {\n if (!googleKeyValue) {\n // 同上\n googleKeyValue = new KeyValue(controlPositionValues, [\n google.maps.ControlPosition.TOP_LEFT,\n google.maps.ControlPosition.TOP_CENTER,\n google.maps.ControlPosition.TOP_RIGHT,\n google.maps.ControlPosition.BOTTOM_LEFT,\n google.maps.ControlPosition.BOTTOM_CENTER,\n google.maps.ControlPosition.BOTTOM_RIGHT,\n google.maps.ControlPosition.LEFT_TOP,\n google.maps.ControlPosition.LEFT_CENTER,\n google.maps.ControlPosition.LEFT_BOTTOM,\n google.maps.ControlPosition.RIGHT_TOP,\n google.maps.ControlPosition.RIGHT_CENTER,\n google.maps.ControlPosition.RIGHT_BOTTOM,\n ])\n }\n return googleKeyValue\n }\n}\n\n/**\n * 自定义的Control\n * @see BaiduMap.addCustomControl\n * @see GoogleMap.addCustomControl\n * */\nexport abstract class CustomControl {\n /** 实际添加到地图中的control对象 */\n inner?: UnionControl\n constructor(\n public position: ControlPosition,\n ) {\n noop()\n }\n /** 创建UI元素 */\n abstract onCreateElement(): HTMLElement\n}\n\n/** 内置的Control */\nexport abstract class AbstractControl {\n abstract isVisible(): boolean\n abstract setVisible(visible: boolean): void\n abstract getPosition(): ControlPosition\n abstract setPosition(position: ControlPosition): void\n abstract addTo(map: AbstractMap): void\n abstract removeFrom(map: AbstractMap): void\n}\n\n/** Control的空实现 */\nexport class EmptyControl extends AbstractControl {\n constructor(\n private visible: boolean,\n private position: ControlPosition,\n ) {\n super()\n }\n override isVisible(): boolean {\n return this.visible\n }\n override setVisible(visible: boolean): void {\n this.visible = visible\n }\n override getPosition(): ControlPosition {\n return this.position\n }\n override setPosition(position: ControlPosition): void {\n this.position = position\n }\n override addTo(_map: AbstractMap): void {\n }\n override removeFrom(_map: AbstractMap): void {\n }\n}\n\n/**\n * baidu内置的Control\n * - NavigationControl\n * - OverviewMapControl\n * - ScaleControl\n * - MapTypeControl\n * - CopyrightControl\n * - GeolocationControl\n */\nexport class BaiduControl<CONTROL extends BMap.Control = BMap.Control> extends AbstractControl {\n constructor(\n private inner: CONTROL,\n /** 由于@tg/map的position和baidu的position是多对一的关系, 为了防止set后再get, 得到不同的值, 应该用this.position保存set的值 */\n private position?: ControlPosition,\n ) {\n super()\n }\n override isVisible() {\n return this.inner.isVisible()\n }\n override setVisible(visible: boolean) {\n visible ? this.inner.show() : this.inner.hide()\n }\n override getPosition(): ControlPosition {\n return this.position ?? ControlPositionConverter.getBaidu().toKey(this.inner.getAnchor())\n }\n override setPosition(position: ControlPosition): void {\n this.position = position\n this.inner.setAnchor(ControlPositionConverter.getBaidu().toValue(position))\n }\n override addTo(map: BaiduMap): void {\n map.innerMap.addControl(this.inner)\n }\n override removeFrom(map: BaiduMap): void {\n map.innerMap.removeControl(this.inner)\n }\n}\n\nexport type GoogleControlType = 'zoom' | 'mapType' | 'scale' | 'streetView' | 'rotate' | 'fullscreen'\n\n/**\n * google内置的Control\n * - ZoomControl\n * - MapTypeControl\n * - ScaleControl\n * - StreetViewControl\n * - RotateControl\n * - FullscreenControl\n */\nexport class GoogleControl extends AbstractControl {\n visible = false\n private map: GoogleMap | undefined\n constructor(\n private _type: GoogleControlType,\n /** 每种类型的position的初始值不一样, 需要外部传入 */\n private position: ControlPosition,\n ) {\n super()\n }\n override isVisib