tg-map-echarts 4.3.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.
- package/README.md +43 -0
- package/dist/src/extension/TgMapCoordSys.d.ts +46 -0
- package/dist/src/extension/TgMapModel.d.ts +51 -0
- package/dist/src/extension/TgMapView.d.ts +16 -0
- package/dist/src/extension/TgMapViewportState.d.ts +21 -0
- package/dist/src/extension/__tests__/TgMapCoordSys.test.d.ts +1 -0
- package/dist/src/extension/install.d.ts +4 -0
- package/dist/src/extension/types.d.ts +38 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/tg-map-echarts.cjs +409 -0
- package/dist/tg-map-echarts.mjs +407 -0
- package/docs/migration-from-bmap.md +508 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# tg-map-echarts
|
|
2
|
+
|
|
3
|
+
基于 `tg-map-core` 的 ECharts 5 地图坐标系扩展,支持 Baidu、Google 和 Talks 地图引擎。
|
|
4
|
+
|
|
5
|
+
从 ECharts 官方 `bmap` 扩展迁移,请参阅[迁移指南](./docs/migration-from-bmap.md)。
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import * as echarts from 'echarts'
|
|
9
|
+
import { type BaseMap, CoordType, GestureHandlingOptions, loadCachedMap, TgMapType } from 'tg-map-core'
|
|
10
|
+
import { TgMapExtension } from 'tg-map-echarts'
|
|
11
|
+
|
|
12
|
+
// 显式安装一次扩展;包本身不会修改全局ECharts实例。
|
|
13
|
+
echarts.use(TgMapExtension)
|
|
14
|
+
// 必须在setOption前完成对应地图引擎的加载。
|
|
15
|
+
await loadCachedMap(TgMapType.talks)
|
|
16
|
+
|
|
17
|
+
const chart = echarts.init(document.getElementById('chart')!)
|
|
18
|
+
chart.setOption({
|
|
19
|
+
tgMap: {
|
|
20
|
+
engine: TgMapType.talks,
|
|
21
|
+
mapOptions: {
|
|
22
|
+
center: { lng: 116.4, lat: 39.9, coord: CoordType.wgs84 },
|
|
23
|
+
zoom: 12,
|
|
24
|
+
gestureHandling: GestureHandlingOptions.greedy,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
series: [{
|
|
28
|
+
type: 'effectScatter',
|
|
29
|
+
coordinateSystem: 'tgMap',
|
|
30
|
+
data: [[116.4, 39.9, 10]],
|
|
31
|
+
}],
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`dataCoordType` 默认为 `wgs84`。业务数据不需要预先转换为地图引擎使用的坐标系,扩展会将 ECharts 的 `[lng, lat, ...]` 数据包装为 `LatLng` 后交给 `tg-map-core` 转换。
|
|
36
|
+
|
|
37
|
+
可以从组件模型取得 `tg-map-core` 地图实例:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const map: BaseMap = chart.getModel().getComponent('tgMap').getMap()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
每个 ECharts 实例只支持一个 `tgMap`。切换 Baidu、Google、Talks 引擎时,应由 Vue 层先 `dispose()` 当前 ECharts 实例,再等待 `loadCachedMap(newEngine)` 并重新创建图表。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { graphic } from 'echarts/core';
|
|
2
|
+
import { CoordType, type BaseMap } from 'tg-map-core';
|
|
3
|
+
import type { CoordinateSystem, CoordinateSystemCreator, ExtensionAPI } from './types';
|
|
4
|
+
type NumericData = [lng: number, lat: number];
|
|
5
|
+
export default class TgMapCoordSys implements CoordinateSystem {
|
|
6
|
+
private map;
|
|
7
|
+
private api;
|
|
8
|
+
private dataCoordType;
|
|
9
|
+
static dimensions: string[];
|
|
10
|
+
static create: CoordinateSystemCreator['create'];
|
|
11
|
+
readonly type = "tgMap";
|
|
12
|
+
readonly dimensions: string[];
|
|
13
|
+
constructor(map: BaseMap, api: ExtensionAPI, dataCoordType: CoordType);
|
|
14
|
+
getMap(): BaseMap;
|
|
15
|
+
dataToPoint(data: NumericData, _clamp?: boolean, out?: number[]): number[];
|
|
16
|
+
pointToData(point: number[]): number[];
|
|
17
|
+
containPoint(point: number[]): boolean;
|
|
18
|
+
getViewRect(): graphic.BoundingRect;
|
|
19
|
+
getRoamTransform(): number[];
|
|
20
|
+
prepareCustoms(): {
|
|
21
|
+
coordSys: {
|
|
22
|
+
type: string;
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
api: {
|
|
29
|
+
coord: (data: NumericData, _clamp?: boolean, out?: number[]) => number[];
|
|
30
|
+
size: (dataSize: NumericData, dataItem?: NumericData) => number[];
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
private dataToCoordSize;
|
|
34
|
+
/**
|
|
35
|
+
* 创建的DOM层级:
|
|
36
|
+
* ```
|
|
37
|
+
* root/viewportParent echarts的元素, viewportRoot原本的parent
|
|
38
|
+
* |--mapRoot class=ec-extension-tg-map
|
|
39
|
+
* |--pane
|
|
40
|
+
* |--viewportRoot painter的viewportRoot
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
private static createMapRuntime;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=TgMapCoordSys.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ComponentModel } from 'echarts/core';
|
|
2
|
+
import { CoordType, LatLng, TgMapType, type BaseMap, type ElementOverlay } from 'tg-map-core';
|
|
3
|
+
import type TgMapCoordSys from './TgMapCoordSys';
|
|
4
|
+
import type { ResolvedTgMapComponentOption } from './types';
|
|
5
|
+
export declare const DYNAMIC_UI_OPTION_KEYS: readonly ["gestureHandling", "minZoom", "maxZoom", "mapStyle", "hideLogo", "fractionalZoom"];
|
|
6
|
+
type DynamicOptionKey = typeof DYNAMIC_UI_OPTION_KEYS[number] | 'buildInMapTypeId';
|
|
7
|
+
export interface TgMapRuntime {
|
|
8
|
+
map: BaseMap;
|
|
9
|
+
mapRoot: HTMLElement;
|
|
10
|
+
overlay: ElementOverlay;
|
|
11
|
+
viewportRoot: HTMLElement;
|
|
12
|
+
viewportBackup: {
|
|
13
|
+
parent: Node;
|
|
14
|
+
nextSibling: Node | null;
|
|
15
|
+
className: string;
|
|
16
|
+
position: string;
|
|
17
|
+
left: string;
|
|
18
|
+
top: string;
|
|
19
|
+
getViewportRootOffset: () => {
|
|
20
|
+
offsetLeft: number;
|
|
21
|
+
offsetTop: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
syncViewportPosition: () => void;
|
|
25
|
+
applyingOptions: boolean;
|
|
26
|
+
animationFrameId?: number;
|
|
27
|
+
dynamicOptionSnapshots: Map<DynamicOptionKey, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export default class TgMapModel extends ComponentModel<ResolvedTgMapComponentOption> {
|
|
30
|
+
static type: string;
|
|
31
|
+
readonly type: string;
|
|
32
|
+
static defaultOption: ResolvedTgMapComponentOption;
|
|
33
|
+
coordinateSystem?: TgMapCoordSys;
|
|
34
|
+
private runtime?;
|
|
35
|
+
getEngine(): TgMapType;
|
|
36
|
+
getDataCoordType(): CoordType;
|
|
37
|
+
getMapOptions(): Omit<import("tg-map-core").MapOptions, "mapStyle" | "center"> & {
|
|
38
|
+
center: import("tg-map-core").LatLngLiteral;
|
|
39
|
+
mapStyle?: string;
|
|
40
|
+
};
|
|
41
|
+
getMap(): BaseMap;
|
|
42
|
+
getRuntime(): TgMapRuntime | undefined;
|
|
43
|
+
setRuntime(runtime: TgMapRuntime | undefined): void;
|
|
44
|
+
setCenterAndZoom(center: LatLng, zoom: number): void;
|
|
45
|
+
/** 应用坐标相关选项到地图 */
|
|
46
|
+
applyCoordinateOptions(runtime: TgMapRuntime): void;
|
|
47
|
+
/** 应用UI相关选项到地图 */
|
|
48
|
+
applyUIOptions(runtime: TgMapRuntime): void;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=TgMapModel.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ComponentView } from 'echarts/core';
|
|
2
|
+
import TgMapModel from './TgMapModel';
|
|
3
|
+
import type { ExtensionAPI, GlobalModel } from './types';
|
|
4
|
+
export default class TgMapView extends ComponentView {
|
|
5
|
+
static type: string;
|
|
6
|
+
readonly type: string;
|
|
7
|
+
private model?;
|
|
8
|
+
private map?;
|
|
9
|
+
private rendering;
|
|
10
|
+
private changeHandler?;
|
|
11
|
+
render(model: TgMapModel, _ecModel: GlobalModel, api: ExtensionAPI): void;
|
|
12
|
+
dispose(_ecModel: GlobalModel, api: ExtensionAPI): void;
|
|
13
|
+
private bindMapEvents;
|
|
14
|
+
private unbindMapEvents;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=TgMapView.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface TgMapViewportPainter {
|
|
2
|
+
getViewportRoot(): HTMLElement;
|
|
3
|
+
getViewportRootOffset(): {
|
|
4
|
+
offsetLeft: number;
|
|
5
|
+
offsetTop: number;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/** 保存ECharts viewport的原始状态,并负责切换和恢复地图Overlay状态。 */
|
|
9
|
+
export declare class TgMapViewportState {
|
|
10
|
+
private painter;
|
|
11
|
+
readonly root: HTMLElement;
|
|
12
|
+
private readonly parent;
|
|
13
|
+
private readonly nextSibling;
|
|
14
|
+
private readonly className;
|
|
15
|
+
private readonly style;
|
|
16
|
+
private readonly getViewportRootOffset;
|
|
17
|
+
constructor(painter: TgMapViewportPainter);
|
|
18
|
+
applyOverlayState(): void;
|
|
19
|
+
restore(): void;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=TgMapViewportState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ComponentModel, registerCoordinateSystem, SeriesModel, use } from 'echarts/core';
|
|
2
|
+
import type { CoordType, DeepPartial, LatLngLiteral, MapOptions, TgMapType } from 'tg-map-core';
|
|
3
|
+
export type TgMapComponentOption = DeepPartial<ResolvedTgMapComponentOption>;
|
|
4
|
+
/**
|
|
5
|
+
* 参考maptalks, 所有自定义的必选字段都通过`defaultOption`设置默认值, 外部设置option时, 全部字段都可选, 这样在读取的时候, 就可以省略判空
|
|
6
|
+
* 注意, 如果用户主动将字段设为null/undefined, 字段依然会读到null/undefined, 而不是默认值
|
|
7
|
+
* 故这种方式并不严谨, 但可以省略一大堆空值判断
|
|
8
|
+
*/
|
|
9
|
+
export interface ResolvedTgMapComponentOption extends ComponentOption {
|
|
10
|
+
mainType?: 'tgMap';
|
|
11
|
+
/**
|
|
12
|
+
* 地图引擎。SDK需要在setOption前通过loadCachedMap加载完成。
|
|
13
|
+
* @default TgMapType.google
|
|
14
|
+
*/
|
|
15
|
+
engine: TgMapType;
|
|
16
|
+
mapOptions: Omit<MapOptions, 'center' | 'mapStyle'> & {
|
|
17
|
+
/** ECharts会深拷贝option,因此中心点使用可序列化的LatLngLiteral。 */
|
|
18
|
+
center: LatLngLiteral;
|
|
19
|
+
/** Echarts会深度合并option, 对于mapStyle这种大对象, 直接转成JSON, 可以避免合并 */
|
|
20
|
+
mapStyle?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* series中[lng, lat, ...]数据的坐标类型。
|
|
24
|
+
* @default CoordType.wgs84
|
|
25
|
+
*/
|
|
26
|
+
dataCoordType: CoordType;
|
|
27
|
+
}
|
|
28
|
+
export type EChartsExtension = Extract<Parameters<typeof use>[0], {
|
|
29
|
+
install: unknown;
|
|
30
|
+
}>;
|
|
31
|
+
export type CoordinateSystemCreator = Parameters<typeof registerCoordinateSystem>[1];
|
|
32
|
+
type CoordinateSystemCreatorParameters = Parameters<CoordinateSystemCreator['create']>;
|
|
33
|
+
export type ExtensionAPI = CoordinateSystemCreatorParameters[1];
|
|
34
|
+
export type GlobalModel = CoordinateSystemCreatorParameters[0];
|
|
35
|
+
export type CoordinateSystem = SeriesModel['coordinateSystem'];
|
|
36
|
+
export type ComponentOption = ComponentModel['option'];
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('echarts/core');
|
|
4
|
+
var tgCommons = require('tg-commons');
|
|
5
|
+
var tgMapCore = require('tg-map-core');
|
|
6
|
+
|
|
7
|
+
const DYNAMIC_UI_OPTION_KEYS = [
|
|
8
|
+
'gestureHandling',
|
|
9
|
+
'minZoom',
|
|
10
|
+
'maxZoom',
|
|
11
|
+
'mapStyle',
|
|
12
|
+
'hideLogo',
|
|
13
|
+
'fractionalZoom',
|
|
14
|
+
];
|
|
15
|
+
class TgMapModel extends core.ComponentModel {
|
|
16
|
+
static type = 'tgMap';
|
|
17
|
+
type = TgMapModel.type;
|
|
18
|
+
static defaultOption = {
|
|
19
|
+
engine: tgMapCore.TgMapType.google,
|
|
20
|
+
mapOptions: {
|
|
21
|
+
center: { lat: 0, lng: 0, coord: tgMapCore.COORD_TYPE_DEFAULT },
|
|
22
|
+
zoom: tgMapCore.MapZoom.STATE,
|
|
23
|
+
},
|
|
24
|
+
dataCoordType: tgMapCore.COORD_TYPE_DEFAULT,
|
|
25
|
+
};
|
|
26
|
+
coordinateSystem;
|
|
27
|
+
runtime;
|
|
28
|
+
getEngine() {
|
|
29
|
+
return this.option.engine;
|
|
30
|
+
}
|
|
31
|
+
getDataCoordType() {
|
|
32
|
+
return this.option.dataCoordType;
|
|
33
|
+
}
|
|
34
|
+
getMapOptions() {
|
|
35
|
+
return this.option.mapOptions;
|
|
36
|
+
}
|
|
37
|
+
getMap() {
|
|
38
|
+
if (!this.runtime) {
|
|
39
|
+
throw new Error('tgMap尚未创建');
|
|
40
|
+
}
|
|
41
|
+
return this.runtime.map;
|
|
42
|
+
}
|
|
43
|
+
getRuntime() {
|
|
44
|
+
return this.runtime;
|
|
45
|
+
}
|
|
46
|
+
setRuntime(runtime) {
|
|
47
|
+
this.runtime = runtime;
|
|
48
|
+
}
|
|
49
|
+
setCenterAndZoom(center, zoom) {
|
|
50
|
+
this.option.mapOptions.center = {
|
|
51
|
+
lat: center.lat,
|
|
52
|
+
lng: center.lng,
|
|
53
|
+
coord: center.coord,
|
|
54
|
+
};
|
|
55
|
+
this.option.mapOptions.zoom = zoom;
|
|
56
|
+
}
|
|
57
|
+
/** 应用坐标相关选项到地图 */
|
|
58
|
+
applyCoordinateOptions(runtime) {
|
|
59
|
+
const options = this.getMapOptions();
|
|
60
|
+
runtime.applyingOptions = true;
|
|
61
|
+
try {
|
|
62
|
+
if (runtime.dynamicOptionSnapshots.get('buildInMapTypeId') !== options.buildInMapTypeId && options.buildInMapTypeId) {
|
|
63
|
+
runtime.map.setBuildInMapTypeId(options.buildInMapTypeId);
|
|
64
|
+
runtime.dynamicOptionSnapshots.set('buildInMapTypeId', options.buildInMapTypeId);
|
|
65
|
+
}
|
|
66
|
+
if (runtime.map.getZoom() !== options.zoom) {
|
|
67
|
+
runtime.map.setZoom(options.zoom);
|
|
68
|
+
}
|
|
69
|
+
const center = tgMapCore.LatLng.fromLiteral(options.center);
|
|
70
|
+
runtime.map.setCenterIfChanged(center);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
runtime.applyingOptions = false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** 应用UI相关选项到地图 */
|
|
77
|
+
applyUIOptions(runtime) {
|
|
78
|
+
const options = this.getMapOptions();
|
|
79
|
+
runtime.applyingOptions = true;
|
|
80
|
+
try {
|
|
81
|
+
DYNAMIC_UI_OPTION_KEYS.forEach(key => {
|
|
82
|
+
const value = options[key];
|
|
83
|
+
const snapshot = typeof value !== 'object'
|
|
84
|
+
? value // 非对象, 可以直接缓存值
|
|
85
|
+
: JSON.stringify(value);
|
|
86
|
+
if (runtime.dynamicOptionSnapshots.get(key) === snapshot) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
runtime.dynamicOptionSnapshots.set(key, snapshot);
|
|
90
|
+
switch (key) {
|
|
91
|
+
case 'gestureHandling':
|
|
92
|
+
options.gestureHandling != null && runtime.map.setGestureHandling(options.gestureHandling);
|
|
93
|
+
break;
|
|
94
|
+
case 'minZoom':
|
|
95
|
+
options.minZoom != null && runtime.map.setMinZoom(options.minZoom);
|
|
96
|
+
break;
|
|
97
|
+
case 'maxZoom':
|
|
98
|
+
options.maxZoom != null && runtime.map.setMaxZoom(options.maxZoom);
|
|
99
|
+
break;
|
|
100
|
+
case 'mapStyle':
|
|
101
|
+
runtime.map.setMapStyle(options.mapStyle ? JSON.parse(options.mapStyle) : undefined);
|
|
102
|
+
break;
|
|
103
|
+
case 'hideLogo':
|
|
104
|
+
runtime.map.setHideLogo(options.hideLogo ?? false);
|
|
105
|
+
break;
|
|
106
|
+
case 'fractionalZoom':
|
|
107
|
+
runtime.map.setFractionalZoom(options.fractionalZoom ?? false);
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
tgCommons.exhaustiveCheck(key);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
runtime.applyingOptions = false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class TgMapViewportOverlay extends tgMapCore.ElementOverlay {
|
|
122
|
+
viewportRoot;
|
|
123
|
+
onOverlayDraw;
|
|
124
|
+
constructor(map, viewportRoot, onOverlayDraw) {
|
|
125
|
+
super(map, { mapPane: tgMapCore.MapPane.overlayMouseTarget });
|
|
126
|
+
this.viewportRoot = viewportRoot;
|
|
127
|
+
this.onOverlayDraw = onOverlayDraw;
|
|
128
|
+
}
|
|
129
|
+
onCreate() {
|
|
130
|
+
return this.viewportRoot;
|
|
131
|
+
}
|
|
132
|
+
onDraw(_projection) {
|
|
133
|
+
this.onOverlayDraw();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
class TgMapCoordSys {
|
|
137
|
+
map;
|
|
138
|
+
api;
|
|
139
|
+
dataCoordType;
|
|
140
|
+
static dimensions = ['lng', 'lat'];
|
|
141
|
+
static create = (ecModel, api) => {
|
|
142
|
+
let coordinateSystem;
|
|
143
|
+
ecModel.eachComponent('tgMap', (model) => {
|
|
144
|
+
if (!(model instanceof TgMapModel))
|
|
145
|
+
return;
|
|
146
|
+
if (coordinateSystem) {
|
|
147
|
+
throw new Error('一个ECharts实例只能配置一个tgMap组件');
|
|
148
|
+
}
|
|
149
|
+
let runtime = model.getRuntime();
|
|
150
|
+
if (runtime) {
|
|
151
|
+
model.applyCoordinateOptions(runtime);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
runtime = TgMapCoordSys.createMapRuntime(model, api);
|
|
155
|
+
}
|
|
156
|
+
console.log('TgMapCoordSys.create', runtime);
|
|
157
|
+
coordinateSystem = new TgMapCoordSys(runtime.map, api, model.getDataCoordType());
|
|
158
|
+
model.coordinateSystem = coordinateSystem;
|
|
159
|
+
});
|
|
160
|
+
ecModel.eachSeries((seriesModel) => {
|
|
161
|
+
if (seriesModel.get('coordinateSystem') === 'tgMap') {
|
|
162
|
+
if (!coordinateSystem) {
|
|
163
|
+
throw new Error('series使用了tgMap坐标系,但option中没有配置tgMap组件');
|
|
164
|
+
}
|
|
165
|
+
seriesModel.coordinateSystem = coordinateSystem;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return coordinateSystem ? [coordinateSystem] : [];
|
|
169
|
+
};
|
|
170
|
+
type = 'tgMap';
|
|
171
|
+
dimensions = TgMapCoordSys.dimensions;
|
|
172
|
+
constructor(map, api, dataCoordType) {
|
|
173
|
+
this.map = map;
|
|
174
|
+
this.api = api;
|
|
175
|
+
this.dataCoordType = dataCoordType;
|
|
176
|
+
}
|
|
177
|
+
getMap() {
|
|
178
|
+
return this.map;
|
|
179
|
+
}
|
|
180
|
+
dataToPoint(data, _clamp, out = []) {
|
|
181
|
+
// bmap的实现中使用的是`overlay.getProjection()`表示的相对Pane的坐标, 再减去pane在平移时的偏移, 最终得到相对地图容器的坐标
|
|
182
|
+
// 这里直接使用containerPoint, 省得偏移来偏移去
|
|
183
|
+
const point = this.map.fromLatLngToContainerPoint(tgMapCore.LatLng.fromLngLat(data[0], data[1], this.dataCoordType));
|
|
184
|
+
out[0] = point.x;
|
|
185
|
+
out[1] = point.y;
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
pointToData(point) {
|
|
189
|
+
const latLng = this.map.fromContainerPointToLatLng({ x: point[0], y: point[1] }).to(this.dataCoordType);
|
|
190
|
+
return [latLng.lng, latLng.lat];
|
|
191
|
+
}
|
|
192
|
+
containPoint(point) {
|
|
193
|
+
return this.getViewRect().contain(point[0], point[1]);
|
|
194
|
+
}
|
|
195
|
+
getViewRect() {
|
|
196
|
+
return new core.graphic.BoundingRect(0, 0, this.api.getWidth(), this.api.getHeight());
|
|
197
|
+
}
|
|
198
|
+
getRoamTransform() {
|
|
199
|
+
return core.matrix.create();
|
|
200
|
+
}
|
|
201
|
+
prepareCustoms() {
|
|
202
|
+
const rect = this.getViewRect();
|
|
203
|
+
return {
|
|
204
|
+
coordSys: {
|
|
205
|
+
type: 'tgMap',
|
|
206
|
+
x: rect.x,
|
|
207
|
+
y: rect.y,
|
|
208
|
+
width: rect.width,
|
|
209
|
+
height: rect.height,
|
|
210
|
+
},
|
|
211
|
+
api: {
|
|
212
|
+
coord: this.dataToPoint.bind(this),
|
|
213
|
+
size: this.dataToCoordSize.bind(this),
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
dataToCoordSize(dataSize, dataItem = [0, 0]) {
|
|
218
|
+
return [0, 1].map(dimIndex => {
|
|
219
|
+
const value = dataItem[dimIndex];
|
|
220
|
+
const halfSize = dataSize[dimIndex] / 2;
|
|
221
|
+
const first = [dataItem[0], dataItem[1]];
|
|
222
|
+
const second = [...first];
|
|
223
|
+
first[dimIndex] = value - halfSize;
|
|
224
|
+
second[dimIndex] = value + halfSize;
|
|
225
|
+
return Math.abs(this.dataToPoint(first)[dimIndex] - this.dataToPoint(second)[dimIndex]);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 创建的DOM层级:
|
|
230
|
+
* ```
|
|
231
|
+
* root/viewportParent echarts的元素, viewportRoot原本的parent
|
|
232
|
+
* |--mapRoot class=ec-extension-tg-map
|
|
233
|
+
* |--pane
|
|
234
|
+
* |--viewportRoot painter的viewportRoot
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
static createMapRuntime(model, api) {
|
|
238
|
+
const root = api.getDom();
|
|
239
|
+
const painter = api.getZr().painter;
|
|
240
|
+
const viewportRoot = painter.getViewportRoot();
|
|
241
|
+
const viewportParent = viewportRoot.parentNode;
|
|
242
|
+
if (!viewportParent) {
|
|
243
|
+
throw new Error('无法获取ECharts viewportRoot的父节点');
|
|
244
|
+
}
|
|
245
|
+
const mapRoot = document.createElement('div');
|
|
246
|
+
mapRoot.className = 'ec-extension-tg-map';
|
|
247
|
+
mapRoot.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%';
|
|
248
|
+
root.insertBefore(mapRoot, root.firstChild);
|
|
249
|
+
const mapOptions = model.getMapOptions();
|
|
250
|
+
const map = tgMapCore.TgMapFactory.createMap(model.getEngine(), mapRoot, {
|
|
251
|
+
...mapOptions,
|
|
252
|
+
center: tgMapCore.LatLng.fromLiteral(mapOptions.center),
|
|
253
|
+
mapStyle: mapOptions.mapStyle ? JSON.parse(mapOptions.mapStyle) : undefined,
|
|
254
|
+
});
|
|
255
|
+
const runtime = {
|
|
256
|
+
map,
|
|
257
|
+
mapRoot,
|
|
258
|
+
overlay: tgCommons.lateinit(),
|
|
259
|
+
viewportRoot,
|
|
260
|
+
viewportBackup: {
|
|
261
|
+
parent: viewportParent,
|
|
262
|
+
nextSibling: viewportRoot.nextSibling,
|
|
263
|
+
className: viewportRoot.className,
|
|
264
|
+
position: viewportRoot.style.position,
|
|
265
|
+
left: viewportRoot.style.left,
|
|
266
|
+
top: viewportRoot.style.top,
|
|
267
|
+
getViewportRootOffset: painter.getViewportRootOffset,
|
|
268
|
+
},
|
|
269
|
+
syncViewportPosition: tgCommons.lateinit(),
|
|
270
|
+
applyingOptions: false,
|
|
271
|
+
dynamicOptionSnapshots: new Map(),
|
|
272
|
+
};
|
|
273
|
+
runtime.syncViewportPosition = () => {
|
|
274
|
+
const pane = viewportRoot.parentElement;
|
|
275
|
+
if (!pane) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// 地图平移后, pane的左上角可能变化, 这里补偿变化的值, 保证viewportRoot始终和mapRoot的左上角对齐
|
|
279
|
+
const mapRect = mapRoot.getBoundingClientRect();
|
|
280
|
+
const paneRect = pane.getBoundingClientRect();
|
|
281
|
+
viewportRoot.style.left = `${mapRect.left - paneRect.left}px`;
|
|
282
|
+
viewportRoot.style.top = `${mapRect.top - paneRect.top}px`;
|
|
283
|
+
};
|
|
284
|
+
runtime.overlay = new TgMapViewportOverlay(map, viewportRoot, runtime.syncViewportPosition);
|
|
285
|
+
viewportRoot.style.position = 'absolute';
|
|
286
|
+
viewportRoot.style.left = '0';
|
|
287
|
+
viewportRoot.style.top = '0';
|
|
288
|
+
map.addElementOverlay(runtime.overlay);
|
|
289
|
+
runtime.syncViewportPosition();
|
|
290
|
+
painter.getViewportRootOffset = () => ({ offsetLeft: 0, offsetTop: 0 });
|
|
291
|
+
model.setRuntime(runtime);
|
|
292
|
+
return runtime;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
class TgMapView extends core.ComponentView {
|
|
297
|
+
static type = 'tgMap';
|
|
298
|
+
type = TgMapView.type;
|
|
299
|
+
model;
|
|
300
|
+
map;
|
|
301
|
+
rendering = false;
|
|
302
|
+
changeHandler;
|
|
303
|
+
render(model, _ecModel, api) {
|
|
304
|
+
this.rendering = true;
|
|
305
|
+
try {
|
|
306
|
+
this.model = model;
|
|
307
|
+
const runtime = model.getRuntime();
|
|
308
|
+
if (!runtime) {
|
|
309
|
+
throw new Error('tgMap尚未创建');
|
|
310
|
+
}
|
|
311
|
+
if (this.map !== runtime.map) {
|
|
312
|
+
this.unbindMapEvents();
|
|
313
|
+
this.map = runtime.map;
|
|
314
|
+
this.bindMapEvents(runtime, api);
|
|
315
|
+
}
|
|
316
|
+
model.applyUIOptions(runtime);
|
|
317
|
+
console.log('TgMapView.render', runtime);
|
|
318
|
+
runtime.syncViewportPosition();
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
this.rendering = false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
dispose(_ecModel, api) {
|
|
325
|
+
this.unbindMapEvents();
|
|
326
|
+
const model = this.model;
|
|
327
|
+
const runtime = model?.getRuntime();
|
|
328
|
+
if (!model || !runtime) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (runtime.animationFrameId !== undefined) {
|
|
332
|
+
cancelAnimationFrame(runtime.animationFrameId);
|
|
333
|
+
}
|
|
334
|
+
runtime.map.removeElementOverlay(runtime.overlay);
|
|
335
|
+
const backup = runtime.viewportBackup;
|
|
336
|
+
if (backup.nextSibling?.parentNode === backup.parent) {
|
|
337
|
+
backup.parent.insertBefore(runtime.viewportRoot, backup.nextSibling);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
backup.parent.appendChild(runtime.viewportRoot);
|
|
341
|
+
}
|
|
342
|
+
runtime.viewportRoot.className = backup.className;
|
|
343
|
+
runtime.viewportRoot.style.position = backup.position;
|
|
344
|
+
runtime.viewportRoot.style.left = backup.left;
|
|
345
|
+
runtime.viewportRoot.style.top = backup.top;
|
|
346
|
+
runtime.mapRoot.parentNode?.removeChild(runtime.mapRoot);
|
|
347
|
+
const painter = api.getZr().painter;
|
|
348
|
+
painter.getViewportRootOffset = backup.getViewportRootOffset;
|
|
349
|
+
model.setRuntime(undefined);
|
|
350
|
+
this.model = undefined;
|
|
351
|
+
}
|
|
352
|
+
bindMapEvents(runtime, api) {
|
|
353
|
+
this.changeHandler = () => {
|
|
354
|
+
if (this.rendering || runtime.applyingOptions || runtime.animationFrameId !== undefined) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
runtime.animationFrameId = requestAnimationFrame(() => {
|
|
358
|
+
runtime.animationFrameId = undefined;
|
|
359
|
+
runtime.syncViewportPosition();
|
|
360
|
+
api.dispatchAction({
|
|
361
|
+
type: 'tgMapRoam',
|
|
362
|
+
animation: { duration: 0 },
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
runtime.map.addEventListener('center-changed', this.changeHandler);
|
|
367
|
+
runtime.map.addEventListener('zoom-changed', this.changeHandler);
|
|
368
|
+
runtime.map.addEventListener('tilesloaded', this.changeHandler);
|
|
369
|
+
}
|
|
370
|
+
unbindMapEvents() {
|
|
371
|
+
if (!this.map || !this.changeHandler) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
this.map.removeEventListener('center-changed', this.changeHandler);
|
|
375
|
+
this.map.removeEventListener('zoom-changed', this.changeHandler);
|
|
376
|
+
this.map.removeEventListener('tilesloaded', this.changeHandler);
|
|
377
|
+
this.changeHandler = undefined;
|
|
378
|
+
this.map = undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const TgMapExtension = {
|
|
383
|
+
install(registers) {
|
|
384
|
+
registers.registerComponentModel(TgMapModel);
|
|
385
|
+
registers.registerComponentView(TgMapView);
|
|
386
|
+
registers.registerCoordinateSystem('tgMap', TgMapCoordSys);
|
|
387
|
+
// 注册地图漫游Action:dispatchAction后先将地图实际的center/zoom回写到Model,
|
|
388
|
+
// 再通过updateLayout触发完整更新,重新创建坐标系并渲染图表;event用于更新完成后的对外通知。
|
|
389
|
+
registers.registerAction({ type: 'tgMapRoam', event: 'tgMapRoam', update: 'updateLayout' }, (_payload, ecModel) => {
|
|
390
|
+
ecModel.eachComponent('tgMap', (model) => {
|
|
391
|
+
if (!(model instanceof TgMapModel))
|
|
392
|
+
return;
|
|
393
|
+
const runtime = model.getRuntime();
|
|
394
|
+
if (runtime) {
|
|
395
|
+
model.setCenterAndZoom(runtime.map.getCenter(), runtime.map.getZoom());
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
},
|
|
400
|
+
// {@template echarts_on_demand_import}
|
|
401
|
+
// 导入echarts, 会自动安装LineChart等常用组件, 当前包是个echarts扩展, 不需要导入那些东西, 故只导入了echarts/core;
|
|
402
|
+
//
|
|
403
|
+
// 而echarts和echarts/core的类型声明是当前是两个独立的d.ts文件, 相互没有关系, 虽然他们的类型声明类似,
|
|
404
|
+
// 但是依然TS依然会报不兼容错误, 故对于需要导出给外部使用的类型, 需要抹除详细的类型信息
|
|
405
|
+
//
|
|
406
|
+
// {@endtemplate}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
exports.TgMapExtension = TgMapExtension;
|