@sl-utils/map 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,283 @@
1
+ import { u_arrItemDel, u_mapGetPointByLatlng } from "../utils/slu-map";
2
+ import rbush from 'rbush'
3
+ /** 地图canvas事件类*/
4
+ export class MapCanvasEvent {
5
+ /**地图事件控制类 */
6
+ constructor(map: AMAP.Map | L.Map) {
7
+ this.map = map;
8
+ this._eventSwitch(true);
9
+ this.map.on('moveend', this.resetRbush);
10
+ this.map.on('zoomend', this.resetRbush);
11
+ }
12
+ /**R树搜索 事件 */
13
+ private rbush = new rbush();
14
+ /**是否重新开始事件指针变化(使不同canvas的事件指针能正确显示)*/
15
+ private static ifInitCursor: boolean = true;
16
+ /**是否开启事件控制类初始化 */
17
+ private static ifInit: boolean = true;
18
+ /**地图销毁必须调用此方法,否则事件指针会异常 */
19
+ public static destory() {
20
+ MapCanvasEvent.ifInit = true;
21
+ }
22
+ private static initCursor() {
23
+ MapCanvasEvent.ifInitCursor = true
24
+ }
25
+ protected map: AMAP.Map | L.Map;
26
+ /** 监听事件 */
27
+ protected _listenCbs: { [key in SLTEventType]?: ((e: MapEventResponse<any>) => void)[] } = Object.create(null);
28
+ /** key 防止setEvent清除其他事件 */
29
+ public _allMapEvents: Map<string, MapEvent[]> = new Map();
30
+ /** Rbush查询子集 */
31
+ private _allRbush: SLTRbush<MapEvent>[] = [];
32
+ /** 上一次触发的事件集合 */
33
+ private perEvents: MapEventResponse[] = [];
34
+ /** 海图事件回调函数 */
35
+ private cbMapEvent = (e: MapEventResponse) => {
36
+ let { cb, cbs } = e.event;
37
+ if (cb) { cb(e); return; }
38
+ if (cbs) { cbs[e.type]?.(e); return }
39
+ /**响应的事件类型集合 */
40
+ let _cbs = this._listenCbs[e.type] || [];
41
+ _cbs.map(cb => cb(e));
42
+ };
43
+ /** 事件开关
44
+ * @param flag true开启地图事件监听 false关闭地图事件监听
45
+ */
46
+ private _eventSwitch(flag: boolean) {
47
+ // this.map.off('mousemove', MapCanvasEvent.initCursor)
48
+ // this.map.on('mousemove', MapCanvasEvent.initCursor)
49
+ if (MapCanvasEvent.ifInit) {
50
+ MapCanvasEvent.ifInit = false;
51
+ this.map.on('mousemove', () => { MapCanvasEvent.ifInitCursor = true })
52
+ }
53
+ let types = ['click', 'dblclick', 'mousemove', 'mousedown', 'mouseup', 'rightclick'];
54
+ types.map(e => {
55
+ this.map[flag ? 'on' : 'off'](e, this.triggerEvent)
56
+ })
57
+ }
58
+ /**重设rbush */
59
+ private resetRbush = () => {
60
+ if (this.rbush) this.rbush.clear();
61
+ // 先暂时取消监听所有事件
62
+ this._eventSwitch(false);
63
+ this._allRbush = [];
64
+ this._allMapEvents.forEach(evs => {
65
+ evs.forEach(ev => {
66
+ this.transformRbush(ev);
67
+ })
68
+ })
69
+ this.rbush.load(this._allRbush);
70
+ // 设置完成重新监听
71
+ this._eventSwitch(true);
72
+ }
73
+ /**统一监听该类的指定事件 */
74
+ public on<T extends MapEvent<any>>(type: SLTEventType, cb: (e: MapEventResponse<T>) => void) {
75
+ let cbs = this._listenCbs[type] = this._listenCbs[type] || [];
76
+ cbs.push(cb);
77
+ }
78
+ /**统一关闭指定事件的监听 */
79
+ public off<T extends MapEvent<any>>(type: SLTEventType, cb?: (e: MapEventResponse<T>) => void) {
80
+ let cbs = this._listenCbs[type] = this._listenCbs[type] || [];
81
+ if (cb) {
82
+ u_arrItemDel(cbs, cb);
83
+ } else {
84
+ this._listenCbs[type] = [];
85
+ }
86
+ }
87
+ /**清空之前设置的统一监听事件 */
88
+ public clear() {
89
+ this._listenCbs = Object.create(null);
90
+ }
91
+ /**
92
+ * @param evs 事件集合
93
+ * @param key 事件key
94
+ * 设置key 事件 会覆盖原来的事件
95
+ * 不覆盖使用 pushEventByKey
96
+ * */
97
+ public setEventsByKey<T extends MapEvent>(evs: T[], key: string) {
98
+ /**ifHide不显示,事件就不添加 */
99
+ this._allMapEvents.set(key, evs.filter(ev => !ev.ifHide));
100
+ this._allRbush = [];
101
+ this.rbush.clear();
102
+ // map所有事件
103
+ this._allMapEvents.forEach((evs) => {
104
+ evs.forEach(e => this.handleTransform(e));
105
+ });
106
+ this.rbush.load(this._allRbush);
107
+ }
108
+ /**
109
+ * 清除所有事件
110
+ */
111
+ public clearAllEvents() {
112
+ this._allMapEvents = new Map();
113
+ this._allRbush = [];
114
+ this.rbush.clear();
115
+ }
116
+ /**
117
+ * 清除指定类型事件
118
+ * @param key
119
+ */
120
+ public clearEventsByKey(key: string) {
121
+ this.setEventsByKey([], key);
122
+ }
123
+ /**
124
+ * 添加一个事件
125
+ * 尽量使用setEventsByKey
126
+ * 或者pushEventByKey数组 而不是for 一个个push
127
+ * 不然每次for循环push都会重新构造rbush
128
+ * */
129
+ public pushEventByKey<T extends MapEvent>(key: string, ev: T | T[]) {
130
+ if (!this._allMapEvents.has(key)) this._allMapEvents.set(key, []);
131
+ const eves = this._allMapEvents.get(key);
132
+ Array.isArray(ev) ? eves.push(...ev) : eves.push(ev);
133
+ this.setEventsByKey(eves, key);
134
+ }
135
+ /** 添加事件 */
136
+ private handleTransform<T extends MapEvent>(ev: T) {
137
+ this.transformEvent(ev);
138
+ this.transformRbush(ev);
139
+ }
140
+ /** 转换添加事件 */
141
+ private transformEvent<T extends MapEvent>(event: T) {
142
+ /**ifHide不显示,事件就不添加 */
143
+ if (event.ifHide === true) return;
144
+ let ev: MapEvent = {
145
+ latlng: event.latlng || undefined,
146
+ latlngs: event.latlngs || [],
147
+ type: event.type,
148
+ info: event.info,
149
+ cb: event.cb
150
+ }
151
+ // let ev = this.genDefaultMapEvent(event, event)
152
+ }
153
+ /** 转为Rbush数据格式 */
154
+ private transformRbush<T extends MapEvent>(event: T) {
155
+ /**ifHide不显示,事件就不添加 */
156
+ if (event.ifHide === true) return;
157
+ let { range = [5, 5], latlng, latlngs = [], left = 0, top = 0 } = event;
158
+ if (latlng && latlng.length === 2) latlngs = [...latlngs, latlng];
159
+ latlngs.forEach(latlng => {
160
+ const [lat, lng] = latlng;
161
+ let [onX, onY] = u_mapGetPointByLatlng(this.map, latlng);
162
+ let item: SLTRbush = {
163
+ minX: onX - range[0] + left,
164
+ minY: onY - range[1] + top,
165
+ maxX: onX + range[0] + left,
166
+ maxY: onY + range[1] + top,
167
+ data: event
168
+ }
169
+ this._allRbush.push(item)
170
+ })
171
+
172
+ }
173
+ /**准备触发事件
174
+ * @param e 地图事件
175
+ */
176
+ private triggerEvent = (e: AMapMapsEvent | L.LeafletMouseEvent): void => {
177
+ let allEvents: MapEvent<any, any>[] = []
178
+ this._allMapEvents.forEach(eves => {
179
+ allEvents = allEvents.concat(eves);
180
+ });
181
+ let style: any = (document.querySelector('#map')! as HTMLElement).style;
182
+ style.cursor = MapCanvasEvent.ifInitCursor ? 'default' : style.cursor;
183
+ if (allEvents.length === 0) return;
184
+ let { curEvents, enterEvents, leaveEvents } = this.getEventsByRange(e);
185
+ enterEvents.forEach(resp => this.doCbByEventType(resp, 'mouseenter'))
186
+ leaveEvents.forEach(resp => this.doCbByEventType(resp, 'mouseleave'))
187
+ this.perEvents = curEvents;
188
+ if (curEvents.length == 0) return
189
+ MapCanvasEvent.ifInitCursor = false;
190
+ style.cursor = 'pointer';
191
+ curEvents.forEach(resp => this.doCbByEventType(resp, e.type as SLTEventType))
192
+ };
193
+ /**获取指针触发范围内的事件 */
194
+ private getEventsByRange(e: AMapMapsEvent | L.LeafletMouseEvent) {
195
+ let lng, lat, x, y, pageX, pageY, zoom = this.map.getZoom();
196
+ if ((e as L.LeafletMouseEvent).latlng) {
197
+ let event: L.LeafletMouseEvent = e as L.LeafletMouseEvent
198
+ ({ lng, lat } = event.latlng, { x, y } = event.containerPoint, { pageX, pageY } = event.originalEvent);
199
+ } else {
200
+ let event: AMapMapsEvent = e as AMapMapsEvent
201
+ ({ lng, lat } = event.lnglat, { x, y } = event.pixel, { pageX, pageY } = event.originEvent);
202
+ }
203
+ /**鼠标位置信息 */
204
+ let cursor: MapCursorInfo = { latlng: [lat, lng], page: [pageX, pageY], point: [x, y], }
205
+ /** curEvents 当前位置存在的所有事件 enterEvents 鼠标首次进入事件集合 leaveEvents 鼠标离开事件集合 */
206
+ let curEvents: MapEventResponse[] = [], enterEvents: MapEventResponse[] = [], leaveEvents: MapEventResponse[] = this.perEvents;
207
+ let curr = new Date()
208
+ if (e.type == 'click')
209
+ console.time('start');
210
+ // rbush查找
211
+ let ret = this.rbush.search({ minX: x, minY: y, maxX: x, maxY: y }) as SLTRbush<MapEvent>[]
212
+ ret.forEach(res => {
213
+ let ev = res.data;
214
+ let { latlng, latlngs = [], range = [5, 5], left = 0, top = 0, minZoom = 1, maxZoom = 50 } = res.data
215
+ if (minZoom > zoom || maxZoom < zoom) return;
216
+ if (latlng && latlng.length === 2) latlngs = [...latlngs, latlng];
217
+ let [onX, onY] = u_mapGetPointByLatlng(this.map, latlng);
218
+ let eventRes = this.genEventResponse(latlng, [onX, onY], ev, cursor);
219
+ curEvents.push(eventRes);
220
+ /**从之前的所有响应对象中查找是否存在位置一样的响应对象 */
221
+ let per = leaveEvents.find(e =>
222
+ e.position.latlng[0] === eventRes.position.latlng[0] && e.position.latlng[1] === eventRes.position.latlng[1]
223
+ );
224
+ if (per) {
225
+ /**存在则说明鼠标没有离开,则从离开事件集合中移除 */
226
+ u_arrItemDel(leaveEvents, per)
227
+ } else {
228
+ /**不存在则说明鼠标刚刚进入,则添加到进入事件集合 */
229
+ enterEvents.push(eventRes)
230
+ };
231
+ })
232
+ // for (let i = 0, len = allEvents.length; i < len; i++) {
233
+ // let ev = allEvents[i];
234
+ // let { latlng, latlngs = [], range = [5, 5], left = 0, top = 0, minZoom = 1, maxZoom = 50 } = ev;
235
+ // if (minZoom > zoom || maxZoom < zoom) continue;
236
+ // if (latlng && latlng.length === 2) latlngs = [...latlngs, latlng];
237
+ // let sizeX = range[0], sizeY = range[1];
238
+ // /**判断是否在范围内 */
239
+ // for (let p = 0; p < latlngs.length; p++) {
240
+ // let latlng = latlngs[p];
241
+ // let [onX, onY] = u_mapGetPointByLatlng(this.map, latlng);
242
+ // if ((onX - sizeX + left) <= x && x <= (onX + sizeX + left) && (onY - sizeY + top) <= y && y <= (onY + sizeY + top)) {
243
+ // /**当前响应对象 */
244
+ // let res = this.genEventResponse(latlng, [onX, onY], ev, cursor);
245
+ // curEvents.push(res);
246
+ // /**从之前的所有响应对象中查找是否存在位置一样的响应对象 */
247
+ // let per = leaveEvents.find(e =>
248
+ // e.position.latlng[0] === res.position.latlng[0] && e.position.latlng[1] === res.position.latlng[1]
249
+ // );
250
+ // if (per) {
251
+ // /**存在则说明鼠标没有离开,则从离开事件集合中移除 */
252
+ // u_arrItemDel(leaveEvents, per)
253
+ // } else {
254
+ // /**不存在则说明鼠标刚刚进入,则添加到进入事件集合 */
255
+ // enterEvents.push(res)
256
+ // };
257
+ // }
258
+ // }
259
+ // }
260
+ if (e.type == 'click')
261
+ console.timeEnd('start');
262
+ return { curEvents, enterEvents, leaveEvents }
263
+ }
264
+ /**通过事件类型执行回调函数*/
265
+ private doCbByEventType(resp: MapEventResponse, type: SLTEventType) {
266
+ let types = resp.event.type;
267
+ if (!Array.isArray(types)) types = [types];
268
+ if (!types.includes(type)) return;
269
+ resp.type = type;
270
+ this.cbMapEvent(resp)
271
+ }
272
+ /**生成地图事件响应对象
273
+ * @param latlng 该事件对象的地图坐标
274
+ * @param point 该事件对象的地图像素坐标
275
+ * @param event 地图事件
276
+ * @param cursor 鼠标位置信息
277
+ */
278
+ private genEventResponse(latlng: [number, number], point: [number, number], event: MapEvent, cursor: MapCursorInfo): MapEventResponse {
279
+ let pageX = point[0] + cursor.page[0] - cursor.point[0], pageY = point[1] + cursor.page[1] - cursor.point[1];
280
+ let position: MapCursorInfo = { latlng: latlng, page: [pageX, pageY], point: point, }
281
+ return { position, cursor, event, info: event.info ?? {}, type: 'unset' };
282
+ }
283
+ }
@@ -0,0 +1,191 @@
1
+ import * as L from "leaflet";
2
+ import { u_mapGetMapSize } from "../utils/slu-map";
3
+ declare var AMap: any;
4
+ /** 地图canvas基础图层类(基本所有插件都要继承此类) 删除永远比新增简单 */
5
+ export class MapCanvasLayer {
6
+ constructor(MAP: L.Map, opt?: MapCanvasPara)
7
+ constructor(MAP: AMAP.Map, opt?: AMAP.CustomLayerOption)
8
+ constructor(MAP: AMAP.Map | L.Map, opt?: AMAP.CustomLayerOption | MapCanvasPara)
9
+ constructor(map: AMAP.Map | L.Map, opt?: AMAP.CustomLayerOption | MapCanvasPara) {
10
+ this.map = map;
11
+ Object.assign(this.options, opt);
12
+ if (map instanceof L.Map) {
13
+ this.type = 0;
14
+ let layer = this.layer = new L.Layer(this.options);
15
+ this.layer.onAdd = () => { this.onAdd(); return layer }
16
+ } else if (map instanceof AMap.Map) {
17
+ this.type = 1;
18
+ opt = Object.assign({
19
+ zooms: [3, 18],
20
+ alwaysRender: false,//缩放过程中是否重绘,复杂绘制建议设为false
21
+ zIndex: 200,
22
+ }, opt);
23
+ this.layer = new AMap.CustomLayer(this.canvas, opt);
24
+ }
25
+ this.initCanvas();
26
+ this.onAdd();
27
+ }
28
+ /**开发自己设置项目使用了的地图插件类型Leaflet(0)、高德(1)、百度(2),防止网络加载的第三方插件再使用instanceof是为define*/
29
+ protected readonly type: 0 | 1 | 2;
30
+ protected readonly map!: AMAP.Map | L.Map;
31
+ private layer: L.Layer | AMAP.CustomLayer;
32
+ protected readonly canvas: HTMLCanvasElement = document.createElement('canvas');
33
+ protected readonly ctx: CanvasRenderingContext2D = this.canvas.getContext("2d")!;
34
+ protected width: number = 0;
35
+ protected height: number = 0;
36
+ public readonly options: SLPMap.Canvas = {
37
+ pane: 'canvas',
38
+ };
39
+ /**动画循环的id标识 */
40
+ protected flagAnimation: number = 0;
41
+ /**移除图层 */
42
+ public onRemove() {
43
+ const { flagAnimation } = this;
44
+ this._eventSwitch(false);
45
+ if (flagAnimation) cancelAnimationFrame(flagAnimation);
46
+ this._onAmapRemove();
47
+ this._onLeafletRemove();
48
+ return this;
49
+ }
50
+ /** 清空并重新设置画布 */
51
+ public resetCanvas(): void {
52
+ const { canvas, map } = this;
53
+ if (map instanceof L.Map) {
54
+ var topLeft = map.containerPointToLayerPoint([0, 0]);
55
+ L.DomUtil.setPosition(canvas, topLeft);
56
+ }
57
+ const { w, h } = u_mapGetMapSize(map);
58
+ canvas.style.width = w + 'px';
59
+ canvas.style.height = h + 'px';
60
+ //清除画布
61
+ this.width = canvas.width = w;
62
+ this.height = canvas.height = h;
63
+ }
64
+ /**添加或关闭地图特定的监听事件(_eventSwitch事件后自动调用) */
65
+ protected addMapEvents(map: AMAP.Map | L.Map, key: 'on' | 'off') { }
66
+ /**绘制静态数据推荐使用此方法(固定的图) */
67
+ protected renderFixedData() { };
68
+ /** 推荐使用此方法绘制动态图(跟随鼠标拖动,移动时需要立刻绘制时)
69
+ ** 动画图层绘制前的画布清空、计算等均在此方法中自行计算
70
+ ** 与renderFixedData本质是一样的
71
+ */
72
+ protected renderAnimation() { };
73
+ /** */
74
+ protected on(key: string, cb: Function) {
75
+ this.map.on(key, (e) => { cb() })
76
+ }
77
+ /** */
78
+ protected off(key: string, cb: Function) {
79
+ this.map.off(key, (e) => { cb() })
80
+ }
81
+ /**初始化canvas */
82
+ private initCanvas() {
83
+ const { canvas, map, type, options, layer } = this;
84
+ canvas.className = `sl-layer ${options.className || 'sl-canvas-map'}`;
85
+ canvas.style['zIndex'] = `${options.zIndex || 100}`;
86
+ canvas.style['transformOrigin'] = '50% 50%';
87
+ this.initLeafletCanvas();
88
+ }
89
+ /** 将图层添加到map实例中显示 */
90
+ private onAdd() {
91
+ this._onAmapAdd();
92
+ this._eventSwitch(true);
93
+ let layer: any = this.layer
94
+ layer['render'] = this._redraw;
95
+ return this;
96
+ }
97
+ /**基础的监听事件
98
+ * @param flag true开启重绘事件监听 false 关闭重绘事件监听
99
+ **/
100
+ private _eventSwitch(flag: boolean = true) {
101
+ let map = this.map;
102
+ let key: 'on' | 'off' = flag ? 'on' : 'off';
103
+ this.addLeafletEvent(flag);
104
+ this.addMapEvents(map, key);
105
+ }
106
+ /**基础绘制 */
107
+ /** 重绘(子类重写也无效)
108
+ ** 清空之前的绘制
109
+ ** ①高德地图渲染配置alwaysRender:true后拖动缩放会多次渲染
110
+ */
111
+ protected _redraw = () => {
112
+ console.log('##########--------MapCanvasLayer=>_redraw--------##########')
113
+ if (!this.map) return;
114
+ this.resetCanvas();
115
+ this.renderFixedData();
116
+ this.renderAnimation();
117
+ };
118
+ /**------------------------------高德地图的实现------------------------------*/
119
+ private _onAmapAdd() {
120
+ const { map, layer, type } = this;
121
+ if (type === 1) {
122
+ (layer as AMAP.CustomLayer).setMap(map as AMAP.Map);
123
+ }
124
+ }
125
+ private _onAmapRemove() {
126
+ const { map, layer, type } = this;
127
+ if (type === 1) {
128
+ map.remove(layer as AMAP.CustomLayer);
129
+ // (layer as AMAP.CustomLayer).destroy();
130
+ }
131
+ }
132
+ /**------------------------------Leaflet地图的实现------------------------------*/
133
+ /**初始化画布并添加到Pane中 */
134
+ private initLeafletCanvas() {
135
+ const { canvas, map, type, options } = this;
136
+ if (type || !(map instanceof L.Map)) return;
137
+ let pane = options.pane || 'overlayPane', paneEle = map.getPane(pane) || map.createPane(pane);
138
+ /**如果指定的pane不存在就自己创建(往map添加div Pane) */
139
+ paneEle.appendChild(canvas);
140
+ paneEle.style.pointerEvents = 'none';
141
+ let animated = map.options.zoomAnimation && L.Browser.any3d;
142
+ L.DomUtil.addClass(canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide'));
143
+ L.extend(canvas, {
144
+ onselectstart: L.Util.falseFn,
145
+ onmousemove: L.Util.falseFn,
146
+ onload: L.bind(this._onCanvasLoad, this),
147
+ });
148
+ }
149
+ /**移除 */
150
+ private _onLeafletRemove() {
151
+ let { map, layer, options, type } = this;
152
+ if (type == 0) {
153
+ let pane = options.pane;
154
+ pane && (map as L.Map).getPane(pane)?.removeChild(this.canvas);
155
+ (layer as L.Layer).remove();
156
+ }
157
+ }
158
+ private addLeafletEvent(flag: boolean = true) {
159
+ let map = this.map;
160
+ if (map instanceof L.Map) {
161
+ /**为了和高德保持一致,初始化后渲染一次 */
162
+ requestAnimationFrame(() => this._reset());
163
+ let key: 'on' | 'off' = flag ? 'on' : 'off';
164
+ map[key]('viewreset', this._reset, this);
165
+ map[key]('resize', this._reset, this);
166
+ map[key]('moveend', this._reset, this);
167
+ if (map.options.zoomAnimation && L.Browser.any3d) {
168
+ /**缩放动画 */
169
+ map[key]('zoomanim', this._animateZoom, this);
170
+ }
171
+ };
172
+ }
173
+ /**重设画布,并重新渲染*/
174
+ private _reset() {
175
+ this.resetCanvas();
176
+ this._redraw();
177
+ }
178
+ /**缩放动画 */
179
+ private _animateZoom(e: any) {
180
+ let map: any = this.map;
181
+ var scale = map.getZoomScale(e.zoom),
182
+ offset = map._getCenterOffset(e.center)._multiplyBy(-scale).subtract(map._getMapPanePos());
183
+ L.DomUtil.setTransform(this.canvas, offset, scale);
184
+ }
185
+ private _onCanvasLoad() {
186
+ if (this.layer instanceof L.Layer) this.layer.fire('load');
187
+ }
188
+
189
+
190
+
191
+ }