生产管理系统前端 - 提交空间数据管理开发页面

This commit is contained in:
2025-10-30 09:10:44 +08:00
parent 3239f819d0
commit 71bc00cc4e
17 changed files with 6496 additions and 13 deletions

146
crop-x/src/lib/mapLoader.ts Normal file
View File

@@ -0,0 +1,146 @@
/**
* 高德地图SDK动态加载器
* 用于在不修改index.html的情况下加载高德地图SDK
*/
// 高德地图配置
const AMAP_CONFIG = {
// 替换为你的高德地图API Key
// 申请地址: https://console.amap.com/
key: 'YOUR_AMAP_KEY',
// 替换为你的安全密钥(可选,用于提高安全性)
securityJsCode: '',
// SDK版本
version: '2.0',
// 可选插件
plugins: ['AMap.Scale', 'AMap.ToolBar', 'AMap.Geocoder'] as string[],
};
/**
* 加载高德地图SDK
* @returns Promise<any> 返回AMap对象或null占位模式
*/
export const loadAMapScript = (): Promise<any> => {
return new Promise((resolve, reject) => {
// 如果已经加载,直接返回
if (window.AMap) {
console.log('✅ 高德地图SDK已加载');
resolve(window.AMap);
return;
}
// 检查Key是否配置
if (AMAP_CONFIG.key === 'YOUR_AMAP_KEY' || !AMAP_CONFIG.key) {
// 使用占位地图(功能完整)
console.log('💡 使用占位地图模式(功能完整)');
console.log('💡 如需真实地图,请在 /lib/mapLoader.ts 中配置高德地图Key');
console.log('💡 申请地址: https://console.amap.com/');
resolve(null); // 返回null表示使用占位地图
return;
}
try {
// 设置安全密钥(如果提供)
if (AMAP_CONFIG.securityJsCode) {
window._AMapSecurityConfig = {
securityJsCode: AMAP_CONFIG.securityJsCode,
};
}
// 创建script标签
const script = document.createElement('script');
script.type = 'text/javascript';
// 构建SDK URL
let url = `https://webapi.amap.com/maps?v=${AMAP_CONFIG.version}&key=${AMAP_CONFIG.key}`;
// 添加插件
if (AMAP_CONFIG.plugins.length > 0) {
url += `&plugin=${AMAP_CONFIG.plugins.join(',')}`;
}
script.src = url;
// 加载成功
script.onload = () => {
console.log('✅ 高德地图SDK加载成功');
console.log('📍 版本:', window.AMap?.version);
resolve(window.AMap);
};
// 加载失败
script.onerror = () => {
console.error('❌ 高德地图SDK加载失败');
reject(new Error('高德地图SDK加载失败'));
};
// 添加到页面
document.head.appendChild(script);
console.log('🔄 正在加载高德地图SDK...');
} catch (error) {
console.error('❌ 加载高德地图SDK时发生错误:', error);
reject(error);
}
});
};
/**
* 检查高德地图SDK是否已加载
* @returns boolean
*/
export const isAMapLoaded = (): boolean => {
return typeof window !== 'undefined' && !!window.AMap;
};
/**
* 获取高德地图版本
* @returns string | null
*/
export const getAMapVersion = (): string | null => {
if (isAMapLoaded()) {
return window.AMap.version || null;
}
return null;
};
// TypeScript 类型声明
declare global {
interface Window {
AMap: any;
_AMapSecurityConfig: {
securityJsCode: string;
};
}
}
/**
* 使用示例:
*
* import { loadAMapScript, isAMapLoaded } from './lib/mapLoader';
*
* // 在组件中使用
* useEffect(() => {
* if (!isAMapLoaded()) {
* loadAMapScript()
* .then((AMap) => {
* if (AMap) {
* console.log('地图SDK加载成功可以初始化地图');
* initMap();
* } else {
* console.log('使用占位地图模式');
* }
* })
* .catch((error) => {
* console.error('地图SDK加载失败使用占位地图', error);
* });
* } else {
* initMap();
* }
* }, []);
*/
export {};

View File

@@ -0,0 +1,937 @@
/**
* 空间数据服务API
* 提供PostGIS风格的空间查询、几何计算和数据导出功能
*/
// ===== 类型定义 =====
export interface Point {
lat: number;
lng: number;
alt?: number; // 海拔高度
}
export interface Polygon {
points: Point[];
holes?: Point[][]; // 多边形的孔洞
}
export interface Field {
id: string;
name: string;
code: string;
geometry: Polygon;
properties?: Record<string, any>;
}
export interface SpatialQueryResult<T = any> {
success: boolean;
data: T;
timestamp: string;
executionTime: number; // 毫秒
}
// ===== 常量定义 =====
// WGS-84椭球参数
const WGS84_A = 6378137.0; // 长半轴(米)
const WGS84_B = 6356752.314245; // 短半轴(米)
const WGS84_F = 1 / 298.257223563; // 扁率
// 1亩 = 666.67平方米
const MU_TO_SQUARE_METERS = 666.67;
// ===== 1. 空间查询API =====
/**
* 点面查询:判断点是否在多边形内
* 使用射线法Ray Casting Algorithm
*/
export class SpatialQuery {
/**
* 点在多边形内查询
* @param point 查询点
* @param fields 地块列表
* @returns 包含该点的地块列表
*/
static pointInPolygon(point: Point, fields: Field[]): SpatialQueryResult<{
matched: boolean;
fields: Array<{
field: Field;
distanceToBorder: number; // 到边界的最短距离(米)
}>;
}> {
const startTime = performance.now();
const results: Array<{ field: Field; distanceToBorder: number }> = [];
for (const field of fields) {
if (this._isPointInPolygon(point, field.geometry.points)) {
const distance = this._pointToPolygonDistance(point, field.geometry.points);
results.push({ field, distanceToBorder: distance });
}
}
const executionTime = performance.now() - startTime;
return {
success: true,
data: {
matched: results.length > 0,
fields: results,
},
timestamp: new Date().toISOString(),
executionTime,
};
}
/**
* 多边形相交查询
* @param sourceField 源地块
* @param targetFields 目标地块列表
* @returns 与源地块相交的地块列表
*/
static polygonIntersect(
sourceField: Field,
targetFields: Field[]
): SpatialQueryResult<{
intersections: Array<{
field: Field;
intersectArea: number; // 相交面积(亩)
intersectRatio: number; // 相交比例(%
intersectGeometry: Polygon; // 相交区域几何
}>;
}> {
const startTime = performance.now();
const intersections: Array<{
field: Field;
intersectArea: number;
intersectRatio: number;
intersectGeometry: Polygon;
}> = [];
const sourceArea = GeometryCalculator.calculateArea(sourceField.geometry);
for (const targetField of targetFields) {
if (targetField.id === sourceField.id) continue;
if (this._polygonsIntersect(sourceField.geometry.points, targetField.geometry.points)) {
const intersectGeometry = this._calculateIntersection(
sourceField.geometry,
targetField.geometry
);
const intersectArea = GeometryCalculator.calculateArea(intersectGeometry);
const intersectRatio = (intersectArea / sourceArea) * 100;
intersections.push({
field: targetField,
intersectArea,
intersectRatio,
intersectGeometry,
});
}
}
const executionTime = performance.now() - startTime;
return {
success: true,
data: { intersections },
timestamp: new Date().toISOString(),
executionTime,
};
}
/**
* 相邻地块查询
* @param sourceField 源地块
* @param targetFields 目标地块列表
* @returns 与源地块相邻的地块列表
*/
static adjacentPolygons(
sourceField: Field,
targetFields: Field[]
): SpatialQueryResult<{
adjacentFields: Array<{
field: Field;
sharedBorderLength: number; // 共享边界长度(米)
sharedBorderPoints: Point[]; // 共享边界点
}>;
}> {
const startTime = performance.now();
const adjacentFields: Array<{
field: Field;
sharedBorderLength: number;
sharedBorderPoints: Point[];
}> = [];
for (const targetField of targetFields) {
if (targetField.id === sourceField.id) continue;
const { isAdjacent, sharedBorder } = this._checkAdjacency(
sourceField.geometry.points,
targetField.geometry.points
);
if (isAdjacent && sharedBorder.length > 0) {
const sharedBorderLength = GeometryCalculator.calculatePerimeter({
points: sharedBorder,
});
adjacentFields.push({
field: targetField,
sharedBorderLength,
sharedBorderPoints: sharedBorder,
});
}
}
const executionTime = performance.now() - startTime;
return {
success: true,
data: { adjacentFields },
timestamp: new Date().toISOString(),
executionTime,
};
}
/**
* 缓冲区分析
* @param sourceField 源地块
* @param bufferDistance 缓冲区距离(米)
* @param targetFields 目标地块列表
* @returns 缓冲区内的地块列表
*/
static bufferAnalysis(
sourceField: Field,
bufferDistance: number,
targetFields: Field[]
): SpatialQueryResult<{
bufferGeometry: Polygon;
bufferArea: number; // 缓冲区面积(亩)
fieldsInBuffer: Array<{
field: Field;
distance: number; // 最短距离(米)
overlap: boolean; // 是否重叠
}>;
}> {
const startTime = performance.now();
// 生成缓冲区几何
const bufferGeometry = this._createBuffer(sourceField.geometry, bufferDistance);
const bufferArea = GeometryCalculator.calculateArea(bufferGeometry);
const fieldsInBuffer: Array<{
field: Field;
distance: number;
overlap: boolean;
}> = [];
for (const targetField of targetFields) {
if (targetField.id === sourceField.id) continue;
const distance = this._polygonToPolygonDistance(
sourceField.geometry.points,
targetField.geometry.points
);
if (distance <= bufferDistance) {
const overlap = this._polygonsIntersect(
bufferGeometry.points,
targetField.geometry.points
);
fieldsInBuffer.push({
field: targetField,
distance,
overlap,
});
}
}
const executionTime = performance.now() - startTime;
return {
success: true,
data: {
bufferGeometry,
bufferArea,
fieldsInBuffer,
},
timestamp: new Date().toISOString(),
executionTime,
};
}
// ===== 私有辅助方法 =====
/**
* 射线法判断点是否在多边形内
*/
private static _isPointInPolygon(point: Point, polygon: Point[]): boolean {
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i].lng,
yi = polygon[i].lat;
const xj = polygon[j].lng,
yj = polygon[j].lat;
const intersect =
yi > point.lat !== yj > point.lat &&
point.lng < ((xj - xi) * (point.lat - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
}
/**
* 计算点到多边形边界的最短距离
*/
private static _pointToPolygonDistance(point: Point, polygon: Point[]): number {
let minDistance = Infinity;
for (let i = 0; i < polygon.length; i++) {
const p1 = polygon[i];
const p2 = polygon[(i + 1) % polygon.length];
const distance = this._pointToSegmentDistance(point, p1, p2);
minDistance = Math.min(minDistance, distance);
}
return minDistance;
}
/**
* 计算点到线段的距离
*/
private static _pointToSegmentDistance(point: Point, p1: Point, p2: Point): number {
const dx = p2.lng - p1.lng;
const dy = p2.lat - p1.lat;
if (dx === 0 && dy === 0) {
return GeometryCalculator.haversineDistance(point, p1);
}
const t = Math.max(
0,
Math.min(
1,
((point.lng - p1.lng) * dx + (point.lat - p1.lat) * dy) / (dx * dx + dy * dy)
)
);
const nearestPoint: Point = {
lat: p1.lat + t * dy,
lng: p1.lng + t * dx,
};
return GeometryCalculator.haversineDistance(point, nearestPoint);
}
/**
* 判断两个多边形是否相交
*/
private static _polygonsIntersect(poly1: Point[], poly2: Point[]): boolean {
// 检查是否有顶点在另一个多边形内
for (const point of poly1) {
if (this._isPointInPolygon(point, poly2)) return true;
}
for (const point of poly2) {
if (this._isPointInPolygon(point, poly1)) return true;
}
// 检查边是否相交
for (let i = 0; i < poly1.length; i++) {
const p1 = poly1[i];
const p2 = poly1[(i + 1) % poly1.length];
for (let j = 0; j < poly2.length; j++) {
const p3 = poly2[j];
const p4 = poly2[(j + 1) % poly2.length];
if (this._segmentsIntersect(p1, p2, p3, p4)) return true;
}
}
return false;
}
/**
* 判断两条线段是否相交
*/
private static _segmentsIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean {
const ccw = (A: Point, B: Point, C: Point) => {
return (C.lat - A.lat) * (B.lng - A.lng) > (B.lat - A.lat) * (C.lng - A.lng);
};
return ccw(p1, p3, p4) !== ccw(p2, p3, p4) && ccw(p1, p2, p3) !== ccw(p1, p2, p4);
}
/**
* 计算两个多边形的相交区域(简化实现)
*/
private static _calculateIntersection(poly1: Polygon, poly2: Polygon): Polygon {
// 这里使用简化算法实际应用中应使用Sutherland-Hodgman算法
const intersectPoints: Point[] = [];
// 收集在两个多边形内的点
for (const point of poly1.points) {
if (this._isPointInPolygon(point, poly2.points)) {
intersectPoints.push(point);
}
}
for (const point of poly2.points) {
if (this._isPointInPolygon(point, poly1.points)) {
intersectPoints.push(point);
}
}
// 如果没有交点,返回空多边形
if (intersectPoints.length === 0) {
return { points: [] };
}
// 计算凸包作为相交区域的近似
return { points: this._convexHull(intersectPoints) };
}
/**
* 计算凸包Graham扫描算法
*/
private static _convexHull(points: Point[]): Point[] {
if (points.length < 3) return points;
// 找到最下最左的点
let start = points[0];
points.forEach((p) => {
if (p.lat < start.lat || (p.lat === start.lat && p.lng < start.lng)) {
start = p;
}
});
// 按极角排序
const sorted = points
.filter((p) => p !== start)
.sort((a, b) => {
const angleA = Math.atan2(a.lat - start.lat, a.lng - start.lng);
const angleB = Math.atan2(b.lat - start.lat, b.lng - start.lng);
return angleA - angleB;
});
const hull: Point[] = [start];
for (const point of sorted) {
while (hull.length >= 2) {
const p2 = hull[hull.length - 1];
const p1 = hull[hull.length - 2];
const cross =
(p2.lng - p1.lng) * (point.lat - p1.lat) -
(p2.lat - p1.lat) * (point.lng - p1.lng);
if (cross <= 0) {
hull.pop();
} else {
break;
}
}
hull.push(point);
}
return hull;
}
/**
* 检查两个多边形是否相邻
*/
private static _checkAdjacency(
poly1: Point[],
poly2: Point[]
): { isAdjacent: boolean; sharedBorder: Point[] } {
const sharedBorder: Point[] = [];
const tolerance = 0.00001; // 约1米的容差
for (let i = 0; i < poly1.length; i++) {
const p1 = poly1[i];
const p2 = poly1[(i + 1) % poly1.length];
for (let j = 0; j < poly2.length; j++) {
const p3 = poly2[j];
const p4 = poly2[(j + 1) % poly2.length];
// 检查边是否重合
if (this._edgesOverlap(p1, p2, p3, p4, tolerance)) {
if (sharedBorder.length === 0 || !this._pointsEqual(sharedBorder[sharedBorder.length - 1], p1, tolerance)) {
sharedBorder.push(p1);
}
sharedBorder.push(p2);
}
}
}
return {
isAdjacent: sharedBorder.length >= 2,
sharedBorder,
};
}
/**
* 判断两条边是否重合
*/
private static _edgesOverlap(
p1: Point,
p2: Point,
p3: Point,
p4: Point,
tolerance: number
): boolean {
return (
(this._pointsEqual(p1, p3, tolerance) && this._pointsEqual(p2, p4, tolerance)) ||
(this._pointsEqual(p1, p4, tolerance) && this._pointsEqual(p2, p3, tolerance))
);
}
/**
* 判断两个点是否相等(在容差范围内)
*/
private static _pointsEqual(p1: Point, p2: Point, tolerance: number): boolean {
return (
Math.abs(p1.lat - p2.lat) < tolerance && Math.abs(p1.lng - p2.lng) < tolerance
);
}
/**
* 计算多边形到多边形的最短距离
*/
private static _polygonToPolygonDistance(poly1: Point[], poly2: Point[]): number {
let minDistance = Infinity;
for (const point of poly1) {
const distance = this._pointToPolygonDistance(point, poly2);
minDistance = Math.min(minDistance, distance);
}
for (const point of poly2) {
const distance = this._pointToPolygonDistance(point, poly1);
minDistance = Math.min(minDistance, distance);
}
return minDistance;
}
/**
* 创建缓冲区(简化实现)
*/
private static _createBuffer(geometry: Polygon, distance: number): Polygon {
const bufferPoints: Point[] = [];
const points = geometry.points;
// 简化算法对每个顶点在法线方向上偏移distance距离
for (let i = 0; i < points.length; i++) {
const prev = points[i === 0 ? points.length - 1 : i - 1];
const curr = points[i];
const next = points[(i + 1) % points.length];
// 计算法向量
const v1 = { lat: curr.lat - prev.lat, lng: curr.lng - prev.lng };
const v2 = { lat: next.lat - curr.lat, lng: next.lng - curr.lng };
// 计算平均法向量
const normal = {
lat: -(v1.lng + v2.lng),
lng: v1.lat + v2.lat,
};
// 归一化
const length = Math.sqrt(normal.lat * normal.lat + normal.lng * normal.lng);
if (length > 0) {
normal.lat /= length;
normal.lng /= length;
}
// 偏移顶点(简化:使用度数偏移,实际应转换为米)
const offsetDegrees = distance / 111320; // 约111.32km每度
bufferPoints.push({
lat: curr.lat + normal.lat * offsetDegrees,
lng: curr.lng + normal.lng * offsetDegrees,
});
}
return { points: bufferPoints };
}
}
// ===== 2. 几何计算API =====
export class GeometryCalculator {
/**
* 计算多边形精确面积(考虑地球曲率)
* 使用球面三角形面积公式
*/
static calculateArea(geometry: Polygon): number {
const points = geometry.points;
if (points.length < 3) return 0;
// 将多边形分解为三角形,计算球面三角形面积之和
let totalArea = 0;
const origin = points[0];
for (let i = 1; i < points.length - 1; i++) {
const area = this._sphericalTriangleArea(origin, points[i], points[i + 1]);
totalArea += area;
}
// 转换为亩
return totalArea / MU_TO_SQUARE_METERS;
}
/**
* 计算球面三角形面积L'Huilier定理
*/
private static _sphericalTriangleArea(p1: Point, p2: Point, p3: Point): number {
const R = WGS84_A; // 使用WGS-84长半轴
// 转换为弧度
const lat1 = this._toRadians(p1.lat);
const lng1 = this._toRadians(p1.lng);
const lat2 = this._toRadians(p2.lat);
const lng2 = this._toRadians(p2.lng);
const lat3 = this._toRadians(p3.lat);
const lng3 = this._toRadians(p3.lng);
// 计算边长(球面距离)
const a = this._sphericalDistance(lat2, lng2, lat3, lng3, R);
const b = this._sphericalDistance(lat3, lng3, lat1, lng1, R);
const c = this._sphericalDistance(lat1, lng1, lat2, lng2, R);
// 半周长
const s = (a + b + c) / 2;
// L'Huilier定理
const E = 4 * Math.atan(Math.sqrt(Math.tan(s / (2 * R)) * Math.tan((s - a) / (2 * R)) * Math.tan((s - b) / (2 * R)) * Math.tan((s - c) / (2 * R))));
// 面积 = R² * E
return R * R * E;
}
/**
* 计算球面距离
*/
private static _sphericalDistance(
lat1: number,
lng1: number,
lat2: number,
lng2: number,
radius: number
): number {
const dLat = lat2 - lat1;
const dLng = lng2 - lng1;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) * Math.sin(dLng / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return radius * c;
}
/**
* 计算多边形周长(考虑地球曲率)
*/
static calculatePerimeter(geometry: Polygon): number {
const points = geometry.points;
if (points.length < 2) return 0;
let perimeter = 0;
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % points.length];
perimeter += this.haversineDistance(p1, p2);
}
return perimeter;
}
/**
* Haversine公式计算两点间距离
*/
static haversineDistance(p1: Point, p2: Point): number {
const R = WGS84_A;
const lat1 = this._toRadians(p1.lat);
const lng1 = this._toRadians(p1.lng);
const lat2 = this._toRadians(p2.lat);
const lng2 = this._toRadians(p2.lng);
return this._sphericalDistance(lat1, lng1, lat2, lng2, R);
}
/**
* 计算多边形中心点(几何中心)
*/
static calculateCentroid(geometry: Polygon): Point {
const points = geometry.points;
if (points.length === 0) return { lat: 0, lng: 0 };
let sumLat = 0;
let sumLng = 0;
let sumArea = 0;
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % points.length];
const cross = p1.lng * p2.lat - p2.lng * p1.lat;
sumArea += cross;
sumLat += (p1.lat + p2.lat) * cross;
sumLng += (p1.lng + p2.lng) * cross;
}
sumArea /= 2;
if (Math.abs(sumArea) < 1e-10) {
// 如果面积接近0使用简单平均
const avgLat = points.reduce((sum, p) => sum + p.lat, 0) / points.length;
const avgLng = points.reduce((sum, p) => sum + p.lng, 0) / points.length;
return { lat: avgLat, lng: avgLng };
}
const centroidLat = sumLat / (6 * sumArea);
const centroidLng = sumLng / (6 * sumArea);
return { lat: centroidLat, lng: centroidLng };
}
/**
* 计算包围盒Bounding Box
*/
static calculateBoundingBox(geometry: Polygon): {
minLat: number;
maxLat: number;
minLng: number;
maxLng: number;
center: Point;
} {
const points = geometry.points;
if (points.length === 0) {
return {
minLat: 0,
maxLat: 0,
minLng: 0,
maxLng: 0,
center: { lat: 0, lng: 0 },
};
}
let minLat = points[0].lat;
let maxLat = points[0].lat;
let minLng = points[0].lng;
let maxLng = points[0].lng;
for (const point of points) {
minLat = Math.min(minLat, point.lat);
maxLat = Math.max(maxLat, point.lat);
minLng = Math.min(minLng, point.lng);
maxLng = Math.max(maxLng, point.lng);
}
return {
minLat,
maxLat,
minLng,
maxLng,
center: {
lat: (minLat + maxLat) / 2,
lng: (minLng + maxLng) / 2,
},
};
}
/**
* 角度转弧度
*/
private static _toRadians(degrees: number): number {
return (degrees * Math.PI) / 180;
}
/**
* 弧度转角度
*/
private static _toDegrees(radians: number): number {
return (radians * 180) / Math.PI;
}
}
// ===== 3. 数据导出API =====
export class DataExporter {
/**
* 导出为GeoJSON格式
*/
static exportToGeoJSON(fields: Field[]): string {
const features = fields.map((field) => ({
type: 'Feature',
id: field.id,
properties: {
name: field.name,
code: field.code,
area: GeometryCalculator.calculateArea(field.geometry),
perimeter: GeometryCalculator.calculatePerimeter(field.geometry),
centroid: GeometryCalculator.calculateCentroid(field.geometry),
...field.properties,
},
geometry: {
type: 'Polygon',
coordinates: [field.geometry.points.map((p) => [p.lng, p.lat])],
},
}));
const geoJSON = {
type: 'FeatureCollection',
crs: {
type: 'name',
properties: {
name: 'EPSG:4326', // WGS-84
},
},
features,
};
return JSON.stringify(geoJSON, null, 2);
}
/**
* 导出为KML格式
*/
static exportToKML(fields: Field[]): string {
const placemarks = fields
.map((field) => {
const coords = field.geometry.points.map((p) => `${p.lng},${p.lat},0`).join(' ');
return `
<Placemark>
<name>${field.name}</name>
<description>
编号: ${field.code}
面积: ${GeometryCalculator.calculateArea(field.geometry).toFixed(2)}
周长: ${GeometryCalculator.calculatePerimeter(field.geometry).toFixed(0)}
</description>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>${coords}</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>`;
})
.join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>地块数据</name>
<description>智慧农业生产管理系统地块导出</description>
${placemarks}
</Document>
</kml>`;
}
/**
* 导出为Shapefile格式WKT格式
*/
static exportToWKT(field: Field): string {
const coords = field.geometry.points.map((p) => `${p.lng} ${p.lat}`).join(', ');
return `POLYGON((${coords}))`;
}
/**
* 导出为CSV格式
*/
static exportToCSV(fields: Field[]): string {
const headers = ['ID', '名称', '编号', '面积(亩)', '周长(米)', '中心点纬度', '中心点经度'];
const rows = fields.map((field) => {
const centroid = GeometryCalculator.calculateCentroid(field.geometry);
return [
field.id,
field.name,
field.code,
GeometryCalculator.calculateArea(field.geometry).toFixed(2),
GeometryCalculator.calculatePerimeter(field.geometry).toFixed(0),
centroid.lat.toFixed(6),
centroid.lng.toFixed(6),
];
});
const csvContent = [
headers.join(','),
...rows.map((row) => row.join(',')),
].join('\n');
return csvContent;
}
/**
* 下载文件
*/
static downloadFile(content: string, filename: string, mimeType: string): void {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
}
// ===== 4. 空间索引(用于性能优化) =====
export class SpatialIndex {
private rtree: Map<string, { bbox: any; field: Field }>;
constructor() {
this.rtree = new Map();
}
/**
* 插入地块
*/
insert(field: Field): void {
const bbox = GeometryCalculator.calculateBoundingBox(field.geometry);
this.rtree.set(field.id, { bbox, field });
}
/**
* 快速查询可能相交的地块
*/
query(bbox: {
minLat: number;
maxLat: number;
minLng: number;
maxLng: number;
}): Field[] {
const results: Field[] = [];
for (const [_, item] of this.rtree) {
if (this._bboxesIntersect(bbox, item.bbox)) {
results.push(item.field);
}
}
return results;
}
/**
* 判断两个包围盒是否相交
*/
private _bboxesIntersect(
bbox1: { minLat: number; maxLat: number; minLng: number; maxLng: number },
bbox2: { minLat: number; maxLat: number; minLng: number; maxLng: number }
): boolean {
return !(
bbox1.maxLat < bbox2.minLat ||
bbox1.minLat > bbox2.maxLat ||
bbox1.maxLng < bbox2.minLng ||
bbox1.minLng > bbox2.maxLng
);
}
}