--- id: floor-map name: 楼层地图与标记点管理 area: rn-app status: done --- ## 概述 以真实 SVG 楼层平面图为底图,支持捏合缩放、单指平移、长按添加标记点、拖拽移动标记点。支持 1F/2F/3F/楼顶四个楼层。SVG 内嵌图片通过构建脚本提取,分层以原生 Image 渲染,保证清晰度和旋转精度。 ## 任务卡 ### ✅ SVG 地图渲染(多层架构) - status: done - complexity: M - files: apps/property-survey/src/features/map/FloorMap.tsx, apps/property-survey/src/features/map/floorMaps.generated.ts, apps/property-survey/scripts/generate-maps.js - acceptance: 三层渲染:① 底图 JPEG(原生 Image,from imageUri)② SVG 矢量路径(SvgXml,overdraw 预渲染提升缩放清晰度,动态计算安全 overdraw 倍数避免 Android Canvas OOM)③ 用户嵌入图片(原生 Image,从 SVG 提取并按 SVG matrix/rotate 变换还原坐标和旋转角) ### ✅ 捏合缩放 + 单指平移手势 - status: done - complexity: M - files: apps/property-survey/src/features/map/FloorMap.tsx - acceptance: 使用 react-native-gesture-handler Gesture API;Pinch 手势控制缩放(MIN_SCALE=1, MAX_SCALE=5);Pan 手势(minPointers:1, maxPointers:1)控制平移;双指和单指通过 Gesture.Race 互斥;使用 react-native-reanimated SharedValue 实现原生线程动画 ### ✅ 长按添加标记点 - status: done - complexity: M - files: apps/property-survey/src/features/map/FloorMap.tsx - acceptance: LongPress 手势(minDuration:500ms);触点坐标反算缩放/平移变换得到地图相对坐标(0~1);调用 insertMarker 写入数据库;与双指手势通过 Gesture.Race 互斥 ### ✅ 标记点图标组件 - status: done - complexity: M - files: apps/property-survey/src/features/map/MapMarker.tsx - depends: floor-map - acceptance: 按相对坐标绝对定位;已有物品的标记点显示彩色图钉 + 数量角标(颜色来自 marker.color);选中状态有缩放高亮;支持拖拽移动(Pan 手势,拖拽结束调用 onDragEnd) ### ✅ 楼层标记点数据加载与标记列表 - status: done - complexity: S - files: apps/property-survey/src/features/map/MarkerList.tsx - depends: floor-map - acceptance: 楼层切换或 refreshKey 变化时重新查询;左侧边栏展示当前楼层所有标记点卡片(标签、物品列表含确认状态图标);删除标记点时弹确认对话框含级联删除提示 ### ✅ SVG 地图生成脚本 - status: done - complexity: M - files: scripts/generate-maps.js, apps/property-survey/img/(1101-1104.svg) - acceptance: 读取 4 个 SVG 文件;提取背景 JPEG(imageUri)+ 用户嵌入图片(embeddedImages,含 cx/cy/width/height/angleDeg/opacity);从 svgXml 中去除所有 image 元素及 Inkscape 私有命名空间;生成 floorMaps.generated.ts TypeScript 文件 ## 决策记录 - **SVG 三层渲染架构**:`SvgXml` 无法可靠渲染带 `rotate`/`matrix` transform 的 `` 元素(坐标偏移、旋转不准)。改为构建时提取:① 背景 JPEG 用原生 `Image` 渲染(性能好);② 矢量路径用 `SvgXml` 渲染(保留矢量清晰度);③ 用户嵌入图片用原生 `Image` 渲染(精确还原旋转和位置)。 - **SVG overdraw 预渲染**:`SvgXml` 在 RN 中被光栅化为固定分辨率纹理,缩放时模糊。通过以 N 倍宽高渲染再 `scale(1/N)` 缩回,在 Android Canvas 安全内存上限内最大化预渲染分辨率。N 值由 `calcSafeOverdraw()` 根据 `PixelRatio.get()` 动态计算,避免高 DPI 设备 OOM。 - **嵌入图片坐标还原**:SVG `matrix(a,b,c,d,e,f)` 变换的图片中心点计算公式:`cx = a*(ox+W/2) + c*(oy+H/2) + e`,旋转角 `angleDeg = atan2(b,a) * 180/π`。RN 中 Image 的 `transform: rotate` 以元素自身中心为锚点,与 SVG transform 语义一致,无需额外偏移修正。