Files
smart-crop-ui/src/GEOMETRY_MAP_PICKER_IMPLEMENTATION.md

487 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🔧 几何计算工具 - 地图选点实现指南
## 📋 实现概述
本文档说明如何在几何计算工具中集成地图选点功能。
---
## ✅ 已完成的工作
### 1. 创建MapPointPicker组件
**文件位置:** `/components/field/MapPointPicker.tsx`
**功能:**
- ✅ 高德地图集成
- ✅ 多边形和单点两种模式
- ✅ 标记管理和多边形绘制
- ✅ 点击添加/删除坐标点
- ✅ 坐标列表显示
### 2. 更新状态管理
**在 FieldSpatialQuery.tsx 中添加:**
```typescript
// 地图选点模式
const [mapPickMode, setMapPickMode] = useState<'polygon' | 'distance-p1' | 'distance-p2' | null>(null);
const [editingPointIndex, setEditingPointIndex] = useState<number | null>(null);
```
### 3. 导入组件
```typescript
import { MapPointPicker } from './MapPointPicker';
import { MousePointer2, Edit3 } from 'lucide-react';
```
---
## 🔨 需要手动完成的集成
由于文件中存在重复的TabsContent结构需要手动修改。以下是集成步骤
### 步骤1: 修改面积计算TabsContent
**找到这部分代码约第1470行**
```typescript
{/* 面积计算 */}
<TabsContent value="area" className="space-y-4">
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<h3>多边形坐标点</h3>
<Button size="sm" onClick={handleAddGeomPoint}>
<Plus className="w-4 h-4 mr-2" />
添加点
</Button>
</div>
<div className="space-y-3 max-h-96 overflow-y-auto">
{geomPoints.map((point, index) => (
// ... 坐标输入框
))}
</div>
<Button onClick={handleCalculateArea} ...>
计算面积 (ST_Area)
</Button>
</Card>
{/* 结果显示 */}
</TabsContent>
```
**替换为:**
```typescript
{/* 面积计算 */}
<TabsContent value="area" className="space-y-4">
{/* 选择方式切换 */}
<div className="flex gap-2">
<Button
variant={mapPickMode === 'polygon' ? 'default' : 'outline'}
onClick={() => setMapPickMode(mapPickMode === 'polygon' ? null : 'polygon')}
className="flex-1"
>
<MousePointer2 className="w-4 h-4 mr-2" />
{mapPickMode === 'polygon' ? '正在使用地图选点' : '地图选点'}
</Button>
<Button
variant={mapPickMode === null ? 'default' : 'outline'}
onClick={() => setMapPickMode(null)}
className="flex-1"
>
<Edit3 className="w-4 h-4 mr-2" />
手动输入坐标
</Button>
</div>
{mapPickMode === 'polygon' ? (
/* 地图选点模式 */
<MapPointPicker
points={geomPoints}
mode="polygon"
onPointsChange={setGeomPoints}
height="400px"
title="在地图上选择多边形顶点"
/>
) : (
/* 手动输入模式 */
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<h3>多边形坐标点</h3>
<Button size="sm" onClick={handleAddGeomPoint}>
<Plus className="w-4 h-4 mr-2" />
添加点
</Button>
</div>
<div className="space-y-3 max-h-96 overflow-y-auto">
{geomPoints.map((point, index) => (
<div key={index} className="flex items-center gap-2">
<Badge variant="outline" className="w-16">
{index + 1}
</Badge>
<Input
type="number"
step="0.000001"
value={point.lat}
onChange={(e) => handleUpdateGeomPoint(index, 'lat', e.target.value)}
placeholder="纬度"
className="flex-1"
/>
<Input
type="number"
step="0.000001"
value={point.lng}
onChange={(e) => handleUpdateGeomPoint(index, 'lng', e.target.value)}
placeholder="经度"
className="flex-1"
/>
<Button
size="sm"
variant="ghost"
onClick={() => handleRemoveGeomPoint(index)}
disabled={geomPoints.length <= 3}
>
<Trash2 className="w-4 h-4 text-red-500" />
</Button>
</div>
))}
</div>
</Card>
)}
<Button
onClick={handleCalculateArea}
className="w-full bg-green-600 hover:bg-green-700"
>
<Calculator className="w-4 h-4 mr-2" />
计算面积 (ST_Area)
</Button>
{/* 结果显示部分保持不变 */}
{geomResult && geomResult.type === 'area' && (
// ... 原有的结果显示代码
)}
</TabsContent>
```
### 步骤2: 修改周长计算TabsContent
使用相同的模式修改周长计算部分约第1550行
### 步骤3: 修改中心点计算TabsContent
使用相同的模式修改中心点计算部分约第1610行
### 步骤4: 修改包围盒计算TabsContent
使用相同的模式修改包围盒计算部分(最后一个多边形计算)
### 步骤5: 修改距离计算TabsContent
**找到距离计算部分:**
```typescript
{/* 距离计算 */}
<TabsContent value="distance" className="space-y-4">
<Card className="p-6">
<h3 className="mb-4">两点坐标</h3>
<div className="space-y-4">
<div>
<div className="mb-2 font-medium">1坐标</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-sm text-muted-foreground">纬度</label>
<Input ... />
</div>
<div>
<label className="text-sm text-muted-foreground">经度</label>
<Input ... />
</div>
</div>
</div>
{/* 点2坐标 - 类似结构 */}
</div>
</Card>
</TabsContent>
```
**替换为:**
```typescript
{/* 距离计算 */}
<TabsContent value="distance" className="space-y-4">
{/* 点1选择 */}
<Card className="p-6">
<h3 className="mb-4">1坐标</h3>
{/* 切换按钮 */}
<div className="flex gap-2 mb-4">
<Button
variant={mapPickMode === 'distance-p1' ? 'default' : 'outline'}
onClick={() => setMapPickMode(mapPickMode === 'distance-p1' ? null : 'distance-p1')}
className="flex-1"
>
<MousePointer2 className="w-4 h-4 mr-2" />
{mapPickMode === 'distance-p1' ? '正在使用地图选点' : '地图选点'}
</Button>
<Button
variant={mapPickMode === null || mapPickMode === 'distance-p2' ? 'default' : 'outline'}
onClick={() => setMapPickMode(null)}
className="flex-1"
>
<Edit3 className="w-4 h-4 mr-2" />
手动输入坐标
</Button>
</div>
{mapPickMode === 'distance-p1' ? (
<MapPointPicker
points={[distPoint1]}
mode="single"
onPointsChange={(points) => setDistPoint1(points[0] || distPoint1)}
height="300px"
title="在地图上选择点1位置"
/>
) : (
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-sm text-muted-foreground">纬度</label>
<Input
type="number"
step="0.000001"
value={distPoint1.lat}
onChange={(e) => setDistPoint1({ ...distPoint1, lat: parseFloat(e.target.value) || 0 })}
/>
</div>
<div>
<label className="text-sm text-muted-foreground">经度</label>
<Input
type="number"
step="0.000001"
value={distPoint1.lng}
onChange={(e) => setDistPoint1({ ...distPoint1, lng: parseFloat(e.target.value) || 0 })}
/>
</div>
</div>
)}
</Card>
{/* 点2选择 - 类似结构,使用 mapPickMode === 'distance-p2' 和 distPoint2 */}
<Card className="p-6">
<h3 className="mb-4">2坐标</h3>
<div className="flex gap-2 mb-4">
<Button
variant={mapPickMode === 'distance-p2' ? 'default' : 'outline'}
onClick={() => setMapPickMode(mapPickMode === 'distance-p2' ? null : 'distance-p2')}
className="flex-1"
>
<MousePointer2 className="w-4 h-4 mr-2" />
{mapPickMode === 'distance-p2' ? '正在使用地图选点' : '地图选点'}
</Button>
<Button
variant={mapPickMode === null || mapPickMode === 'distance-p1' ? 'default' : 'outline'}
onClick={() => setMapPickMode(null)}
className="flex-1"
>
<Edit3 className="w-4 h-4 mr-2" />
手动输入坐标
</Button>
</div>
{mapPickMode === 'distance-p2' ? (
<MapPointPicker
points={[distPoint2]}
mode="single"
onPointsChange={(points) => setDistPoint2(points[0] || distPoint2)}
height="300px"
title="在地图上选择点2位置"
/>
) : (
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-sm text-muted-foreground">纬度</label>
<Input
type="number"
step="0.000001"
value={distPoint2.lat}
onChange={(e) => setDistPoint2({ ...distPoint2, lat: parseFloat(e.target.value) || 0 })}
/>
</div>
<div>
<label className="text-sm text-muted-foreground">经度</label>
<Input
type="number"
step="0.000001"
value={distPoint2.lng}
onChange={(e) => setDistPoint2({ ...distPoint2, lng: parseFloat(e.target.value) || 0 })}
/>
</div>
</div>
)}
</Card>
<Button
onClick={handleCalculateDistance}
className="w-full bg-orange-600 hover:bg-orange-700"
>
<MapIcon className="w-4 h-4 mr-2" />
计算距离 (ST_Distance)
</Button>
{/* 结果显示部分保持不变 */}
</TabsContent>
```
---
## 📝 修改清单
需要修改的5个TabsContent
- [ ] **面积计算** (area) - 多边形模式
- [ ] **周长计算** (perimeter) - 多边形模式
- [ ] **中心点计算** (centroid) - 多边形模式
- [ ] **包围盒计算** (bbox) - 多边形模式
- [ ] **距离计算** (distance) - 双点模式
---
## 🎯 关键要点
### 1. 模式切换按钮
```typescript
<Button
variant={mapPickMode === 'polygon' ? 'default' : 'outline'}
onClick={() => setMapPickMode(mapPickMode === 'polygon' ? null : 'polygon')}
>
地图选点
</Button>
```
### 2. 条件渲染
```typescript
{mapPickMode === 'polygon' ? (
<MapPointPicker ... />
) : (
<Card>手动输入</Card>
)}
```
### 3. MapPointPicker属性
```typescript
<MapPointPicker
points={geomPoints} // 坐标数组
mode="polygon" // 或 "single"
onPointsChange={setGeomPoints} // 更新回调
height="400px" // 地图高度
title="选择坐标点" // 标题
/>
```
---
## ✅ 测试检查
完成集成后,测试以下功能:
### 面积计算
- [ ] 切换到地图选点模式
- [ ] 在地图上点击添加4个点
- [ ] 验证多边形自动绘制
- [ ] 点击标记删除点
- [ ] 切换回手动输入模式
- [ ] 验证坐标保留
- [ ] 计算面积成功
### 周长计算
- [ ] 使用相同测试流程
### 中心点计算
- [ ] 使用相同测试流程
### 距离计算
- [ ] 点1使用地图选择
- [ ] 点2使用地图选择
- [ ] 验证两点标记显示
- [ ] 计算距离成功
### 包围盒计算
- [ ] 使用相同测试流程
---
## 🐛 可能的问题
### 问题1: 地图未显示
**原因:** 高德地图SDK未加载
**解决:** 检查mapLoader.ts是否正常工作
### 问题2: 点击地图无反应
**原因:** 事件监听未生效
**解决:** 检查MapPointPicker组件的useEffect
### 问题3: 标记位置不准
**原因:** 经纬度顺序错误
**解决:** 高德地图使用[lng, lat]顺序
### 问题4: 多边形未绘制
**原因:** 点数不足3个
**解决:** 至少添加3个点才能形成多边形
---
## 🎉 完成后的效果
用户可以:
1. **灵活选择输入方式**
- 地图直观选点
- 手动精确输入
- 随时切换
2. **实时可视化反馈**
- 标记即时显示
- 多边形自动绘制
- 坐标实时更新
3. **便捷操作**
- 点击添加
- 点击删除
- 一键清除
4. **保留原有功能**
- 所有计算功能不变
- 精度保持不变
- 结果显示不变
---
## 📚 相关文件
- `/components/field/MapPointPicker.tsx` - 地图选点组件
- `/components/field/FieldSpatialQuery.tsx` - 空间数据管理主组件
- `/lib/spatialDataService.ts` - 空间计算服务
- `/lib/mapLoader.ts` - 地图SDK加载器
---
**创建日期:** 2025-10-18
**版本:** v4.0
**状态:** 组件已创建,需手动集成
**维护团队:** 智慧农业研发中心