geo-types-cz 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 +21 -0
- package/README.md +297 -0
- package/dist/bbox.d.ts +25 -0
- package/dist/bbox.d.ts.map +1 -0
- package/dist/bbox.js +98 -0
- package/dist/bbox.js.map +1 -0
- package/dist/crs.d.ts +37 -0
- package/dist/crs.d.ts.map +1 -0
- package/dist/crs.js +81 -0
- package/dist/crs.js.map +1 -0
- package/dist/extensions.d.ts +150 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +63 -0
- package/dist/extensions.js.map +1 -0
- package/dist/feature.d.ts +29 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +42 -0
- package/dist/feature.js.map +1 -0
- package/dist/geometry.d.ts +58 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +78 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +238 -0
- package/dist/utils.js.map +1 -0
- package/package.json +40 -0
- package/src/bbox.ts +142 -0
- package/src/crs.ts +107 -0
- package/src/extensions.ts +231 -0
- package/src/feature.ts +70 -0
- package/src/geometry.ts +138 -0
- package/src/index.ts +156 -0
- package/src/utils.ts +289 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GIS工具函数
|
|
3
|
+
* 提供常用的地理计算和操作函数
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Position, Geometry, Point, LineString, Polygon, GeometryType } from './geometry';
|
|
7
|
+
import { Feature, FeatureCollection } from './feature';
|
|
8
|
+
import { BBox, createBBox2D } from './bbox';
|
|
9
|
+
|
|
10
|
+
// 地球半径(米)
|
|
11
|
+
const EARTH_RADIUS = 6378137;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// 角度转弧度
|
|
15
|
+
export function degreesToRadians(degrees: number): number {
|
|
16
|
+
return degrees * (Math.PI / 180);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 弧度转角度
|
|
20
|
+
export function radiansToDegrees(radians: number): number {
|
|
21
|
+
return radians * (180 / Math.PI);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 计算两点间的距离(米)- 使用Haversine公式
|
|
25
|
+
export function calculateDistance(pos1: Position, pos2: Position): number {
|
|
26
|
+
const [lon1, lat1] = pos1;
|
|
27
|
+
const [lon2, lat2] = pos2;
|
|
28
|
+
|
|
29
|
+
const dLat = degreesToRadians(lat2 - lat1);
|
|
30
|
+
const dLon = degreesToRadians(lon2 - lon1);
|
|
31
|
+
|
|
32
|
+
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
33
|
+
Math.cos(degreesToRadians(lat1)) * Math.cos(degreesToRadians(lat2)) *
|
|
34
|
+
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
35
|
+
|
|
36
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
37
|
+
|
|
38
|
+
return EARTH_RADIUS * c;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 计算方位角(度)
|
|
42
|
+
export function calculateBearing(pos1: Position, pos2: Position): number {
|
|
43
|
+
const [lon1, lat1] = pos1;
|
|
44
|
+
const [lon2, lat2] = pos2;
|
|
45
|
+
|
|
46
|
+
const dLon = degreesToRadians(lon2 - lon1);
|
|
47
|
+
const lat1Rad = degreesToRadians(lat1);
|
|
48
|
+
const lat2Rad = degreesToRadians(lat2);
|
|
49
|
+
|
|
50
|
+
const y = Math.sin(dLon) * Math.cos(lat2Rad);
|
|
51
|
+
const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
|
|
52
|
+
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(dLon);
|
|
53
|
+
|
|
54
|
+
const bearing = Math.atan2(y, x);
|
|
55
|
+
|
|
56
|
+
return (radiansToDegrees(bearing) + 360) % 360;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 根据起点、距离和方位角计算终点
|
|
60
|
+
export function calculateDestination(
|
|
61
|
+
start: Position,
|
|
62
|
+
distance: number,
|
|
63
|
+
bearing: number
|
|
64
|
+
): Position {
|
|
65
|
+
const [lon, lat] = start;
|
|
66
|
+
const bearingRad = degreesToRadians(bearing);
|
|
67
|
+
const latRad = degreesToRadians(lat);
|
|
68
|
+
const lonRad = degreesToRadians(lon);
|
|
69
|
+
|
|
70
|
+
const angularDistance = distance / EARTH_RADIUS;
|
|
71
|
+
|
|
72
|
+
const destLatRad = Math.asin(
|
|
73
|
+
Math.sin(latRad) * Math.cos(angularDistance) +
|
|
74
|
+
Math.cos(latRad) * Math.sin(angularDistance) * Math.cos(bearingRad)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const destLonRad = lonRad + Math.atan2(
|
|
78
|
+
Math.sin(bearingRad) * Math.sin(angularDistance) * Math.cos(latRad),
|
|
79
|
+
Math.cos(angularDistance) - Math.sin(latRad) * Math.sin(destLatRad)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return [radiansToDegrees(destLonRad), radiansToDegrees(destLatRad)];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 计算线段长度
|
|
86
|
+
export function calculateLineLength(lineString: LineString): number {
|
|
87
|
+
let totalLength = 0;
|
|
88
|
+
const coordinates = lineString.coordinates;
|
|
89
|
+
|
|
90
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
91
|
+
totalLength += calculateDistance(coordinates[i - 1], coordinates[i]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return totalLength;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 计算多边形面积(平方米)- 使用球面三角形公式
|
|
98
|
+
export function calculatePolygonArea(polygon: Polygon): number {
|
|
99
|
+
const coordinates = polygon.coordinates[0]; // 外环
|
|
100
|
+
let area = 0;
|
|
101
|
+
|
|
102
|
+
if (coordinates.length < 3) return 0;
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < coordinates.length - 1; i++) {
|
|
105
|
+
const p1 = coordinates[i];
|
|
106
|
+
const p2 = coordinates[i + 1];
|
|
107
|
+
|
|
108
|
+
area += degreesToRadians(p2[0] - p1[0]) *
|
|
109
|
+
(2 + Math.sin(degreesToRadians(p1[1])) + Math.sin(degreesToRadians(p2[1])));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
area = Math.abs(area * EARTH_RADIUS * EARTH_RADIUS / 2);
|
|
113
|
+
|
|
114
|
+
return area;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 计算几何对象的边界框
|
|
118
|
+
export function calculateGeometryBBox(geometry: Geometry): BBox {
|
|
119
|
+
let minLon = Infinity;
|
|
120
|
+
let minLat = Infinity;
|
|
121
|
+
let maxLon = -Infinity;
|
|
122
|
+
let maxLat = -Infinity;
|
|
123
|
+
|
|
124
|
+
function processPosition(pos: Position) {
|
|
125
|
+
const [lon, lat] = pos;
|
|
126
|
+
minLon = Math.min(minLon, lon);
|
|
127
|
+
minLat = Math.min(minLat, lat);
|
|
128
|
+
maxLon = Math.max(maxLon, lon);
|
|
129
|
+
maxLat = Math.max(maxLat, lat);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function processCoordinates(coords: any) {
|
|
133
|
+
if (Array.isArray(coords[0])) {
|
|
134
|
+
coords.forEach(processCoordinates);
|
|
135
|
+
} else {
|
|
136
|
+
processPosition(coords as Position);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
switch (geometry.type) {
|
|
141
|
+
case 'Point':
|
|
142
|
+
processPosition(geometry.coordinates);
|
|
143
|
+
break;
|
|
144
|
+
case 'LineString':
|
|
145
|
+
case 'MultiPoint':
|
|
146
|
+
geometry.coordinates.forEach(processPosition);
|
|
147
|
+
break;
|
|
148
|
+
case 'Polygon':
|
|
149
|
+
case 'MultiLineString':
|
|
150
|
+
geometry.coordinates.forEach(ring => ring.forEach(processPosition));
|
|
151
|
+
break;
|
|
152
|
+
case 'MultiPolygon':
|
|
153
|
+
geometry.coordinates.forEach(polygon =>
|
|
154
|
+
polygon.forEach(ring => ring.forEach(processPosition))
|
|
155
|
+
);
|
|
156
|
+
break;
|
|
157
|
+
case 'GeometryCollection':
|
|
158
|
+
geometry.geometries.forEach(geom => {
|
|
159
|
+
const bbox = calculateGeometryBBox(geom);
|
|
160
|
+
minLon = Math.min(minLon, bbox[0]);
|
|
161
|
+
minLat = Math.min(minLat, bbox[1]);
|
|
162
|
+
maxLon = Math.max(maxLon, bbox[2]);
|
|
163
|
+
maxLat = Math.max(maxLat, bbox[3]);
|
|
164
|
+
});
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return createBBox2D(minLon, minLat, maxLon, maxLat);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 计算要素集合的边界框
|
|
172
|
+
export function calculateFeatureCollectionBBox(featureCollection: FeatureCollection): BBox {
|
|
173
|
+
let minLon = Infinity;
|
|
174
|
+
let minLat = Infinity;
|
|
175
|
+
let maxLon = -Infinity;
|
|
176
|
+
let maxLat = -Infinity;
|
|
177
|
+
|
|
178
|
+
featureCollection.features.forEach(feature => {
|
|
179
|
+
if (feature.geometry) {
|
|
180
|
+
const bbox = calculateGeometryBBox(feature.geometry);
|
|
181
|
+
minLon = Math.min(minLon, bbox[0]);
|
|
182
|
+
minLat = Math.min(minLat, bbox[1]);
|
|
183
|
+
maxLon = Math.max(maxLon, bbox[2]);
|
|
184
|
+
maxLat = Math.max(maxLat, bbox[3]);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return createBBox2D(minLon, minLat, maxLon, maxLat);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 简化线段(Douglas-Peucker算法)
|
|
192
|
+
export function simplifyLineString(lineString: LineString, tolerance: number): LineString {
|
|
193
|
+
const coordinates = lineString.coordinates;
|
|
194
|
+
|
|
195
|
+
if (coordinates.length <= 2) {
|
|
196
|
+
return lineString;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function perpendicularDistance(point: Position, lineStart: Position, lineEnd: Position): number {
|
|
200
|
+
const [x, y] = point;
|
|
201
|
+
const [x1, y1] = lineStart;
|
|
202
|
+
const [x2, y2] = lineEnd;
|
|
203
|
+
|
|
204
|
+
const A = x - x1;
|
|
205
|
+
const B = y - y1;
|
|
206
|
+
const C = x2 - x1;
|
|
207
|
+
const D = y2 - y1;
|
|
208
|
+
|
|
209
|
+
const dot = A * C + B * D;
|
|
210
|
+
const lenSq = C * C + D * D;
|
|
211
|
+
|
|
212
|
+
if (lenSq === 0) {
|
|
213
|
+
return Math.sqrt(A * A + B * B);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const param = dot / lenSq;
|
|
217
|
+
|
|
218
|
+
let xx, yy;
|
|
219
|
+
|
|
220
|
+
if (param < 0) {
|
|
221
|
+
xx = x1;
|
|
222
|
+
yy = y1;
|
|
223
|
+
} else if (param > 1) {
|
|
224
|
+
xx = x2;
|
|
225
|
+
yy = y2;
|
|
226
|
+
} else {
|
|
227
|
+
xx = x1 + param * C;
|
|
228
|
+
yy = y1 + param * D;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const dx = x - xx;
|
|
232
|
+
const dy = y - yy;
|
|
233
|
+
|
|
234
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function douglasPeucker(points: Position[], tolerance: number): Position[] {
|
|
238
|
+
if (points.length <= 2) {
|
|
239
|
+
return points;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let maxDistance = 0;
|
|
243
|
+
let maxIndex = 0;
|
|
244
|
+
|
|
245
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
246
|
+
const distance = perpendicularDistance(points[i], points[0], points[points.length - 1]);
|
|
247
|
+
if (distance > maxDistance) {
|
|
248
|
+
maxDistance = distance;
|
|
249
|
+
maxIndex = i;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (maxDistance > tolerance) {
|
|
254
|
+
const left = douglasPeucker(points.slice(0, maxIndex + 1), tolerance);
|
|
255
|
+
const right = douglasPeucker(points.slice(maxIndex), tolerance);
|
|
256
|
+
|
|
257
|
+
return left.slice(0, -1).concat(right);
|
|
258
|
+
} else {
|
|
259
|
+
return [points[0], points[points.length - 1]];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const simplified = douglasPeucker(coordinates, tolerance);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
type: lineString.type,
|
|
267
|
+
coordinates: simplified
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 创建缓冲区(简单的矩形缓冲区)
|
|
272
|
+
export function createBuffer(geometry: Point, distance: number): Polygon {
|
|
273
|
+
const [lon, lat] = geometry.coordinates;
|
|
274
|
+
|
|
275
|
+
// 简化计算:使用度数作为近似
|
|
276
|
+
const deltaLon = distance / (111320 * Math.cos(degreesToRadians(lat)));
|
|
277
|
+
const deltaLat = distance / 110540;
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
type: GeometryType.Polygon,
|
|
281
|
+
coordinates: [[
|
|
282
|
+
[lon - deltaLon, lat - deltaLat],
|
|
283
|
+
[lon + deltaLon, lat - deltaLat],
|
|
284
|
+
[lon + deltaLon, lat + deltaLat],
|
|
285
|
+
[lon - deltaLon, lat + deltaLat],
|
|
286
|
+
[lon - deltaLon, lat - deltaLat]
|
|
287
|
+
]]
|
|
288
|
+
};
|
|
289
|
+
}
|