@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.
- package/LICENSE +201 -0
- package/README.md +1 -0
- package/package.json +32 -0
- package/src/canvas/index.ts +4 -0
- package/src/canvas/slu-canvas-gif.ts +600 -0
- package/src/canvas/slu-canvas-img.ts +54 -0
- package/src/canvas/slu-canvas-text.ts +179 -0
- package/src/canvas/slu-canvas.ts +188 -0
- package/src/index.ts +0 -0
- package/src/map/canvas-draw.ts +250 -0
- package/src/map/canvas-event.ts +283 -0
- package/src/map/canvas-layer.ts +191 -0
- package/src/utils/slu-map.ts +427 -0
- package/src/utils/txt.ts +251 -0
- package/tsconfig.json +23 -0
- package/types/amap.d.ts +7542 -0
- package/types/core.d.ts +894 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import * as L from "leaflet";
|
|
2
|
+
declare var AMap: any;
|
|
3
|
+
const a = 6378245.0;
|
|
4
|
+
const pi = 3.1415926535897932384626;
|
|
5
|
+
const ee = 0.00669342162296594323;
|
|
6
|
+
const x_pi = pi * 3000.0 / 180.0;
|
|
7
|
+
/**地球半径 */
|
|
8
|
+
const R = 6378137;
|
|
9
|
+
/** 百度转84 */
|
|
10
|
+
function tobd09gps84(lng: number, lat: number) {
|
|
11
|
+
var gcj02 = tobd09cj02(lng, lat);
|
|
12
|
+
var map84 = togcj02gps84(gcj02.lng, gcj02.lat);
|
|
13
|
+
return map84;
|
|
14
|
+
}
|
|
15
|
+
/** 火星转84 */
|
|
16
|
+
function togcj02gps84(lng: number, lat: number) {
|
|
17
|
+
var coord = transform(lng, lat);
|
|
18
|
+
var lontitude = lng * 2 - coord.lng;
|
|
19
|
+
var latitude = lat * 2 - coord.lat;
|
|
20
|
+
var newCoord = {
|
|
21
|
+
lng: lontitude,
|
|
22
|
+
lat: latitude
|
|
23
|
+
};
|
|
24
|
+
return newCoord;
|
|
25
|
+
}
|
|
26
|
+
/** 84转百度 */
|
|
27
|
+
function togps84bd09(lng: number, lat: number) {
|
|
28
|
+
var gcj02 = togps84gcj02(lng, lat);
|
|
29
|
+
var bd09 = togcj02bd09(gcj02.lng, gcj02.lat);
|
|
30
|
+
return bd09;
|
|
31
|
+
}
|
|
32
|
+
/** 84转火星 */
|
|
33
|
+
function togps84gcj02(lng: number, lat: number) {
|
|
34
|
+
var dLat = transformLat(lng - 105.0, lat - 35.0);
|
|
35
|
+
var dLng = transformLng(lng - 105.0, lat - 35.0);
|
|
36
|
+
var radLat = lat / 180.0 * pi;
|
|
37
|
+
var magic = Math.sin(radLat);
|
|
38
|
+
magic = 1 - ee * magic * magic;
|
|
39
|
+
var sqrtMagic = Math.sqrt(magic);
|
|
40
|
+
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
|
41
|
+
dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
|
42
|
+
var mgLat = lat + dLat;
|
|
43
|
+
var mgLng = lng + dLng;
|
|
44
|
+
var newCoord = {
|
|
45
|
+
lng: mgLng,
|
|
46
|
+
lat: mgLat
|
|
47
|
+
};
|
|
48
|
+
return newCoord;
|
|
49
|
+
}
|
|
50
|
+
/** 火星转百度 */
|
|
51
|
+
function togcj02bd09(lng: number, lat: number) {
|
|
52
|
+
var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_pi);
|
|
53
|
+
var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_pi);
|
|
54
|
+
var bd_lng = z * Math.cos(theta) + 0.0065;
|
|
55
|
+
var bd_lat = z * Math.sin(theta) + 0.006;
|
|
56
|
+
var newCoord = {
|
|
57
|
+
lng: bd_lng,
|
|
58
|
+
lat: bd_lat
|
|
59
|
+
};
|
|
60
|
+
return newCoord;
|
|
61
|
+
}
|
|
62
|
+
/** 百度转火星 */
|
|
63
|
+
function tobd09cj02(bd_lng: number, bd_lat: number) {
|
|
64
|
+
var x = bd_lng - 0.0065;
|
|
65
|
+
var y = bd_lat - 0.006;
|
|
66
|
+
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
|
|
67
|
+
var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
|
|
68
|
+
var gg_lng = z * Math.cos(theta);
|
|
69
|
+
var gg_lat = z * Math.sin(theta);
|
|
70
|
+
var newCoord = {
|
|
71
|
+
lng: gg_lng,
|
|
72
|
+
lat: gg_lat
|
|
73
|
+
};
|
|
74
|
+
return newCoord;
|
|
75
|
+
}
|
|
76
|
+
function transform(lng: number, lat: number) {
|
|
77
|
+
var dLat = transformLat(lng - 105.0, lat - 35.0);
|
|
78
|
+
var dLng = transformLng(lng - 105.0, lat - 35.0);
|
|
79
|
+
var radLat = lat / 180.0 * pi;
|
|
80
|
+
var magic = Math.sin(radLat);
|
|
81
|
+
magic = 1 - ee * magic * magic;
|
|
82
|
+
var sqrtMagic = Math.sqrt(magic);
|
|
83
|
+
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
|
|
84
|
+
dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
|
|
85
|
+
var mgLat = lat + dLat;
|
|
86
|
+
var mgLng = lng + dLng;
|
|
87
|
+
var newCoord = {
|
|
88
|
+
lng: mgLng,
|
|
89
|
+
lat: mgLat
|
|
90
|
+
};
|
|
91
|
+
return newCoord;
|
|
92
|
+
}
|
|
93
|
+
function transformLat(x: number, y: number) {
|
|
94
|
+
var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
|
|
95
|
+
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
|
96
|
+
ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
|
|
97
|
+
ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
|
|
98
|
+
return ret;
|
|
99
|
+
}
|
|
100
|
+
function transformLng(x: number, y: number) {
|
|
101
|
+
var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
|
|
102
|
+
ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
|
|
103
|
+
ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
|
|
104
|
+
ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
|
|
105
|
+
return ret;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 测算两点与Y轴形成的角度大小(Y轴方向 ↑ )
|
|
109
|
+
* @param map 当前的地图
|
|
110
|
+
* @param latLngA 第一个点的[纬度,经度]
|
|
111
|
+
* @param latLngB 第二个点的[纬度,经度]
|
|
112
|
+
* @returns 两点与正北方的角度
|
|
113
|
+
*/
|
|
114
|
+
function getAngle(map: AMAP.Map | L.Map, latLngA: [number, number], latLngB: [number, number]): number {
|
|
115
|
+
let [y0, x0] = getPointByLatlng(map, latLngA),
|
|
116
|
+
[y1, x1] = getPointByLatlng(map, latLngB);
|
|
117
|
+
let θ = Math.atan2(x1 - x0, y1 - y0);
|
|
118
|
+
θ = θ * 180 / Math.PI;
|
|
119
|
+
θ = (90 + θ) < 0 ? 450 + θ : 90 + θ;
|
|
120
|
+
return θ
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 获取地图边界
|
|
124
|
+
* @params map 地图实例
|
|
125
|
+
* @params mapType=0 0天地图 1高德地图 2 百度 暂不支持
|
|
126
|
+
* */
|
|
127
|
+
function getBounds(map: AMAP.Map | L.Map) {
|
|
128
|
+
const mapType = getMapType(map);
|
|
129
|
+
if (mapType == 0) {
|
|
130
|
+
let bounds = (map as L.Map).getBounds();
|
|
131
|
+
return {
|
|
132
|
+
lngLeft: bounds.getSouthWest().lng,
|
|
133
|
+
latTop: bounds.getNorthEast().lat,
|
|
134
|
+
lngRight: bounds.getNorthEast().lng,
|
|
135
|
+
latBottom: bounds.getSouthWest().lat
|
|
136
|
+
}
|
|
137
|
+
} else if (mapType == 1) {
|
|
138
|
+
let { southwest, northeast } = (map as AMAP.Map).getBounds();
|
|
139
|
+
return {
|
|
140
|
+
lngLeft: southwest.lng,
|
|
141
|
+
latTop: northeast.lat,
|
|
142
|
+
lngRight: northeast.lng,
|
|
143
|
+
latBottom: southwest.lat
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
throw new Error('百度地图暂时不支持!')
|
|
147
|
+
}
|
|
148
|
+
/**获取距离distance在地球上的纬度跨度
|
|
149
|
+
* @param distance 距离(米)
|
|
150
|
+
*/
|
|
151
|
+
function getDiffLatitude(distance: number | string) {
|
|
152
|
+
let d = Number(distance);
|
|
153
|
+
const delta_lat = 2 * Math.asin(d / (2 * R)); // 两点间的纬度差值
|
|
154
|
+
return delta_lat * (180 / Math.PI); // 将弧度转换为角度
|
|
155
|
+
}
|
|
156
|
+
/**获取两点间的距离
|
|
157
|
+
* @param latLngA A点的[纬度,经度]
|
|
158
|
+
* @param latLngB B点的[纬度,经度]
|
|
159
|
+
* @param type=0 0天地图 1高德地图
|
|
160
|
+
* @returns 两点间的距离(米)
|
|
161
|
+
*/
|
|
162
|
+
function getDistance(latLngA: [number, number], latLngB: [number, number], type: 0 | 1 = 0): number {
|
|
163
|
+
let [latA, lngA] = latLngA, [latB, lngB] = latLngB, dis = 0;
|
|
164
|
+
if (type && AMap && AMap.GeometryUtil) {
|
|
165
|
+
dis = AMap.GeometryUtil.distance([lngA, latA], [lngB, latB])
|
|
166
|
+
} else {
|
|
167
|
+
dis = L.latLng(latLngA).distanceTo(latLngB);
|
|
168
|
+
}
|
|
169
|
+
return dis;
|
|
170
|
+
}
|
|
171
|
+
/** 将坐标系转换为经纬度数
|
|
172
|
+
* @param map 地图实例
|
|
173
|
+
* @param point 像素点位
|
|
174
|
+
* @returns latlng [lat,lng]
|
|
175
|
+
*/
|
|
176
|
+
function getLatLngByPoint(map: AMAP.Map | L.Map, point: [number, number] | undefined): [number, number] {
|
|
177
|
+
if (!point) return [0, 0];
|
|
178
|
+
let p: L.LatLng | AMAP.LngLat;
|
|
179
|
+
if (map instanceof L.Map) {
|
|
180
|
+
p = map.containerPointToLatLng(point)
|
|
181
|
+
} else {
|
|
182
|
+
p = map.containerToLngLat(new AMap.Pixel(point[0], point[1]));
|
|
183
|
+
}
|
|
184
|
+
return [p.lat, p.lng]
|
|
185
|
+
}
|
|
186
|
+
/** 获取指定间隔距离的经度差值
|
|
187
|
+
* @param 间隔距离
|
|
188
|
+
* @param 纬度点位集合(纬度不同,相同距离经度变化差值不一样)
|
|
189
|
+
* @param type=0 0 天地图 1 高德地图
|
|
190
|
+
*/
|
|
191
|
+
function getLngDiffByDistance(map: AMAP.Map | L.Map, distance: number = 100, latLng: [number, number][]): number {
|
|
192
|
+
if (latLng.length === 0) { return 0; }
|
|
193
|
+
let type: 0 | 1 = map instanceof L.Map ? 0 : 1;
|
|
194
|
+
let lng = 0.00001, lat = latLng.map(e => e[0]).reduce((s, v) => s + v) / latLng.length;
|
|
195
|
+
let positionA: [number, number] = [lat, 100],
|
|
196
|
+
positionB: [number, number] = [lat, 100 + lng];
|
|
197
|
+
let xMeasure = getDistance(positionA, positionB, type);
|
|
198
|
+
return distance / xMeasure * lng
|
|
199
|
+
}
|
|
200
|
+
/** 得到坐标系点位
|
|
201
|
+
* @param map 当前的地图
|
|
202
|
+
* @param latlng [纬度,经度]
|
|
203
|
+
* @returns latlng有效时返回 [x,y] , 无效时返回 [-1000, -1000]
|
|
204
|
+
*/
|
|
205
|
+
function getPointByLatlng(map: AMAP.Map | L.Map, latlng: [number, number] | undefined): [number, number] {
|
|
206
|
+
if (!latlng) return [-1000, -1000];
|
|
207
|
+
let [lat = 90, lng = 180] = latlng, p: AMAP.Pixel | L.Point;
|
|
208
|
+
if (isNaN(lat) || isNaN(lng)) return [-1000, -1000];
|
|
209
|
+
if ((map as L.Map).latLngToContainerPoint) {
|
|
210
|
+
p = (map as L.Map).latLngToContainerPoint([lat, lng]);
|
|
211
|
+
} else {
|
|
212
|
+
p = (map as AMAP.Map).lngLatToContainer([lng, lat]);
|
|
213
|
+
}
|
|
214
|
+
return [p.x, p.y]
|
|
215
|
+
}
|
|
216
|
+
/** 将经纬度数组转换为坐标系
|
|
217
|
+
* @param map 当前的地图
|
|
218
|
+
* @param latlngs [纬度,经度][]
|
|
219
|
+
* @returns latlngs有效时返回 [x,y][]
|
|
220
|
+
*/
|
|
221
|
+
function getPointsByLatlngs(map: AMAP.Map | L.Map, latlngs: [number, number][] | undefined): [number, number][] {
|
|
222
|
+
return latlngs?.map(e => getPointByLatlng(map, e)) || [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**对大小进行解析设置
|
|
226
|
+
* @param map 当前的地图
|
|
227
|
+
* @param info 大小信息和位置信息
|
|
228
|
+
* @returns [x轴的像素大小 number,y轴的像素大小 number]
|
|
229
|
+
*/
|
|
230
|
+
function getSizeByMap(map: AMAP.Map | L.Map, info: (MapPoint | MapPoints) & (MapSize | MapSizeFix)): [number, number] {
|
|
231
|
+
let { sizeFix, latlng, size = [0, 0] } = info;
|
|
232
|
+
if (!sizeFix || !latlng) {
|
|
233
|
+
Array.isArray(size) || (size = [size, size]);
|
|
234
|
+
return size;
|
|
235
|
+
}
|
|
236
|
+
let sizes: [number, number] = Array.isArray(sizeFix) ? sizeFix : [sizeFix, sizeFix];
|
|
237
|
+
let [x, y] = latlng;
|
|
238
|
+
let lngDiff = getLngDiffByDistance(map, sizes[1], [latlng]);
|
|
239
|
+
/**获取同纬度下,经度变化指定sizeFix后像素点的差值 */
|
|
240
|
+
let [x0, y0] = getPointByLatlng(map, [x, y]);
|
|
241
|
+
let [x1, y1] = getPointByLatlng(map, [x, y + lngDiff]);
|
|
242
|
+
let xd = Math.abs(x1 - x0), yd = (xd * sizes[1]) / sizes[0];
|
|
243
|
+
return [xd, yd];
|
|
244
|
+
}
|
|
245
|
+
/**获取地图实例的大小宽高 */
|
|
246
|
+
function getMapSize(map: AMAP.Map | L.Map): { w: number, h: number } {
|
|
247
|
+
let size: any = map.getSize();
|
|
248
|
+
let { x, y, width, height } = size;
|
|
249
|
+
return {
|
|
250
|
+
w: x || width,
|
|
251
|
+
h: y || height
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 转化为通用地图事件
|
|
256
|
+
* @param event L.LeafletMouseEvent | AMap.MouseEventArgs
|
|
257
|
+
* @param mapType 0 | 1 | 2 对应leaflet | 高德 | 百度 (百度暂时不支持)
|
|
258
|
+
*/
|
|
259
|
+
function getMapMouseEvent(e: L.LeafletMouseEvent | AMapMapsEvent, mapType: MapType): MapUtils.MapEventResponse<TypeToMap<MapType>> {
|
|
260
|
+
let latlng, point, page, originalEvent, type;
|
|
261
|
+
type = e.type;
|
|
262
|
+
if (mapType == 0) {
|
|
263
|
+
const { latlng: Llatlng, originalEvent: LorginalEvent, containerPoint } = e = e as L.LeafletMouseEvent;
|
|
264
|
+
const { lat, lng } = Llatlng;
|
|
265
|
+
latlng = { lat, lng };
|
|
266
|
+
const { x, y } = containerPoint;
|
|
267
|
+
point = { x, y };
|
|
268
|
+
originalEvent = LorginalEvent;
|
|
269
|
+
} else if (mapType == 1) {
|
|
270
|
+
const { pixel, originEvent, lnglat } = e = e as AMapMapsEvent;
|
|
271
|
+
const { lat, lng } = lnglat;
|
|
272
|
+
latlng = { lat, lng };
|
|
273
|
+
const { x, y } = pixel;
|
|
274
|
+
point = { x, y };
|
|
275
|
+
originalEvent = originEvent;
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
type,
|
|
279
|
+
latlng,
|
|
280
|
+
containerPoint: point,
|
|
281
|
+
orginDOMEvent: originalEvent,
|
|
282
|
+
orginMapEvent: e
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**设置地图状态
|
|
286
|
+
* showIndoorMap: boolean; // 是否在有矢量底图的时候自动展示室内地图,PC默认true,移动端默认false
|
|
287
|
+
* resizeEnable: boolean; //是否监控地图容器尺寸变化,默认值为false
|
|
288
|
+
* dragEnable: boolean; // 地图是否可通过鼠标拖拽平移,默认为true
|
|
289
|
+
* keyboardEnable: boolean; //地图是否可通过键盘控制,默认为true
|
|
290
|
+
* doubleClickZoom: boolean; // 地图是否可通过双击鼠标放大地图,默认为true
|
|
291
|
+
* zoomEnable: boolean; //地图是否可缩放,默认值为true
|
|
292
|
+
* rotateEnable: boolean; // 地图是否可旋转,3D视图默认为true,2D视图默认false
|
|
293
|
+
*/
|
|
294
|
+
function setMapStatus(map: AMAP.Map | L.Map, key: 'dragEnable', flag: boolean) {
|
|
295
|
+
let _map: any = map;
|
|
296
|
+
const amap: AMAP.Map = _map.setStatus ? _map : undefined, lmap: L.Map = _map.dragging ? _map : undefined
|
|
297
|
+
switch (key) {
|
|
298
|
+
case 'dragEnable': if (lmap) {
|
|
299
|
+
flag ? lmap.dragging.enable() : lmap.dragging.disable()
|
|
300
|
+
} else if (amap) {
|
|
301
|
+
amap.setStatus({ dragEnable: flag })
|
|
302
|
+
}; break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 根据传入实例判断地图类型
|
|
307
|
+
* @param map
|
|
308
|
+
* @returns 0 leaflet 1 高德 2 百度
|
|
309
|
+
*/
|
|
310
|
+
function getMapType(map: AMAP.Map | L.Map): MapType {
|
|
311
|
+
if (map instanceof L.Map) {
|
|
312
|
+
return 0;
|
|
313
|
+
} else if (map instanceof AMap.Map) {
|
|
314
|
+
return 1;
|
|
315
|
+
} else {
|
|
316
|
+
return 2;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
*
|
|
321
|
+
* @param map
|
|
322
|
+
* @param center 中心 latlng顺序
|
|
323
|
+
* @param zoom
|
|
324
|
+
* @param offset 中心 但需要偏移固定像素
|
|
325
|
+
*/
|
|
326
|
+
function setViewCenter(map: L.Map | AMAP.Map, center: [number, number], zoom: number, offset?: [number, number]) {
|
|
327
|
+
const mapType = getMapType(map);
|
|
328
|
+
if (offset) {
|
|
329
|
+
const centerPixel = getPointByLatlng(map, center);
|
|
330
|
+
center = getLatLngByPoint(map, [centerPixel[0] + offset[0], centerPixel[1] + offset[1]])
|
|
331
|
+
}
|
|
332
|
+
if (mapType == 0) {
|
|
333
|
+
map = map as L.Map
|
|
334
|
+
map.setView(center, zoom);
|
|
335
|
+
} else if (mapType == 1) {
|
|
336
|
+
map = map as AMAP.Map
|
|
337
|
+
map.setCenter(center.reverse() as [number, number]);
|
|
338
|
+
map.setZoom(zoom);
|
|
339
|
+
} else {
|
|
340
|
+
throw new Error('百度地图暂时不支持!')
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* 设置地图最合适缩放位置中心
|
|
345
|
+
* @param map
|
|
346
|
+
* @param allPoints 大于等于2个点
|
|
347
|
+
*/
|
|
348
|
+
function setFitBounds(map: L.Map | AMAP.Map, allPoints: [number, number][]): void;
|
|
349
|
+
/**
|
|
350
|
+
* 设置地图最合适缩放位置中心
|
|
351
|
+
* @param map
|
|
352
|
+
* @param southwest 地图左下
|
|
353
|
+
* @param northeast 地图右上
|
|
354
|
+
*/
|
|
355
|
+
function setFitBounds(map: L.Map | AMAP.Map, southwest: [number, number], northeast: [number, number]): void;
|
|
356
|
+
function setFitBounds(map: L.Map | AMAP.Map, point: [number, number] | [number, number][], point2?: [number, number]) {
|
|
357
|
+
const mapType = getMapType(map);
|
|
358
|
+
let southwest: [number, number], northeast: [number, number];
|
|
359
|
+
if (point.length == 0 || !point) return;
|
|
360
|
+
if (uc_tsIfTwoArr(point)) {
|
|
361
|
+
// 传入坐标数组
|
|
362
|
+
let maxLat = Math.max(...point.map((e) => e[0])),
|
|
363
|
+
minLat = Math.min(...point.map((e) => e[0])),
|
|
364
|
+
maxLng = Math.max(...point.map((e) => e[1])),
|
|
365
|
+
minLng = Math.min(...point.map((e) => e[1]));
|
|
366
|
+
southwest = [minLat, minLng];
|
|
367
|
+
northeast = [maxLat, maxLng];
|
|
368
|
+
} else {
|
|
369
|
+
southwest = point as [number, number];
|
|
370
|
+
northeast = point2 as [number, number];
|
|
371
|
+
}
|
|
372
|
+
if (mapType == 0) {
|
|
373
|
+
map = map as L.Map;
|
|
374
|
+
map.fitBounds([southwest, northeast]);
|
|
375
|
+
} else if (mapType == 1) {
|
|
376
|
+
map = map as AMAP.Map;
|
|
377
|
+
const bounds = new AMap.Bounds(southwest.reverse(), northeast.reverse());
|
|
378
|
+
const [zoom, center] = map.getFitZoomAndCenterByBounds(bounds);
|
|
379
|
+
map.setZoomAndCenter(zoom, center);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**判断参数是否是Cartesian2*/
|
|
383
|
+
function uc_tsIfTwoArr(value: [number, number] | [number, number][]): value is [number, number][] {
|
|
384
|
+
return value && Array.isArray(value[0]);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**移除数组指定item,会改变原数组,不改变引用地址
|
|
388
|
+
* @param arr 要操作的数组
|
|
389
|
+
* @param item 要移除的对象或某个对象key属性的值
|
|
390
|
+
* @param key 用于比较的key属性
|
|
391
|
+
*/
|
|
392
|
+
function delItem<T>(arr: T[] | undefined, item: T, key?: keyof T): T[] {
|
|
393
|
+
if (Array.isArray(arr) && arr.length > 0) {
|
|
394
|
+
let index;
|
|
395
|
+
if (key) {
|
|
396
|
+
index = arr.findIndex(e => e == item || e[key] == item[key])
|
|
397
|
+
} else {
|
|
398
|
+
index = arr.findIndex(e => e == item);
|
|
399
|
+
}
|
|
400
|
+
index >= 0 && arr.splice(index, 1);
|
|
401
|
+
}
|
|
402
|
+
return arr || [];
|
|
403
|
+
}
|
|
404
|
+
export {
|
|
405
|
+
delItem as u_arrItemDel,
|
|
406
|
+
tobd09gps84 as u_mapTobd09gps84,
|
|
407
|
+
togcj02gps84 as u_mapTogcj02gps84,
|
|
408
|
+
togps84bd09 as u_mapTogps84bd09,
|
|
409
|
+
togps84gcj02 as u_mapTogps84gcj02,
|
|
410
|
+
togcj02bd09 as u_mapTogcj02bd09,
|
|
411
|
+
tobd09cj02 as u_mapTobd09cj02,
|
|
412
|
+
getAngle as u_mapGetAngle,
|
|
413
|
+
getBounds as u_mapGetBounds,
|
|
414
|
+
getDiffLatitude as u_mapGetDiffLatitude,
|
|
415
|
+
getDistance as u_mapGetDistance,
|
|
416
|
+
getLatLngByPoint as u_mapGetLatLngByPoint,
|
|
417
|
+
getLngDiffByDistance as u_mapGetLngDiffByDistance,
|
|
418
|
+
getPointByLatlng as u_mapGetPointByLatlng,
|
|
419
|
+
getPointsByLatlngs as u_mapGetPointsByLatlngs,
|
|
420
|
+
getSizeByMap as u_mapGetSizeByMap,
|
|
421
|
+
getMapSize as u_mapGetMapSize,
|
|
422
|
+
setMapStatus as u_mapSetMapStatus,
|
|
423
|
+
getMapMouseEvent as u_mapGetMapMouseEvent,
|
|
424
|
+
setFitBounds as u_mapSetFitBounds,
|
|
425
|
+
setViewCenter as u_mapSetViewCenter,
|
|
426
|
+
getMapType as u_mapGetMapType
|
|
427
|
+
};
|
package/src/utils/txt.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 参考
|
|
3
|
+
* https://github.com/glideapps/canvas-hypertxt
|
|
4
|
+
*/
|
|
5
|
+
const resultCache: Map<string, readonly string[]> = new Map();
|
|
6
|
+
|
|
7
|
+
// font -> avg pixels per char
|
|
8
|
+
const metrics: Map<string, { count: number; size: number }> = new Map();
|
|
9
|
+
|
|
10
|
+
const hyperMaps: Map<string, Map<string, number>> = new Map();
|
|
11
|
+
|
|
12
|
+
type BreakCallback = (str: string) => readonly number[];
|
|
13
|
+
|
|
14
|
+
function backProp(text: string, realWidth: number, keyMap: Map<string, number>, temperature: number, avgSize: number) {
|
|
15
|
+
let guessWidth = 0;
|
|
16
|
+
const contribMap: Record<string, number> = {};
|
|
17
|
+
for (const char of text) {
|
|
18
|
+
const v = keyMap.get(char) ?? avgSize;
|
|
19
|
+
guessWidth += v;
|
|
20
|
+
contribMap[char] = (contribMap[char] ?? 0) + 1;
|
|
21
|
+
``;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const diff = realWidth - guessWidth;
|
|
25
|
+
|
|
26
|
+
for (const key of Object.keys(contribMap)) {
|
|
27
|
+
const numContribution = contribMap[key];
|
|
28
|
+
const contribWidth = keyMap.get(key) ?? avgSize;
|
|
29
|
+
const contribAmount = (contribWidth * numContribution) / guessWidth;
|
|
30
|
+
const adjustment = (diff * contribAmount * temperature) / numContribution;
|
|
31
|
+
const newVal = contribWidth + adjustment;
|
|
32
|
+
keyMap.set(key, newVal);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeHyperMap(ctx: CanvasRenderingContext2D, avgSize: number): Map<string, number> {
|
|
37
|
+
const result: Map<string, number> = new Map();
|
|
38
|
+
let total = 0;
|
|
39
|
+
for (const char of "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,.-+=?") {
|
|
40
|
+
const w = ctx.measureText(char).width;
|
|
41
|
+
result.set(char, w);
|
|
42
|
+
total += w;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const avg = total / result.size;
|
|
46
|
+
|
|
47
|
+
// Artisnal hand-tuned constants that have no real meaning other than they make it work better for most fonts
|
|
48
|
+
// These don't really need to be accurate, we are going to be adjusting the weights. It just converges faster
|
|
49
|
+
// if they start somewhere close.
|
|
50
|
+
const damper = 3;
|
|
51
|
+
const scaler = (avgSize / avg + damper) / (damper + 1);
|
|
52
|
+
const keys = result.keys();
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
result.set(key, (result.get(key) ?? avg) * scaler);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function measureText(ctx: CanvasRenderingContext2D, text: string, fontStyle: string, hyperMode: boolean): number {
|
|
60
|
+
const current = metrics.get(fontStyle);
|
|
61
|
+
|
|
62
|
+
if (hyperMode && current !== undefined && current.count > 20_000) {
|
|
63
|
+
let hyperMap = hyperMaps.get(fontStyle);
|
|
64
|
+
if (hyperMap === undefined) {
|
|
65
|
+
hyperMap = makeHyperMap(ctx, current.size);
|
|
66
|
+
hyperMaps.set(fontStyle, hyperMap);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (current.count > 500_000) {
|
|
70
|
+
let final = 0;
|
|
71
|
+
for (const char of text) {
|
|
72
|
+
final += hyperMap.get(char) ?? current.size;
|
|
73
|
+
}
|
|
74
|
+
return final * 1.01; //safety margin
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = ctx.measureText(text);
|
|
78
|
+
backProp(text, result.width, hyperMap, Math.max(0.05, 1 - current.count / 200_000), current.size);
|
|
79
|
+
metrics.set(fontStyle, {
|
|
80
|
+
count: current.count + text.length,
|
|
81
|
+
size: current.size,
|
|
82
|
+
});
|
|
83
|
+
return result.width;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = ctx.measureText(text);
|
|
87
|
+
|
|
88
|
+
const avg = result.width / text.length;
|
|
89
|
+
|
|
90
|
+
// we've collected enough data
|
|
91
|
+
if ((current?.count ?? 0) > 20_000) {
|
|
92
|
+
return result.width;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (current === undefined) {
|
|
96
|
+
metrics.set(fontStyle, {
|
|
97
|
+
count: text.length,
|
|
98
|
+
size: avg,
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
const diff = avg - current.size;
|
|
102
|
+
const contribution = text.length / (current.count + text.length);
|
|
103
|
+
const newVal = current.size + diff * contribution;
|
|
104
|
+
metrics.set(fontStyle, {
|
|
105
|
+
count: current.count + text.length,
|
|
106
|
+
size: newVal,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return result.width;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getSplitPoint(
|
|
114
|
+
ctx: CanvasRenderingContext2D,
|
|
115
|
+
text: string,
|
|
116
|
+
width: number,
|
|
117
|
+
fontStyle: string,
|
|
118
|
+
totalWidth: number,
|
|
119
|
+
measuredChars: number,
|
|
120
|
+
hyperMode: boolean,
|
|
121
|
+
getBreakOpportunities?: BreakCallback
|
|
122
|
+
): number {
|
|
123
|
+
if (text.length <= 1) return text.length;
|
|
124
|
+
|
|
125
|
+
// this should never happen, but we are protecting anyway
|
|
126
|
+
if (totalWidth < width) return -1;
|
|
127
|
+
|
|
128
|
+
let guess = Math.floor((width / totalWidth) * measuredChars);
|
|
129
|
+
let guessWidth = measureText(ctx, text.slice(0, Math.max(0, guess)), fontStyle, hyperMode);
|
|
130
|
+
|
|
131
|
+
const oppos = getBreakOpportunities?.(text);
|
|
132
|
+
|
|
133
|
+
if (guessWidth === width) {
|
|
134
|
+
// NAILED IT
|
|
135
|
+
} else if (guessWidth < width) {
|
|
136
|
+
while (guessWidth < width) {
|
|
137
|
+
guess++;
|
|
138
|
+
guessWidth = measureText(ctx, text.slice(0, Math.max(0, guess)), fontStyle, hyperMode);
|
|
139
|
+
}
|
|
140
|
+
guess--;
|
|
141
|
+
} else {
|
|
142
|
+
// we only need to check for spaces as we go back
|
|
143
|
+
while (guessWidth > width) {
|
|
144
|
+
const lastSpace = oppos !== undefined ? 0 : text.lastIndexOf(" ", guess - 1);
|
|
145
|
+
if (lastSpace > 0) {
|
|
146
|
+
guess = lastSpace;
|
|
147
|
+
} else {
|
|
148
|
+
guess--;
|
|
149
|
+
}
|
|
150
|
+
guessWidth = measureText(ctx, text.slice(0, Math.max(0, guess)), fontStyle, hyperMode);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (text[guess] !== " ") {
|
|
155
|
+
let greedyBreak = 0;
|
|
156
|
+
if (oppos === undefined) {
|
|
157
|
+
greedyBreak = text.lastIndexOf(" ", guess);
|
|
158
|
+
} else {
|
|
159
|
+
for (const o of oppos) {
|
|
160
|
+
if (o > guess) break;
|
|
161
|
+
greedyBreak = o;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (greedyBreak > 0) {
|
|
165
|
+
guess = greedyBreak;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return guess;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 分割canvas文本的方法
|
|
173
|
+
// getBreakOpportunities可以自定义分割条件,防止一些标点符号分割后在开头
|
|
174
|
+
// Algorithm improved from https://github.com/geongeorge/Canvas-Txt/blob/master/src/index.js
|
|
175
|
+
/**
|
|
176
|
+
* 切割多行文本
|
|
177
|
+
* @param ctx 画布上下文
|
|
178
|
+
* @param value 文本内容
|
|
179
|
+
* @param fontStyle 字体样式
|
|
180
|
+
* @param width 最大宽度
|
|
181
|
+
* @param hyperWrappingAllowed 是否允许超文本模式
|
|
182
|
+
* @param getBreakOpportunities 分割回调
|
|
183
|
+
* @returns 切割后的文本数组
|
|
184
|
+
*/
|
|
185
|
+
function splitMultilineText(
|
|
186
|
+
ctx: CanvasRenderingContext2D,
|
|
187
|
+
value: string,
|
|
188
|
+
fontStyle: string,
|
|
189
|
+
width: number,
|
|
190
|
+
hyperWrappingAllowed: boolean,
|
|
191
|
+
getBreakOpportunities?: BreakCallback
|
|
192
|
+
): readonly string[] {
|
|
193
|
+
const key = `${value}_${fontStyle}_${width}px`;
|
|
194
|
+
const cacheResult = resultCache.get(key);
|
|
195
|
+
if (cacheResult !== undefined) return cacheResult;
|
|
196
|
+
if (width <= 0) {
|
|
197
|
+
// dont render 0 width stuff
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
let result: string[] = [];
|
|
201
|
+
const encodedLines: string[] = value.split("\n");
|
|
202
|
+
const fontMetrics = metrics.get(fontStyle);
|
|
203
|
+
const safeLineGuess = fontMetrics === undefined ? value.length : (width / fontMetrics.size) * 1.5;
|
|
204
|
+
const hyperMode = hyperWrappingAllowed && fontMetrics !== undefined && fontMetrics.count > 20_000;
|
|
205
|
+
for (let line of encodedLines) {
|
|
206
|
+
let textWidth = measureText(ctx, line.slice(0, Math.max(0, safeLineGuess)), fontStyle, hyperMode);
|
|
207
|
+
let measuredChars = Math.min(line.length, safeLineGuess);
|
|
208
|
+
if (textWidth <= width) {
|
|
209
|
+
// line fits, just push it
|
|
210
|
+
result.push(line);
|
|
211
|
+
} else {
|
|
212
|
+
while (textWidth > width) {
|
|
213
|
+
const splitPoint = getSplitPoint(ctx, line, width, fontStyle, textWidth, measuredChars, hyperMode, getBreakOpportunities);
|
|
214
|
+
const subLine = line.slice(0, Math.max(0, splitPoint));
|
|
215
|
+
|
|
216
|
+
line = line.slice(subLine.length);
|
|
217
|
+
result.push(subLine);
|
|
218
|
+
textWidth = measureText(ctx, line.slice(0, Math.max(0, safeLineGuess)), fontStyle, hyperMode);
|
|
219
|
+
measuredChars = Math.min(line.length, safeLineGuess);
|
|
220
|
+
}
|
|
221
|
+
if (textWidth > 0) {
|
|
222
|
+
result.push(line);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
result = result.map((l, i) => (i === 0 ? l.trimEnd() : l.trim()));
|
|
228
|
+
resultCache.set(key, result);
|
|
229
|
+
if (resultCache.size > 500) {
|
|
230
|
+
// this is not technically LRU behavior but it works "close enough" and is much cheaper
|
|
231
|
+
resultCache.delete(resultCache.keys().next().value);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 清空记录单字符像素长度缓存
|
|
238
|
+
*/
|
|
239
|
+
function clearMultilineCache() {
|
|
240
|
+
resultCache.clear();
|
|
241
|
+
hyperMaps.clear();
|
|
242
|
+
metrics.clear();
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 用于计算canvas measureText字符长度
|
|
246
|
+
* 内部缓存其值增加计算速度
|
|
247
|
+
*/
|
|
248
|
+
export {
|
|
249
|
+
splitMultilineText as u_TextSplitMultilineText,
|
|
250
|
+
clearMultilineCache as u_TextClearMultilineCache,
|
|
251
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
|
2
|
+
{
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"noImplicitAny": true,
|
|
6
|
+
"removeComments": true,
|
|
7
|
+
"preserveConstEnums": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"target": "es2015",
|
|
10
|
+
"lib": [
|
|
11
|
+
"es2020",
|
|
12
|
+
"dom"
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
"include": [
|
|
16
|
+
"types/**/*",
|
|
17
|
+
"src/**/*",
|
|
18
|
+
],
|
|
19
|
+
"exclude": [
|
|
20
|
+
"node_modules",
|
|
21
|
+
"**/*.spec.ts"
|
|
22
|
+
]
|
|
23
|
+
}
|