@hzab/map-combine 0.0.1
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/CHANGELOG.md +2 -0
- package/README.md +67 -0
- package/package.json +56 -0
- package/src/assets/Thumbs.db +0 -0
- package/src/assets/add.png +0 -0
- package/src/assets/point.png +0 -0
- package/src/basic/ContextMenu.tsx +49 -0
- package/src/basic/MapCombine.ts +67 -0
- package/src/basic/MapElement.ts +32 -0
- package/src/basic/MapEvent.ts +18 -0
- package/src/basic/Point.ts +176 -0
- package/src/basic/PointAggregation.tsx +189 -0
- package/src/basic/Polygon.ts +180 -0
- package/src/basic/Polyline.ts +194 -0
- package/src/basic/ReactPoint.tsx +357 -0
- package/src/basic/ReactPopup.tsx +34 -0
- package/src/basic/Tile3D.ts +99 -0
- package/src/cesium/CesiumEvent.ts +86 -0
- package/src/cesium/CesiumMap.ts +167 -0
- package/src/cesium/CesiumPoint.ts +137 -0
- package/src/cesium/CesiumPolygon.ts +368 -0
- package/src/cesium/CesiumPolyline.ts +382 -0
- package/src/cesium/CesiumTile3D.ts +100 -0
- package/src/mine/MineEvent.ts +29 -0
- package/src/mine/MineMap.ts +90 -0
- package/src/mine/MinePoint.ts +139 -0
- package/src/mine/MinePolygon.ts +388 -0
- package/src/mine/MinePolyline.ts +359 -0
- package/src/mine/MineTile3D.ts +32 -0
- package/src/openlayer/OpenlayerEvent.ts +75 -0
- package/src/openlayer/OpenlayerMap.ts +149 -0
- package/src/openlayer/OpenlayerPoint.ts +177 -0
- package/src/openlayer/OpenlayerPolygon.ts +300 -0
- package/src/openlayer/OpenlayerPolyline.ts +289 -0
- package/src/utils/Image.ts +46 -0
- package/src/utils/PolygonEditor.ts +159 -0
- package/src/utils/PolylineEditor.ts +149 -0
- package/src/utils/Projection.ts +46 -0
- package/src/utils/index.ts +131 -0
- package/src/utils/static.ts +7 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @hzab/map-combine
|
|
2
|
+
|
|
3
|
+
组件模板
|
|
4
|
+
|
|
5
|
+
- node@16.16.0
|
|
6
|
+
|
|
7
|
+
# 组件
|
|
8
|
+
|
|
9
|
+
## 示例
|
|
10
|
+
|
|
11
|
+
```jsx
|
|
12
|
+
import Demo from "@hzab/map-combine";
|
|
13
|
+
|
|
14
|
+
<Demo />;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
### InfoPanel Attributes
|
|
20
|
+
|
|
21
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
22
|
+
| ------ | ------ | ---- | ------ | ----------------- |
|
|
23
|
+
| schema | Object | 是 | - | 数据信息的 schema |
|
|
24
|
+
|
|
25
|
+
# 组件开发流程
|
|
26
|
+
|
|
27
|
+
- 在 config/webpack.config.js 中按需修改 library 配置的文件名
|
|
28
|
+
- 在 config/webpack.config.js 中按需修改 alias 配置的包名,便于本地调试
|
|
29
|
+
- 在 tsconfig.json 中按需修改 paths 配置的包名,解决 ts 报错问题
|
|
30
|
+
- npm run dev
|
|
31
|
+
|
|
32
|
+
## 文件目录
|
|
33
|
+
|
|
34
|
+
- example 本地开发测试代码
|
|
35
|
+
- src 组件源码
|
|
36
|
+
- lib 组件打包编译后的代码
|
|
37
|
+
|
|
38
|
+
## 命令
|
|
39
|
+
|
|
40
|
+
- Mac 执行该命令,设置 pre-commit 为可执行文件
|
|
41
|
+
|
|
42
|
+
- npm run mac-chmod
|
|
43
|
+
- chmod +x .husky && chmod +x .husky/pre-commit
|
|
44
|
+
|
|
45
|
+
- 生成文档:npm run docs
|
|
46
|
+
- 本地运行:npm run dev
|
|
47
|
+
- 打包编译:npm run build
|
|
48
|
+
|
|
49
|
+
## 发布
|
|
50
|
+
|
|
51
|
+
- 注意:示例代码生效,但发布之后未生效。确认是否执行了编译!!!
|
|
52
|
+
|
|
53
|
+
- 编译组件:npm run build
|
|
54
|
+
- 命令:npm publish --access public
|
|
55
|
+
- 发布目录:
|
|
56
|
+
- lib
|
|
57
|
+
- src
|
|
58
|
+
|
|
59
|
+
## 配置
|
|
60
|
+
|
|
61
|
+
### 配置文件
|
|
62
|
+
|
|
63
|
+
- 本地配置文件:config/config.js
|
|
64
|
+
|
|
65
|
+
### webpack 配置文件
|
|
66
|
+
|
|
67
|
+
- config/webpack.config.js
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hzab/map-combine",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "地图组件",
|
|
5
|
+
"main": "src",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "npm run prepare && webpack serve -c ./config/webpack.config.js --env local",
|
|
8
|
+
"build": "webpack -c ./config/webpack.config.js --env production",
|
|
9
|
+
"publish-patch": "npm version patch && npm publish --access public",
|
|
10
|
+
"publish-minor": "npm version minor && npm publish --access public",
|
|
11
|
+
"publish-major": "npm version major && npm publish --access public",
|
|
12
|
+
"prepare": "husky install",
|
|
13
|
+
"mac-chmod": "chmod +x .husky && chmod +x .husky/pre-commit",
|
|
14
|
+
"docs": "typedoc"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"CHANGELOG.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [],
|
|
21
|
+
"author": "CaiYansong",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@hzab/permissions": "0.1.1",
|
|
25
|
+
"@hzab/webpack-config": "^0.2.1",
|
|
26
|
+
"@types/react": "^17.0.62",
|
|
27
|
+
"@types/react-dom": "^17.0.20",
|
|
28
|
+
"antd": "^4.14.0",
|
|
29
|
+
"axios": "^1.4.0",
|
|
30
|
+
"eslint": "^8.30.0",
|
|
31
|
+
"less": "^4.1.3",
|
|
32
|
+
"mobx": "^6.7.0",
|
|
33
|
+
"mobx-react": "^7.6.0",
|
|
34
|
+
"react": "^17.0.2",
|
|
35
|
+
"react-dom": "^17.0.2",
|
|
36
|
+
"react-router-dom": "^6.14.1",
|
|
37
|
+
"typedoc": "^0.24.8",
|
|
38
|
+
"typescript": "^4.9.4"
|
|
39
|
+
},
|
|
40
|
+
"directories": {
|
|
41
|
+
"lib": "lib"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@dvgis/cesium-map": "^3.1.0",
|
|
45
|
+
"coordtransform": "^2.1.2",
|
|
46
|
+
"lodash": ">=4.17.21",
|
|
47
|
+
"ol": "^7.4.0",
|
|
48
|
+
"react-dom": "^17.0.2",
|
|
49
|
+
"zrender": "^5.4.4"
|
|
50
|
+
},
|
|
51
|
+
"lint-staged": {
|
|
52
|
+
"**.{js,jsx,ts,tsx,css,scss,less,json,html}": [
|
|
53
|
+
"prettier --write"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import ReactDOM from "react-dom";
|
|
2
|
+
import { MapCombine } from "./MapCombine";
|
|
3
|
+
|
|
4
|
+
export interface ContextMenuItem {
|
|
5
|
+
label: string;
|
|
6
|
+
onclick: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ContextMenu {
|
|
10
|
+
map: MapCombine;
|
|
11
|
+
root: HTMLDivElement;
|
|
12
|
+
element = (items: ContextMenuItem[]) => {
|
|
13
|
+
return (
|
|
14
|
+
<div className="combine-context-menu">
|
|
15
|
+
{items.map((e) => (
|
|
16
|
+
<div className="combine-context-item" onClick={e.onclick} key={e.label}>
|
|
17
|
+
{e.label}
|
|
18
|
+
</div>
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
hide() {
|
|
24
|
+
ReactDOM.unmountComponentAtNode(this.root);
|
|
25
|
+
}
|
|
26
|
+
constructor(map: MapCombine) {
|
|
27
|
+
this.map = map;
|
|
28
|
+
const root = document.createElement("div");
|
|
29
|
+
root.style.zIndex = "2000";
|
|
30
|
+
root.style.position = "absolute";
|
|
31
|
+
root.addEventListener("pointerleave", () => {
|
|
32
|
+
this.hide();
|
|
33
|
+
});
|
|
34
|
+
root.addEventListener("click", () => {
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
this.hide();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
map.htmllayer.appendChild(root);
|
|
40
|
+
this.root = root;
|
|
41
|
+
}
|
|
42
|
+
show(items: ContextMenuItem[]) {
|
|
43
|
+
const { root, map } = this;
|
|
44
|
+
ReactDOM.render(this.element(items), root);
|
|
45
|
+
root.style.left = map.event.canvas[0] - 5 + "px";
|
|
46
|
+
root.style.top = map.event.canvas[1] - 5 + "px";
|
|
47
|
+
root.focus();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ContextMenu } from "./ContextMenu";
|
|
2
|
+
import { MapElement } from "./MapElement";
|
|
3
|
+
import { MapEvent } from "./MapEvent";
|
|
4
|
+
import { Point, PointOption } from "./Point";
|
|
5
|
+
import { Polygon, PolygonOption } from "./Polygon";
|
|
6
|
+
import { Polyline, PolylineOption } from "./Polyline";
|
|
7
|
+
import { ReactPoint, ReactPointOption, drawReactPoint } from "./ReactPoint";
|
|
8
|
+
import { Tile3D, Tile3DOption } from "./Tile3D";
|
|
9
|
+
export abstract class MapCombine {
|
|
10
|
+
/* 用于挂载html元素的根节点 */
|
|
11
|
+
readonly htmllayer = document.createElement("div");
|
|
12
|
+
readonly elements = new Map<string, MapElement>()
|
|
13
|
+
readonly event = new MapEvent()
|
|
14
|
+
readonly menu: ContextMenu;
|
|
15
|
+
abstract get cursor(): string;
|
|
16
|
+
abstract set cursor(val: string);
|
|
17
|
+
|
|
18
|
+
abstract get moveable(): boolean;
|
|
19
|
+
abstract set moveable(val: boolean);
|
|
20
|
+
|
|
21
|
+
abstract get center(): number[]
|
|
22
|
+
abstract set center(val)
|
|
23
|
+
|
|
24
|
+
abstract get zoom(): number
|
|
25
|
+
abstract set zoom(val)
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
const { htmllayer } = this
|
|
29
|
+
htmllayer.style.zIndex = "1000";
|
|
30
|
+
htmllayer.style.position = "absolute";
|
|
31
|
+
htmllayer.style.width = "0";
|
|
32
|
+
htmllayer.style.height = "0";
|
|
33
|
+
htmllayer.style.left = "0";
|
|
34
|
+
htmllayer.style.top = "0";
|
|
35
|
+
this.menu = new ContextMenu(this);
|
|
36
|
+
(window as any).map = this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createReactPoint<K>(option?: Partial<ReactPointOption<K>>): ReactPoint<K> {
|
|
40
|
+
const e = new ReactPoint(this, option)
|
|
41
|
+
drawReactPoint(this, e)
|
|
42
|
+
return e
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 设置地图视图
|
|
48
|
+
* @param option 视图参数
|
|
49
|
+
* @param time 设置时间会动画形式调整视图
|
|
50
|
+
*/
|
|
51
|
+
abstract createPoint<K>(option?: Partial<PointOption<K>>): Point<K>
|
|
52
|
+
|
|
53
|
+
abstract createPolyline<K>(option?: Partial<PolylineOption<K>>): Polyline<K>
|
|
54
|
+
|
|
55
|
+
abstract createPolygon<K>(option?: Partial<PolygonOption<K>>): Polygon<K>
|
|
56
|
+
|
|
57
|
+
abstract createTile3D(option?: Partial<Tile3DOption>): Tile3D
|
|
58
|
+
|
|
59
|
+
abstract canvasTgeography(p: number[]): number[]
|
|
60
|
+
|
|
61
|
+
abstract geographyTcanvas(p: number[]): number[]
|
|
62
|
+
|
|
63
|
+
destroy() {
|
|
64
|
+
this.htmllayer.remove()
|
|
65
|
+
this.event.trigger('destroy')
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { MapCombine } from "./MapCombine"
|
|
2
|
+
|
|
3
|
+
export interface MapElementOption {
|
|
4
|
+
name: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export abstract class MapElement<
|
|
8
|
+
T extends MapElementOption = MapElementOption
|
|
9
|
+
>{
|
|
10
|
+
abstract type: string
|
|
11
|
+
readonly map: MapCombine
|
|
12
|
+
protected readonly _option: T
|
|
13
|
+
get name() {
|
|
14
|
+
return this._option.name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor(map: MapCombine, option: T) {
|
|
18
|
+
this.map = map
|
|
19
|
+
this._option = option
|
|
20
|
+
if (map.elements.has(option.name)) {
|
|
21
|
+
throw new Error(`已存在同名元素 ${option.name}`)
|
|
22
|
+
}
|
|
23
|
+
map.elements.set(option.name, this)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
destroy() {
|
|
27
|
+
this.map.elements.delete(this.name)
|
|
28
|
+
this._onDestroy()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected abstract _onDestroy(): void
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Eventful from "zrender/lib/core/Eventful";
|
|
2
|
+
|
|
3
|
+
export class MapEvent extends Eventful<{
|
|
4
|
+
'pointer-down': () => void
|
|
5
|
+
'pointer-move': () => void
|
|
6
|
+
'pointer-up': () => void
|
|
7
|
+
'left-click': () => void
|
|
8
|
+
'right-click': () => void
|
|
9
|
+
'double-click': () => void
|
|
10
|
+
'view-change': () => void
|
|
11
|
+
'view-moveEnd': () => void
|
|
12
|
+
'frame': () => void
|
|
13
|
+
'resize': () => void
|
|
14
|
+
'destroy': () => void
|
|
15
|
+
}> {
|
|
16
|
+
canvas = [0, 0]
|
|
17
|
+
geography = [0, 0, 0]
|
|
18
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import Eventful from "zrender/lib/core/Eventful"
|
|
2
|
+
|
|
3
|
+
import { MapElement, MapElementOption } from "./MapElement"
|
|
4
|
+
import { MapCombine } from "./MapCombine"
|
|
5
|
+
|
|
6
|
+
export interface PointOption<K> extends MapElementOption {
|
|
7
|
+
show: boolean
|
|
8
|
+
cursor: string
|
|
9
|
+
coordinates?: number[]
|
|
10
|
+
src: string
|
|
11
|
+
center: number[]
|
|
12
|
+
size: number[]
|
|
13
|
+
rotation: number
|
|
14
|
+
editable: boolean
|
|
15
|
+
userdata: K
|
|
16
|
+
silent: boolean,
|
|
17
|
+
height: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Point<K = undefined> extends MapElement<PointOption<K>> {
|
|
21
|
+
type = 'Point'
|
|
22
|
+
event = new Eventful<{
|
|
23
|
+
'data-loaded': () => void
|
|
24
|
+
'data-update': () => void
|
|
25
|
+
'pointer-in': () => void
|
|
26
|
+
'pointer-out': () => void
|
|
27
|
+
'left-click': () => void
|
|
28
|
+
'right-click': () => void
|
|
29
|
+
'update': (keys: Set<string>) => void
|
|
30
|
+
'destroy': () => void
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
get userdata() {
|
|
34
|
+
return this._option.userdata
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
get cursor() {
|
|
39
|
+
return this._option.cursor
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get show() {
|
|
43
|
+
return this._option.show
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
set show(val) {
|
|
47
|
+
if (this._option.show != val) {
|
|
48
|
+
this._option.show = val
|
|
49
|
+
this._update('show')
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get silent() {
|
|
54
|
+
return this._option.silent
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set silent(val) {
|
|
58
|
+
if (this._option.silent != val) {
|
|
59
|
+
this._option.silent = val
|
|
60
|
+
this._update('silent')
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get editable() {
|
|
65
|
+
return this._option.editable
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
set editable(val) {
|
|
69
|
+
if (this._option.editable != val) {
|
|
70
|
+
this._option.editable = val
|
|
71
|
+
this._update('editable')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get coordinates() {
|
|
76
|
+
return this._option.coordinates
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
set coordinates(val) {
|
|
80
|
+
if (this._option.coordinates != val) {
|
|
81
|
+
this._option.coordinates = val
|
|
82
|
+
this._update('coordinates')
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get src() {
|
|
87
|
+
return this._option.src
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set src(val) {
|
|
91
|
+
if (this._option.src != val) {
|
|
92
|
+
this._option.src = val
|
|
93
|
+
this._update('src')
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get size() {
|
|
98
|
+
return this._option.size
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set size(val) {
|
|
102
|
+
if (this._option.size != val) {
|
|
103
|
+
this._option.size = val
|
|
104
|
+
this._update('size')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
get center() {
|
|
108
|
+
return this._option.center
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
set center(val) {
|
|
112
|
+
if (this._option.center != val) {
|
|
113
|
+
this._option.center = val
|
|
114
|
+
this._update('center')
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get height() {
|
|
119
|
+
return this._option.height
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
set height(val) {
|
|
123
|
+
if (this._option.height != val) {
|
|
124
|
+
this._option.height = val
|
|
125
|
+
this._update('height')
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get rotation() {
|
|
130
|
+
return this._option.rotation
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
set rotation(val) {
|
|
134
|
+
if (this._option.rotation != val) {
|
|
135
|
+
this._option.rotation = val
|
|
136
|
+
this._update('rotation')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
constructor(map: MapCombine, option: Partial<PointOption<K>> = {}) {
|
|
143
|
+
super(map, Object.assign({ ...Point.option }, option))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static option: PointOption<undefined> = {
|
|
147
|
+
name: '点',
|
|
148
|
+
show: true,
|
|
149
|
+
src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAIxJREFUOE+tU4ENgCAM6y5TL1MuEy+bmQjChAjIEkKAretIS1DBwAy3pnuXDAvgIGDT+RRfsEtYdZI6mxgoAFQWe6wAcgE0FicgHoA/aJeeF+rs/rBgYI9+u5WIFQa99K9mQwB+j1AjntLfGBlBZCssmoMAGiOkDjWmUvbcK0WVN1PGlS87i63JWTvECRNBLcaa3Jq0AAAAAElFTkSuQmCC',
|
|
150
|
+
center: [0.5, 0.5],
|
|
151
|
+
editable: false,
|
|
152
|
+
size: [16, 16],
|
|
153
|
+
rotation: 0,
|
|
154
|
+
cursor: 'pointer',
|
|
155
|
+
userdata: undefined,
|
|
156
|
+
silent: false,
|
|
157
|
+
height: 0
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private _needUpdate?: Set<string>
|
|
161
|
+
private _update(key: string) {
|
|
162
|
+
if (this._needUpdate) {
|
|
163
|
+
this._needUpdate.add(key)
|
|
164
|
+
} else {
|
|
165
|
+
this._needUpdate = new Set([key])
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
this.event.trigger('update', this._needUpdate)
|
|
168
|
+
this._needUpdate = undefined
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected _onDestroy(): void {
|
|
174
|
+
this.event.trigger('destroy')
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ReactElement } from "react";
|
|
2
|
+
import { throttle } from 'lodash';
|
|
3
|
+
|
|
4
|
+
import { LATTOONE, LONTOONE } from "../utils/static";
|
|
5
|
+
import { getUUID, setValue } from "../utils";
|
|
6
|
+
import { MapCombine } from "../basic/MapCombine";
|
|
7
|
+
import { ReactPoint } from "./ReactPoint";
|
|
8
|
+
|
|
9
|
+
export interface PointAggregationOption {
|
|
10
|
+
/** 组件名,唯一标识 */
|
|
11
|
+
name: string;
|
|
12
|
+
/** 组件是否显示 */
|
|
13
|
+
show: boolean;
|
|
14
|
+
datas: any[];
|
|
15
|
+
/** 小于该值的等级下会聚合 */
|
|
16
|
+
maxZoomLevel?: number;
|
|
17
|
+
/** 值越大聚合点的间距越小 */
|
|
18
|
+
density?: number;
|
|
19
|
+
element(e: any[], p: number[]): ReactElement;
|
|
20
|
+
location(e: any): number[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class TileNode {
|
|
24
|
+
private _location: number[];
|
|
25
|
+
private _element: any;
|
|
26
|
+
marker: ReactPoint | undefined;
|
|
27
|
+
private _map: MapCombine;
|
|
28
|
+
constructor(map: MapCombine, location: number[], element: ReactElement) {
|
|
29
|
+
this._map = map;
|
|
30
|
+
this._location = location;
|
|
31
|
+
this._element = element;
|
|
32
|
+
}
|
|
33
|
+
test(east: number, south: number, west: number, north: number) {
|
|
34
|
+
if (
|
|
35
|
+
this._location[0] < east &&
|
|
36
|
+
this._location[1] > south &&
|
|
37
|
+
this._location[0] > west &&
|
|
38
|
+
this._location[1] < north
|
|
39
|
+
) {
|
|
40
|
+
if (!this.marker) {
|
|
41
|
+
const option = {
|
|
42
|
+
show: true,
|
|
43
|
+
name: getUUID(),
|
|
44
|
+
element: () => (this._element),
|
|
45
|
+
center: [0.5, 0.5],
|
|
46
|
+
coordinates: [this._location[0], this._location[1], 0],
|
|
47
|
+
editable: false,
|
|
48
|
+
};
|
|
49
|
+
this.marker = this._map.createReactPoint(option);
|
|
50
|
+
} else {
|
|
51
|
+
this.marker.coordinates = [this._location[0], this._location[1]];
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
if (this.marker) {
|
|
55
|
+
this.marker.destroy();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export class PointAggregation {
|
|
63
|
+
private readonly _cache = new Map<number, TileNode[]>();
|
|
64
|
+
private _current = 0;
|
|
65
|
+
private _map: MapCombine;
|
|
66
|
+
private _option: PointAggregationOption;
|
|
67
|
+
|
|
68
|
+
/** 唯一标识 */
|
|
69
|
+
get name() {
|
|
70
|
+
return this._option.name;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 是否显示 */
|
|
74
|
+
get show() {
|
|
75
|
+
return this._option.show;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
set show(val) {
|
|
79
|
+
setValue(this._option, "show", val, () => {
|
|
80
|
+
val ? this._update() : this._turnoff();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 点聚合 */
|
|
85
|
+
constructor(map: MapCombine, option: PointAggregationOption) {
|
|
86
|
+
this._map = map;
|
|
87
|
+
this._option = option;
|
|
88
|
+
const { event } = map;
|
|
89
|
+
this.onAfterViewChange();
|
|
90
|
+
event.on("view-moveEnd", () => {
|
|
91
|
+
this.onAfterViewChange()
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private _turnon(zoom: number) {
|
|
96
|
+
const { _option, _cache } = this;
|
|
97
|
+
const maxZoomLevel = this._option.maxZoomLevel ?? 17;
|
|
98
|
+
const cache = _cache.get(zoom);
|
|
99
|
+
if (!cache) {
|
|
100
|
+
if (zoom < maxZoomLevel) {
|
|
101
|
+
const cache: TileNode[] = [];
|
|
102
|
+
const grid = new Map();
|
|
103
|
+
const max = Math.pow(2, zoom) * (_option.density ?? 1);
|
|
104
|
+
_option.datas.forEach((data) => {
|
|
105
|
+
const location = _option.location(data);
|
|
106
|
+
const x = Math.floor(LONTOONE.forward(location[0]) * max * 2);
|
|
107
|
+
const y = Math.floor(LATTOONE.forward(location[1]) * max);
|
|
108
|
+
const k = `${x}_${y}`;
|
|
109
|
+
const group = grid.get(k);
|
|
110
|
+
if (group) {
|
|
111
|
+
group.push(data);
|
|
112
|
+
} else {
|
|
113
|
+
grid.set(k, [data]);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
grid.forEach((e) => {
|
|
117
|
+
const { lon, lat } = e.reduce(
|
|
118
|
+
(a, b) => {
|
|
119
|
+
const loaction = _option.location(b);
|
|
120
|
+
a.lon += loaction[0];
|
|
121
|
+
a.lat += loaction[1];
|
|
122
|
+
return a;
|
|
123
|
+
},
|
|
124
|
+
{ lon: 0, lat: 0 },
|
|
125
|
+
);
|
|
126
|
+
cache.push(
|
|
127
|
+
new TileNode(
|
|
128
|
+
this._map,
|
|
129
|
+
[lon / e.length, lat / e.length],
|
|
130
|
+
_option.element(e, [lon / e.length, lat / e.length]),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
_cache.set(zoom, cache);
|
|
135
|
+
} else {
|
|
136
|
+
const cache: TileNode[] = [];
|
|
137
|
+
_option.datas.forEach((e) => {
|
|
138
|
+
const location = _option.location(e);
|
|
139
|
+
cache.push(new TileNode(this._map, location, _option.element([e], location)));
|
|
140
|
+
});
|
|
141
|
+
_cache.set(zoom, cache);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private _turnoff() {
|
|
147
|
+
this._cache.get(this._current)?.forEach((e) => {
|
|
148
|
+
if (e.marker) {
|
|
149
|
+
e.marker.destroy();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
this._cache.delete(this._current);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _update() {
|
|
156
|
+
const { _map } = this;
|
|
157
|
+
const { offsetHeight, offsetWidth } = (_map as any).container;
|
|
158
|
+
if (offsetHeight && offsetWidth) {
|
|
159
|
+
const [west, south] = _map.canvasTgeography([0, offsetHeight]);
|
|
160
|
+
const [east, north] = _map.canvasTgeography([offsetWidth, 0]);
|
|
161
|
+
this._cache.get(this._current)?.forEach((e) => {
|
|
162
|
+
e.test(east, south, west, north);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
onAfterViewChange(): void {
|
|
168
|
+
const { _map } = this;
|
|
169
|
+
const zoom = Math.min(Math.round(_map.zoom * 4) / 4, this._option.maxZoomLevel ?? 17);
|
|
170
|
+
if (zoom !== this._current) {
|
|
171
|
+
this._turnoff();
|
|
172
|
+
this._current = zoom;
|
|
173
|
+
this._turnon(zoom);
|
|
174
|
+
}
|
|
175
|
+
this._update();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** 销毁组件 */
|
|
179
|
+
destroy() {
|
|
180
|
+
const { event } = this._map;
|
|
181
|
+
event.on("view-moveEnd", null);
|
|
182
|
+
this._map.elements.delete(this.name);
|
|
183
|
+
this._cache.forEach((e) => {
|
|
184
|
+
e.forEach((f) => {
|
|
185
|
+
f.marker?.destroy();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|