@fleet-frontend/mower-maps
Version:
a mower maps in google maps
1,202 lines (1,191 loc) • 492 kB
JavaScript
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
/**
* 常量和枚举类型定义
*/
/**
* 机器人状态枚举
*/
var RobotStatus;
(function (RobotStatus) {
RobotStatus[RobotStatus["PARKED"] = 1] = "PARKED";
RobotStatus[RobotStatus["CHARGING"] = 2] = "CHARGING";
RobotStatus[RobotStatus["STANDBY"] = 3] = "STANDBY";
RobotStatus[RobotStatus["MOWING"] = 4] = "MOWING";
RobotStatus[RobotStatus["WORKING"] = 5] = "WORKING";
RobotStatus[RobotStatus["MAPPING"] = 6] = "MAPPING";
RobotStatus[RobotStatus["ERROR"] = 7] = "ERROR";
RobotStatus[RobotStatus["UPGRADING"] = 8] = "UPGRADING";
RobotStatus[RobotStatus["DISCONNECTED"] = 9] = "DISCONNECTED";
RobotStatus[RobotStatus["UNKNOWN"] = -1] = "UNKNOWN";
RobotStatus[RobotStatus["TASK_DELAY"] = 10] = "TASK_DELAY";
})(RobotStatus || (RobotStatus = {}));
/**
* RTK状态枚举
*/
var RTK_STATE;
(function (RTK_STATE) {
RTK_STATE[RTK_STATE["LOW_RTK"] = 1] = "LOW_RTK";
RTK_STATE[RTK_STATE["MIDDLE_RTK"] = 2] = "MIDDLE_RTK";
RTK_STATE[RTK_STATE["HIGH_RTK"] = 3] = "HIGH_RTK";
RTK_STATE[RTK_STATE["NO_POSTURE"] = 10] = "NO_POSTURE";
RTK_STATE[RTK_STATE["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
RTK_STATE[RTK_STATE["OFF_LINE"] = 19] = "OFF_LINE";
})(RTK_STATE || (RTK_STATE = {}));
/**
* 实时数据类型枚举
*/
var REAL_TIME_DATA_TYPE;
(function (REAL_TIME_DATA_TYPE) {
REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["LOCATION"] = 1] = "LOCATION";
REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["PROCESS"] = 2] = "PROCESS";
})(REAL_TIME_DATA_TYPE || (REAL_TIME_DATA_TYPE = {}));
/**
* 地图渲染相关常量配置
*/
/**
* 缩放因子 - 将米转换为像素
* 与Python代码中的SVG比例一致
*/
const SCALE_FACTOR = 50; // 50像素/米
/**
* 默认线宽设置
*/
const DEFAULT_LINE_WIDTHS = {
OBSTACLE: 2,
CHARGING_PILE: 2,
CHANNEL: 2,
PATH: 20,
VISION_OFF_AREA: 2,
TIME_LIMIT_OBSTACLE: 1,
};
/**
* 默认透明度设置
*/
const DEFAULT_OPACITIES = {
FULL: 1.0,
HIGH: 0.7,
MEDIUM: 0.6,
DOODLE: 0.8};
/**
* 默认半径设置
*/
const DEFAULT_RADII = {
CHARGING_PILE: 12};
/**
* 图层等级
*/
const LAYER_LEVELS = {
BOUNDARY: 2,
BOUNDARY_BORDER: 4};
/**
* 图层默认id
*/
const LAYER_DEFAULT_TYPE = {
CHANNEL: 'channel',
BOUNDARY: 'boundary',
PATH: 'path',
BOUNDARY_BORDER: 'boundary_border',
OBSTACLE: 'obstacle',
CHARGING_PILE: 'charging_pile',
POINT: 'point',
SVG: 'svg',
VISION_OFF_AREA: 'vision_off_area',
ANTENNA: 'antenna',
};
const ISOLATED_BOUNDARY_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.6">
<rect width="24" height="24" rx="12" fill="#1E1E1F" fill-opacity="0.5"/>
<path d="M8.20573 12.4961C6.739 11.1707 4.85775 11.2284 3.60938 11.1707C6.17744 13.0156 6.05584 15.7887 5.67404 16.9446H14.327C13.837 13.1673 15.5321 10.1289 16.4408 9.08187C15.0342 9.46118 13.5794 10.8303 13.0278 11.4674C12.8102 8.94572 13.992 5.97489 14.61 4.80469C12.5235 5.92214 11.0501 8.26056 10.4938 9.56261C9.22803 7.5947 6.83894 7.17806 5.883 6.9432C8.20573 10.2373 8.00039 11.1707 8.20573 12.4961Z" fill="white"/>
<mask id="path-3-outside-1_9822_43516" maskUnits="userSpaceOnUse" x="13.2344" y="7.20545" width="12.0208" height="12.0208" fill="black">
<rect fill="white" x="13.2344" y="7.20545" width="12.0208" height="12.0208"/>
<path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z"/>
</mask>
<path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z" fill="white"/>
<path d="M20.6852 11.1208L21.7458 10.0601L21.7459 10.0601L20.6852 11.1208ZM17.8568 11.1208L16.7961 10.0601L16.7962 10.0601L17.8568 11.1208ZM17.2733 11.7043L16.2126 10.6437L16.2126 10.6436L17.2733 11.7043ZM16.8631 13.9319L17.9238 14.9926L16.381 16.5354L15.493 14.5425L16.8631 13.9319ZM17.5916 13.2034L19.0855 13.0678L19.149 13.7674L18.6523 14.2641L17.5916 13.2034ZM20.4056 12.1421L21.4662 11.0814L21.4665 11.0817L20.4056 12.1421ZM18.9699 14.5348L17.9093 13.4741L18.3741 13.0093L19.0309 13.036L18.9699 14.5348ZM18.2235 15.2812L17.6298 16.6587L15.5997 15.7837L17.1628 14.2206L18.2235 15.2812ZM20.429 14.86L21.4897 15.9207L21.4897 15.9207L20.429 14.86ZM21.0125 14.2765L22.0732 15.3371L22.0732 15.3372L21.0125 14.2765ZM20.6852 11.1208L19.6246 12.1815C19.4294 11.9862 19.1127 11.9863 18.9174 12.1815L17.8568 11.1208L16.7962 10.0601C18.163 8.69337 20.379 8.6934 21.7458 10.0601L20.6852 11.1208ZM17.8568 11.1208L18.9175 12.1814L18.334 12.7649L17.2733 11.7043L16.2126 10.6436L16.7961 10.0601L17.8568 11.1208ZM17.2733 11.7043L18.334 12.7649C18.1877 12.9112 18.1488 13.1318 18.2333 13.3214L16.8631 13.9319L15.493 14.5425C14.9228 13.2629 15.1537 11.7026 16.2126 10.6437L17.2733 11.7043ZM16.8631 13.9319L15.8025 12.8713L16.531 12.1428L17.5916 13.2034L18.6523 14.2641L17.9238 14.9926L16.8631 13.9319ZM17.5916 13.2034L16.0978 13.339C16.0334 12.6302 16.2727 11.8914 16.8196 11.3445L17.8803 12.4052L18.9409 13.4658C19.051 13.3558 19.098 13.206 19.0855 13.0678L17.5916 13.2034ZM17.8803 12.4052L16.8196 11.3445L17.5067 10.6574L18.5674 11.7181L19.628 12.7788L18.9409 13.4658L17.8803 12.4052ZM18.5674 11.7181L17.5067 10.6574C18.483 9.68112 20.0659 9.68112 21.0422 10.6574L19.9816 11.7181L18.9209 12.7788C19.1162 12.974 19.4328 12.974 19.628 12.7788L18.5674 11.7181ZM19.9816 11.7181L21.0422 10.6574L21.4662 11.0814L20.4056 12.1421L19.3449 13.2027L18.9209 12.7788L19.9816 11.7181ZM20.4056 12.1421L21.4665 11.0817C22.4419 12.0577 22.4428 13.6404 21.4662 14.617L20.4056 13.5563L19.3449 12.4956C19.1493 12.6913 19.1498 13.0076 19.3446 13.2024L20.4056 12.1421ZM20.4056 13.5563L21.4662 14.617L20.7791 15.304L19.7185 14.2434L18.6578 13.1827L19.3449 12.4956L20.4056 13.5563ZM19.7185 14.2434L20.7791 15.304C20.2628 15.8204 19.5762 16.0607 18.909 16.0335L18.9699 14.5348L19.0309 13.036C18.9025 13.0308 18.7629 13.0777 18.6578 13.1827L19.7185 14.2434ZM18.9699 14.5348L20.0306 15.5954L19.2841 16.3419L18.2235 15.2812L17.1628 14.2206L17.9093 13.4741L18.9699 14.5348ZM18.2235 15.2812L18.8172 13.9037C19.0042 13.9843 19.2223 13.9455 19.3684 13.7993L20.429 14.86L21.4897 15.9207C20.4427 16.9676 18.9034 17.2077 17.6298 16.6587L18.2235 15.2812ZM20.429 14.86L19.3684 13.7994L19.9519 13.2159L21.0125 14.2765L22.0732 15.3372L21.4897 15.9207L20.429 14.86ZM21.0125 14.2765L19.9518 13.2159C20.1471 13.0206 20.1471 12.704 19.9519 12.5088L21.0125 11.4481L22.0732 10.3874C23.4401 11.7543 23.4399 13.9703 22.0732 15.3371L21.0125 14.2765ZM21.0125 11.4481L19.9519 12.5088L19.6246 12.1814L20.6852 11.1208L21.7459 10.0601L22.0732 10.3874L21.0125 11.4481Z" fill="#8E8E8F" mask="url(#path-3-outside-1_9822_43516)"/>
<mask id="path-5-outside-2_9822_43516" maskUnits="userSpaceOnUse" x="9.22265" y="10.388" width="12.7279" height="12.7279" fill="black">
<rect fill="white" x="9.22265" y="10.388" width="12.7279" height="12.7279"/>
<path d="M16.1708 14.5077C15.4353 14.1778 14.5422 14.3133 13.9383 14.9172L13.3548 15.5007C12.574 16.2818 12.5738 17.5481 13.3548 18.3291L13.6821 18.6564C14.4631 19.4374 15.7295 19.4373 16.5105 18.6564L17.094 18.0729C17.6895 17.4775 17.8299 16.6003 17.5173 15.8708L16.7743 16.6138C16.7828 16.8803 16.6863 17.1494 16.4829 17.3527L15.7952 18.0405C15.4046 18.4309 14.7714 18.431 14.3809 18.0405L13.957 17.6165C13.5667 17.226 13.5666 16.5927 13.957 16.2023L14.6447 15.5145C14.8654 15.2939 15.1639 15.1979 15.452 15.2266L16.1708 14.5077Z"/>
</mask>
<path d="M16.1708 14.5077C15.4353 14.1778 14.5422 14.3133 13.9383 14.9172L13.3548 15.5007C12.574 16.2818 12.5738 17.5481 13.3548 18.3291L13.6821 18.6564C14.4631 19.4374 15.7295 19.4373 16.5105 18.6564L17.094 18.0729C17.6895 17.4775 17.8299 16.6003 17.5173 15.8708L16.7743 16.6138C16.7828 16.8803 16.6863 17.1494 16.4829 17.3527L15.7952 18.0405C15.4046 18.4309 14.7714 18.431 14.3809 18.0405L13.957 17.6165C13.5667 17.226 13.5666 16.5927 13.957 16.2023L14.6447 15.5145C14.8654 15.2939 15.1639 15.1979 15.452 15.2266L16.1708 14.5077Z" fill="white"/>
<path d="M16.1708 14.5077L16.7847 13.1391L18.7701 14.0297L17.2315 15.5684L16.1708 14.5077ZM13.3548 15.5007L12.294 14.4402L12.2941 14.44L13.3548 15.5007ZM16.5105 18.6564L17.5712 19.7171L17.5711 19.7172L16.5105 18.6564ZM17.5173 15.8708L16.4567 14.8102L18.0236 13.2433L18.8962 15.2802L17.5173 15.8708ZM16.7743 16.6138L15.2751 16.6613L15.2545 16.0123L15.7137 15.5532L16.7743 16.6138ZM15.7952 18.0405L16.8558 19.1011L16.8556 19.1013L15.7952 18.0405ZM13.957 17.6165L12.8963 18.6772L12.8959 18.6767L13.957 17.6165ZM13.957 16.2023L12.8961 15.1418L12.8963 15.1416L13.957 16.2023ZM15.452 15.2266L16.5126 16.2872L16.0102 16.7896L15.3032 16.7192L15.452 15.2266ZM16.1708 14.5077L15.5569 15.8763C15.3688 15.792 15.1469 15.8299 14.999 15.9779L13.9383 14.9172L12.8776 13.8565C13.9375 12.7967 15.5018 12.5636 16.7847 13.1391L16.1708 14.5077ZM13.9383 14.9172L14.999 15.9779L14.4155 16.5614L13.3548 15.5007L12.2941 14.44L12.8776 13.8565L13.9383 14.9172ZM13.3548 15.5007L14.4156 16.5612C14.2202 16.7567 14.2204 17.0734 14.4155 17.2685L13.3548 18.3291L12.2941 19.3898C10.9272 18.0229 10.9277 15.8069 12.294 14.4402L13.3548 15.5007ZM13.3548 18.3291L14.4155 17.2685L14.7428 17.5958L13.6821 18.6564L12.6215 19.7171L12.2941 19.3898L13.3548 18.3291ZM13.6821 18.6564L14.7428 17.5958C14.9378 17.7908 15.2546 17.791 15.45 17.5956L16.5105 18.6564L17.5711 19.7172C16.2044 21.0836 13.9884 21.084 12.6215 19.7171L13.6821 18.6564ZM16.5105 18.6564L15.4499 17.5958L16.0334 17.0123L17.094 18.0729L18.1547 19.1336L17.5712 19.7171L16.5105 18.6564ZM17.094 18.0729L16.0334 17.0123C16.1795 16.8662 16.2185 16.6481 16.1385 16.4615L17.5173 15.8708L18.8962 15.2802C19.4413 16.5526 19.1996 18.0887 18.1547 19.1336L17.094 18.0729ZM17.5173 15.8708L18.578 16.9315L17.835 17.6745L16.7743 16.6138L15.7137 15.5532L16.4567 14.8102L17.5173 15.8708ZM16.7743 16.6138L18.2736 16.5664C18.2945 17.226 18.0544 17.9026 17.5436 18.4134L16.4829 17.3527L15.4223 16.2921C15.3182 16.3961 15.2711 16.5346 15.2751 16.6613L16.7743 16.6138ZM16.4829 17.3527L17.5436 18.4134L16.8558 19.1011L15.7952 18.0405L14.7345 16.9798L15.4223 16.2921L16.4829 17.3527ZM15.7952 18.0405L16.8556 19.1013C15.8795 20.0771 14.2967 20.0776 13.3203 19.1011L14.3809 18.0405L15.4416 16.9798C15.2461 16.7844 14.9297 16.7847 14.7347 16.9797L15.7952 18.0405ZM14.3809 18.0405L13.3203 19.1011L12.8963 18.6772L13.957 17.6165L15.0176 16.5558L15.4416 16.9798L14.3809 18.0405ZM13.957 17.6165L12.8959 18.6767C11.9207 17.7008 11.92 16.1182 12.8961 15.1418L13.957 16.2023L15.0178 17.2628C15.2133 17.0672 15.2128 16.7512 15.018 16.5563L13.957 17.6165ZM13.957 16.2023L12.8963 15.1416L13.5841 14.4539L14.6447 15.5145L15.7054 16.5752L15.0176 17.2629L13.957 16.2023ZM14.6447 15.5145L13.5841 14.4539C14.1364 13.9015 14.8848 13.6626 15.6007 13.734L15.452 15.2266L15.3032 16.7192C15.443 16.7331 15.5943 16.6862 15.7054 16.5752L14.6447 15.5145ZM15.452 15.2266L14.3913 14.1659L15.1101 13.4471L16.1708 14.5077L17.2315 15.5684L16.5126 16.2872L15.452 15.2266Z" fill="#8E8E8F" mask="url(#path-5-outside-2_9822_43516)"/>
<path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z" fill="white"/>
<rect width="2.59942" height="2.97264" rx="1" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 21.1133 12.8486)" fill="#8E8E8F"/>
<rect width="1.91578" height="4.24403" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 19.6992 13.8066)" fill="#8E8E8F"/>
<rect width="2.59942" height="2.97264" rx="1" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 17.3125 16.6455)" fill="#8E8E8F"/>
</g>
</svg>`;
/**
* 遍历割草任务,下述四个字段可以在路径中唯一确定遍历的位置(当前区域、当前块、当前行、在当前行上的路程)
*/
const ACTION_BLOCK_COVER = 5;
/**
* 遍历割草块转移任务(在同一区域中的一个块转移到下一个块),下述四个字段可以在路径中唯一确定转移的位置(当前区域、前置块、当前转移路径线序号、在当前线上的路程)
*/
const ACTION_BLOCK_TRANSFER = 6;
/**
* 边界割草任务(割草任务内部的巡边任务)
*/
const ACTION_BOUNDARY_TASK = 8;
const SVG_MAP_VIEW_ID = 'fleet-maps-svg-map-view';
/**
* SVG基础MapView
* 使用真正的矢量SVG渲染替代Canvas位图渲染
*/
class SvgMapView {
constructor(containerElement, width = 800, height = 600) {
// 图层管理
this.layers = [];
// 简单变换参数
this.scale = 1;
this.lineScale = 1; // 线条缩放系数
// 状态标志
this.destroyed = false;
// 渲染系统 - 移除节流以确保用户操作的实时响应
// 拖动功能
this.isDragging = false;
this.lastMouseX = 0;
this.lastMouseY = 0;
this.container = containerElement;
// 创建SVG元素
this.svg = this.createSVGElement();
this.setupSVG(width, height);
// 创建图层组
this.layersGroup = this.createSVGGroup('layers-group');
this.svg.appendChild(this.layersGroup);
// 添加到容器
this.container.appendChild(this.svg);
// 初始化viewBox
this.viewBox = { x: 0, y: 0, width, height };
this.updateViewBox();
// 设置拖拽功能
this.setupDragHandlers();
}
/**
* 创建SVG元素
*/
createSVGElement() {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
// SVG样式设置
svg.style.display = 'block';
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.background = 'transparent';
return svg;
}
/**
* 设置SVG基本属性
*/
setupSVG(_width, _height) {
// 设置高质量渲染属性
this.svg.setAttribute('shape-rendering', 'geometricPrecision');
this.svg.setAttribute('text-rendering', 'geometricPrecision');
this.svg.setAttribute('image-rendering', 'optimizeQuality');
this.svg.setAttribute('id', SVG_MAP_VIEW_ID);
}
/**
* 创建SVG组元素
*/
createSVGGroup(id) {
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.setAttribute('id', id);
return group;
}
/**
* 更新viewBox
*/
updateViewBox() {
const viewBoxStr = `${this.viewBox.x} ${this.viewBox.y} ${this.viewBox.width} ${this.viewBox.height}`;
this.svg.setAttribute('viewBox', viewBoxStr);
}
// ==================== 图层管理 ====================
addLayers(layers) {
if (this.destroyed)
return;
this.layers.push(...layers);
this.layers.sort((a, b) => a.getLevel() - b.getLevel());
}
getLayer(type) {
return this.layers.find((layer) => layer.getType() === type) || null;
}
/**
* 添加图层
*/
addLayer(layer) {
if (this.destroyed)
return;
this.layers.push(layer);
this.layers.sort((a, b) => a.getLevel() - b.getLevel());
}
/**
* 移除图层
*/
removeLayer(layer) {
const index = this.layers.indexOf(layer);
if (index !== -1) {
this.layers.splice(index, 1);
this.refresh();
}
}
/**
* 根据类型移除图层
* @param type 图层类型
*/
removeLayerByType(type) {
const layer = this.layers.find((layer) => layer.getType() === type);
if (layer) {
this.clearLayersGroup(layer);
this.layers = this.layers.filter((cLayer) => cLayer.getType() !== type);
}
}
// ==================== 变换系统 ====================
/**
* 设置自适应视图变换 - 让SVG刚好包裹住图形
*/
fitToView(bounds) {
const padding = 20; // 添加一些边距以避免内容贴边
const boundWidth = bounds.maxX - bounds.minX;
const boundHeight = bounds.maxY - bounds.minY;
// 防止宽高为0的情况
if (boundWidth <= 0 || boundHeight <= 0) {
console.warn('Invalid bounds for fitToView:', bounds);
return;
}
// 获取容器的实际尺寸进行验证
const containerRect = this.container.getBoundingClientRect();
const containerWidth = containerRect.width;
const containerHeight = containerRect.height;
if (containerWidth <= 0 || containerHeight <= 0) {
console.warn('Invalid container size:', containerWidth, containerHeight);
return;
}
// 计算内容和容器的宽高比
const contentAspectRatio = boundWidth / boundHeight;
const containerAspectRatio = containerWidth / containerHeight;
// 设置viewBox以包含所有内容
this.viewBox = {
x: bounds.minX - padding,
y: bounds.minY - padding,
width: boundWidth + padding * 2,
height: boundHeight + padding * 2,
};
// 根据宽高比选择合适的preserveAspectRatio设置
if (Math.abs(contentAspectRatio - containerAspectRatio) < 0.01) {
// 宽高比接近,使用slice填满容器
this.svg.setAttribute('preserveAspectRatio', 'xMidYMid slice');
}
else {
// 宽高比差异较大,使用meet确保内容完全可见
this.svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
}
this.updateViewBox();
}
/**
* 获取当前缩放级别
*/
getZoom() {
return this.scale;
}
/**
* 获取当前线条缩放系数
*/
getLineScale() {
return this.lineScale;
}
/**
* 绘制特定的图层
*/
renderLayer(type) {
const layer = this.layers.find((layer) => layer.getType() === type);
// 清空图层组
this.clearLayersGroup(layer);
this.onDrawLayers(type);
}
// ==================== 渲染系统 ====================
/**
* 主渲染方法
*/
render() {
// 清空图层组
this.clearLayersGroup();
// 绘制所有图层
this.onDrawLayers();
}
/**
* 获取图层id
*/
getLayerId(layer) {
return `layer-${layer.getType()}-${layer.getLevel()}`;
}
/**
* 清空图层组
*/
clearLayersGroup(layer) {
if (layer) {
const layerId = this.getLayerId(layer);
const layerGroup = this.layersGroup.querySelector(`#${layerId}`);
if (layerGroup) {
this.layersGroup.removeChild(layerGroup);
}
}
else {
while (this.layersGroup.firstChild) {
this.layersGroup.removeChild(this.layersGroup.firstChild);
}
}
}
/**
* 获取图层的下一个兄弟元素
* 根据图层的level来获取
*/
getNextSibling(layer) {
const nextLayer = this.layers.find((cLayer) => cLayer.getLevel() > layer.getLevel());
const id = `layer-${nextLayer?.getType()}-${nextLayer?.getLevel()}`;
const nextSibling = this.layersGroup.querySelector(`#${id}`);
return nextSibling;
}
/**
* 绘制图层,不传参数则默认绘制所有图层
*/
onDrawLayers(type) {
if (type) {
const layer = this.layers.find((layer) => layer.getType() === type);
if (layer) {
const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
layer.drawSVG(layerGroup, this.scale, this.lineScale);
const nextSibling = this.getNextSibling(layer);
if (nextSibling) {
this.layersGroup.insertBefore(layerGroup, nextSibling);
}
else {
this.layersGroup.appendChild(layerGroup);
}
}
}
else {
for (const layer of this.layers) {
if (layer.isVisible()) {
const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
layer.drawSVG(layerGroup, this.scale, this.lineScale);
this.layersGroup.appendChild(layerGroup);
}
}
}
// if (type) {
// const layer = this.layers.find((layer) => layer.getType() === type);
// const layerId = this.getLayerId(layer);
// // 记录原始位置信息
// const layerGroup = this.layersGroup.querySelector(`#${layerId}`);
// let nextSibling: Node | null = null;
// if (layerGroup) {
// // 记录被删除元素的下一个兄弟元素,用于确定插入位置
// nextSibling = layerGroup.nextSibling;
// // 删除旧的图层组
// this.layersGroup.removeChild(layerGroup);
// // 从layerId解析出图层类型和层级
// // layerId格式: layer-${type}-${level}
// const layerIdParts = layerId.split('-');
// if (layerIdParts.length >= 3) {
// const layerType = layerIdParts.slice(1, -1).join('-'); // 处理类型名中可能包含连字符的情况
// const layerLevel = parseInt(layerIdParts[layerIdParts.length - 1]);
// // 查找对应的图层对象
// const targetLayer = this.layers.find(layer =>
// layer.getType() === layerType && layer.getLevel() === layerLevel
// );
// if (targetLayer && targetLayer.isVisible()) {
// // 创建新的图层组
// const newLayerGroup = this.createSVGGroup(layerId);
// // 重新绘制图层
// targetLayer.drawSVG(newLayerGroup, this.scale, this.lineScale);
// // 在原始位置插入新元素
// if (nextSibling) {
// this.layersGroup.insertBefore(newLayerGroup, nextSibling);
// } else {
// // 如果没有下一个兄弟元素,说明原来是最后一个,直接appendChild
// this.layersGroup.appendChild(newLayerGroup);
// }
// }
// }
// }
// } else {
// // 重绘所有图层
// for (const layer of this.layers) {
// if (layer.isVisible()) {
// const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
// layer.drawSVG(layerGroup, this.scale, this.lineScale);
// this.layersGroup.appendChild(layerGroup);
// }
// }
// }
}
/**
* 刷新渲染
*/
refresh() {
if (this.destroyed)
return;
this.render();
}
// ==================== 拖拽功能 ====================
/**
* 设置拖拽事件处理器
*/
setupDragHandlers() {
// 鼠标事件
this.svg.addEventListener('mousedown', this.handleMouseDown.bind(this));
this.svg.addEventListener('mousemove', this.handleMouseMove.bind(this));
this.svg.addEventListener('mouseup', this.handleMouseUp.bind(this));
this.svg.addEventListener('mouseleave', this.handleMouseLeave.bind(this));
// 触摸事件
this.svg.addEventListener('touchstart', this.handleTouchStart.bind(this));
this.svg.addEventListener('touchmove', this.handleTouchMove.bind(this));
this.svg.addEventListener('touchend', this.handleTouchEnd.bind(this));
// 防止默认的拖拽行为
this.svg.addEventListener('dragstart', (e) => e.preventDefault());
}
/**
* 鼠标按下事件
*/
handleMouseDown(event) {
this.isDragging = true;
this.lastMouseX = event.clientX;
this.lastMouseY = event.clientY;
this.svg.style.cursor = 'grabbing';
event.preventDefault();
}
/**
* 鼠标移动事件
*/
handleMouseMove(event) {
if (!this.isDragging) {
// 设置悬停时的光标样式
this.svg.style.cursor = this.scale > 1 ? 'grab' : 'default';
return;
}
const deltaX = event.clientX - this.lastMouseX;
const deltaY = event.clientY - this.lastMouseY;
this.panByPixels(deltaX, deltaY);
this.lastMouseX = event.clientX;
this.lastMouseY = event.clientY;
event.preventDefault();
}
/**
* 鼠标释放事件
*/
handleMouseUp(_event) {
this.isDragging = false;
this.svg.style.cursor = this.scale > 1 ? 'grab' : 'default';
}
/**
* 鼠标离开事件
*/
handleMouseLeave(_event) {
this.isDragging = false;
this.svg.style.cursor = 'default';
}
/**
* 触摸开始事件
*/
handleTouchStart(event) {
if (event.touches.length === 1) {
const touch = event.touches[0];
this.isDragging = true;
this.lastMouseX = touch.clientX;
this.lastMouseY = touch.clientY;
event.preventDefault();
}
}
/**
* 触摸移动事件
*/
handleTouchMove(event) {
if (this.isDragging && event.touches.length === 1) {
const touch = event.touches[0];
const deltaX = touch.clientX - this.lastMouseX;
const deltaY = touch.clientY - this.lastMouseY;
this.panByPixels(deltaX, deltaY);
this.lastMouseX = touch.clientX;
this.lastMouseY = touch.clientY;
event.preventDefault();
}
}
/**
* 触摸结束事件
*/
handleTouchEnd(_event) {
this.isDragging = false;
}
/**
* 按像素平移视图
*/
panByPixels(deltaX, deltaY) {
// 获取容器尺寸
const containerRect = this.container.getBoundingClientRect();
// 计算viewBox坐标系中的移动量
const scaleX = this.viewBox.width / containerRect.width;
const scaleY = this.viewBox.height / containerRect.height;
// 更新viewBox位置(注意方向相反)
this.viewBox.x -= deltaX * scaleX;
this.viewBox.y -= deltaY * scaleY;
this.updateViewBox();
}
// ==================== 公共API ====================
/**
* 获取layers-group的实际边界
*/
getLayersGroupBounds() {
if (!this.layersGroup || this.destroyed)
return null;
try {
const bbox = this.layersGroup.getBBox();
return {
x: bbox.x,
y: bbox.y,
width: bbox.width,
height: bbox.height,
};
}
catch (error) {
console.warn('Failed to get layers group bounds:', error);
return null;
}
}
/**
* 获取ViewBox信息
*/
getViewBoxInfo() {
return { ...this.viewBox };
}
/**
* 获取容器的实际尺寸
*/
getContainerSize() {
const rect = this.container.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
};
}
/**
* 诊断SVG尺寸信息
*/
diagnosticSizeInfo() { }
/**
* 获取SVG元素
*/
getSVG() {
return this.svg;
}
/**
* 获取容器元素
*/
getContainer() {
return this.container;
}
/**
* 获取所有图层
*/
getLayers() {
return [...this.layers];
}
/**
* 清空所有图层
*/
clear() {
this.layers = [];
this.clearLayersGroup();
}
/**
* 销毁MapView
*/
destroy() {
if (this.destroyed)
return;
this.destroyed = true;
this.layers = [];
// 清理事件监听器
this.cleanupDragHandlers();
// 从DOM中移除SVG
if (this.svg.parentNode) {
this.svg.parentNode.removeChild(this.svg);
}
}
/**
* 清理拖拽事件处理器
*/
cleanupDragHandlers() {
this.svg.removeEventListener('mousedown', this.handleMouseDown);
this.svg.removeEventListener('mousemove', this.handleMouseMove);
this.svg.removeEventListener('mouseup', this.handleMouseUp);
this.svg.removeEventListener('mouseleave', this.handleMouseLeave);
this.svg.removeEventListener('touchstart', this.handleTouchStart);
this.svg.removeEventListener('touchmove', this.handleTouchMove);
this.svg.removeEventListener('touchend', this.handleTouchEnd);
}
}
/**
* 基础图层类
* 提供图层的基本属性和元素管理功能
*/
class BaseLayer {
constructor() {
this.elements = [];
this.level = 100; // 中等层级
this.visible = true;
this.needsRedraw = true;
this.mapView = null;
this.type = '';
this.level = 0; // 中等层级
}
// ==================== 基础属性方法 ====================
/**
* 设置图层层级
*/
setLevel(level) {
this.level = level;
}
/**
* 获取图层层级
*/
getLevel() {
return this.level;
}
getType() {
return this.type;
}
/**
* 设置图层可见性
*/
setVisible(visible) {
this.visible = visible;
this.needsRedraw = true;
if (this.mapView) {
this.mapView.refresh();
}
}
/**
* 获取图层可见性
*/
isVisible() {
return this.visible;
}
// ==================== 元素管理 ====================
/**
* 添加绘制元素
*/
addElement(element) {
this.elements.push(element);
this.needsRedraw = true;
if (this.mapView) {
this.mapView.refresh();
}
}
/**
* 添加多个绘制元素
*/
addElements(elements) {
this.elements.push(...elements);
this.needsRedraw = true;
if (this.mapView) {
this.mapView.refresh();
}
}
/**
* 移除绘制元素
*/
removeElement(element) {
const index = this.elements.indexOf(element);
if (index !== -1) {
this.elements.splice(index, 1);
this.needsRedraw = true;
if (this.mapView) {
this.mapView.refresh();
}
}
}
/**
* 清空所有元素
*/
clearElements() {
this.elements = [];
this.needsRedraw = true;
if (this.mapView) {
this.mapView.refresh();
}
}
/**
* 获取所有元素
*/
getElements() {
return [...this.elements];
}
}
function dp2px(dp, customDensity = null) {
// 使用自定义密度或设备像素比
const density = customDensity || (typeof window !== 'undefined' ? window.devicePixelRatio : 1);
return Math.round(dp * density + 0.5);
}
const createStoreImpl = (createState) => {
let state;
const listeners = /* @__PURE__ */ new Set();
const setState = (partial, replace) => {
const nextState = typeof partial === "function" ? partial(state) : partial;
if (!Object.is(nextState, state)) {
const previousState = state;
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
listeners.forEach((listener) => listener(state, previousState));
}
};
const getState = () => state;
const getInitialState = () => initialState;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const api = { setState, getState, getInitialState, subscribe };
const initialState = state = createState(setState, getState, api);
return api;
};
const createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
const identity$1 = (arg) => arg;
function useStore(api, selector = identity$1) {
const slice = React.useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState())
);
React.useDebugValue(slice);
return slice;
}
const createImpl = (createState) => {
const api = createStore(createState);
const useBoundStore = (selector) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
};
const create = (createState) => createState ? createImpl(createState) : createImpl;
const useSubBoundaryBorderStore = create((set, get) => ({
subBoundaryBorder: {},
// 追加单个数据
addSubBoundaryBorder: (key, element) => set((state) => ({
subBoundaryBorder: {
...state.subBoundaryBorder,
[key]: element,
},
})),
// 清空所有数据
clearSubBoundaryBorder: () => set({ subBoundaryBorder: {} }),
// 障碍物
obstacles: {},
addObstacles: (key, element) => set((state) => ({
obstacles: {
...state.obstacles,
[key]: element,
},
})),
clearObstacles: () => set({ obstacles: {} }),
// svg数据
svgElements: {},
addSvgElements: (key, element) => set((state) => ({
svgElements: {
...state.svgElements,
[key]: element,
},
})),
clearSvgElements: () => set({ svgElements: {} }),
}));
/**
* 路径图层
* 专门处理路径元素的渲染
*/
class ChannelLayer extends BaseLayer {
constructor() {
super();
this.level = 1;
this.scale = 1;
this.type = LAYER_DEFAULT_TYPE.CHANNEL;
}
/**
* 获取元素
*/
getElements() {
return this.elements;
}
/**
* SVG渲染方法
*/
drawSVG(svgGroup, scale) {
if (!this.visible || this.elements.length === 0) {
return;
}
this.scale = scale;
// 1. 先创建所有需要的 clipPath 定义
const clipPathIdsMap = this.createExclusionClipPathDefinitions(svgGroup);
// 2. 渲染路径元素
for (const element of this.elements) {
this.renderPath(svgGroup, element, clipPathIdsMap);
}
}
/**
* 创建排除分区内部的 clipPath 定义
*/
createExclusionClipPathDefinitions(svgGroup) {
// 获取所有分区边界数据
const subBoundaryBorder = useSubBoundaryBorderStore.getState().subBoundaryBorder;
if (!subBoundaryBorder || Object.keys(subBoundaryBorder).length === 0) {
return {};
}
const clipPathIdsMap = {};
// 确保 defs 元素存在
let defs = svgGroup.querySelector('defs');
if (!defs) {
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
svgGroup.appendChild(defs);
}
// �� 修改:计算包含所有分区和通道的边界框
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
// 1. 先计算所有分区的边界,如果能拿到边界的svg的大小,就使用这个如果拿不到,就根据分区去计算
const svg = document.getElementById(SVG_MAP_VIEW_ID);
if (svg && svg instanceof SVGSVGElement && svg.viewBox) {
const viewBox = svg.viewBox.baseVal;
minX = viewBox.x;
minY = viewBox.y;
maxX = viewBox.x + viewBox.width;
maxY = viewBox.y + viewBox.height;
}
else {
for (const partitionId in subBoundaryBorder) {
const boundaryData = subBoundaryBorder[partitionId];
if (boundaryData && boundaryData.coordinates && boundaryData.coordinates.length > 0) {
for (const coord of boundaryData.coordinates) {
minX = Math.min(minX, coord[0]);
minY = Math.min(minY, coord[1]);
maxX = Math.max(maxX, coord[0]);
maxY = Math.max(maxY, coord[1]);
}
}
}
}
// 2. 再计算所有通道的边界
for (const element of this.elements) {
// const tunnelConnection = element.originalData?.connection;
// if (tunnelConnection && Array.isArray(tunnelConnection)) {
// const clipPathId = `channel-exclude-${
// element.originalData?.id || Math.random().toString(36).substr(2, 9)
// }`;
// // 检查是否已存在该 clipPath
// const existingClipPath = defs.querySelector(`#${clipPathId}`);
// if (existingClipPath) continue;
// // 创建 clipPath
// const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
// clipPath.setAttribute('id', clipPathId);
// clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
// // === 合成一个 path ===
// let d = `M ${minX} ${minY} L ${maxX} ${minY} L ${maxX} ${maxY} L ${minX} ${maxY} Z`;
// for (const partitionId of tunnelConnection) {
// const boundaryData = subBoundaryBorder[partitionId];
// if (boundaryData && boundaryData.coordinates.length >= 3) {
// d += ` M ${boundaryData.coordinates[0][0]} ${boundaryData.coordinates[0][1]}`;
// for (let i = 1; i < boundaryData.coordinates.length; i++) {
// d += ` L ${boundaryData.coordinates[i][0]} ${boundaryData.coordinates[i][1]}`;
// }
// d += ' Z';
// }
// }
// const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// path.setAttribute('d', d);
// path.setAttribute('clip-rule', 'evenodd'); // 关键
// clipPath.appendChild(path);
// defs.appendChild(clipPath);
// clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
// } else {
const clipPathId = `channel-exclude-all-${element.originalData?.id || Math.random().toString(36).substr(2, 9)}`;
// 检查是否已存在该 clipPath
const existingClipPath = defs.querySelector(`#${clipPathId}`);
if (existingClipPath)
continue;
// 创建 clipPath
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
clipPath.setAttribute('id', clipPathId);
clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
// === 合成一个 path ===
let d = `M ${minX} ${minY} L ${maxX} ${minY} L ${maxX} ${maxY} L ${minX} ${maxY} Z`;
for (const partitionId in subBoundaryBorder) {
const boundaryData = subBoundaryBorder[partitionId];
if (boundaryData && boundaryData.coordinates.length >= 3) {
d += ` M ${boundaryData.coordinates[0][0]} ${boundaryData.coordinates[0][1]}`;
for (let i = 1; i < boundaryData.coordinates.length; i++) {
d += ` L ${boundaryData.coordinates[i][0]} ${boundaryData.coordinates[i][1]}`;
}
d += ' Z';
}
}
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', d);
path.setAttribute('clip-rule', 'evenodd'); // 关键
clipPath.appendChild(path);
defs.appendChild(clipPath);
clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
}
// }
return clipPathIdsMap;
}
/**
* 渲染路径元素
*/
renderPath(svgGroup, element, clipPathIdsMap) {
const { coordinates, style } = element;
if (coordinates.length < 2)
return;
// 构建路径数据,使用整数坐标以避免渲染问题
let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
for (let i = 1; i < coordinates.length; i++) {
pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
}
const topLineWidth = dp2px(style.lineWidth).toString();
const bottomLineWidth = dp2px(style.bottomLineWidth).toString();
const dashLength = dp2px(style.lineWidth);
// 1. 创建底层path(宽度20,boundaryBorder颜色)
const bottomPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
bottomPath.setAttribute('d', pathData);
bottomPath.setAttribute('fill', 'none');
bottomPath.setAttribute('stroke', '#8498A9'); // boundaryBorder颜色
bottomPath.setAttribute('stroke-width', bottomLineWidth);
bottomPath.setAttribute('stroke-linejoin', 'round');
bottomPath.setAttribute('opacity', (style.opacity || 1).toString());
// �� 应用排除 clipPath(如果存在)
if (clipPathIdsMap && clipPathIdsMap[element.originalData?.id.toString()]) {
// 使用 CSS 方式设置 clip-path
bottomPath.style.clipPath = `url(#${clipPathIdsMap[element.originalData?.id.toString()]})`;
}
bottomPath.classList.add('vector-path-bottom');
svgGroup.appendChild(bottomPath);
// 2. 创建上层path(宽度10,居中虚线)
const topPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
topPath.setAttribute('d', pathData);
topPath.setAttribute('fill', 'none');
topPath.setAttribute('stroke', '#FFFFFF');
topPath.setAttribute('stroke-width', topLineWidth);
topPath.setAttribute('stroke-linejoin', 'round');
topPath.setAttribute('opacity', (style.opacity || 1).toString());
// �� 应用排除 clipPath(如果存在)
if (clipPathIdsMap && clipPathIdsMap[element.originalData?.id.toString()]) {
// 使用 CSS 方式设置 clip-path
topPath.style.clipPath = `url(#${clipPathIdsMap[element.originalData?.id.toString()]})`;
}
// 支持两种虚线格式:lineDash数组和strokeDasharray字符串
// 默认虚线样式
topPath.setAttribute('stroke-dasharray', `${dashLength * 2}, ${dashLength}`);
topPath.classList.add('vector-path-top');
svgGroup.appendChild(topPath);
}
}
/**
* 路径图层
* 专门处理路径元素的渲染
*/
class PathLayer extends BaseLayer {
constructor() {
super();
this.level = 3;
this.scale = 1;
this.lineScale = 1;
this.boundaryPaths = {};
this.type = LAYER_DEFAULT_TYPE.PATH;
}
/**
* 创建所有分区并集的 clipPath
*/
createUnionClipPath(svgGroup) {
const { subBoundaryBorder, obstacles, svgElements } = useSubBoundaryBorderStore.getState();
// 确保 defs 元素存在
let defs = svgGroup.querySelector('defs');
if (!defs) {
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
svgGroup.appendChild(defs);
}
const clipPathId = 'clip-union-partitions';
// 如果已存在,先移除
const existing = defs.querySelector(`#${clipPathId}`);
if (existing)
defs.removeChild(existing);
// 合成所有分区的 path
let d = '';
// 1. 外圈(主边界,顺时针)
Object.values(subBoundaryBorder).forEach((item) => {
const bCoords = item.coordinates;
if (bCoords.length >= 3) {
d += `M ${bCoords[0][0]} ${bCoords[0][1]}`;
for (let i = 1; i < bCoords.length; i++) {
d += ` L ${bCoords[i][0]} ${bCoords[i][1]}`;
}
d += ' Z ';
}
});
// 2. 内圈(禁区,逆时针)
Object.values(obstacles).forEach((item) => {
const bCoords = item.coordinates;
if (bCoords.length >= 3) {
d += `M ${bCoords[bCoords.length - 1][0]} ${bCoords[bCoords.length - 1][1]}`;
for (let i = bCoords.length - 2; i >= 0; i--) {
d += ` L ${bCoords[i][0]} ${bCoords[i][1]}`;
}
d += ' Z ';
}
});
// 3. svgElements(解析 SVG 字符串并提取 path 数据)
Object.values(svgElements).forEach((svgPath) => {
const svgPathString = svgPath?.metadata?.svg;
if (svgPathString && typeof svgPathString === 'string' && svgPathString.trim()) {
// 处理转义字符
const processedSvgString = svgPathString.replace(/\\n/g, '\n').replace(/\\"/g, '"');
// 解析 SVG 字符串
const parser = new DOMParser();
const svgDoc = parser.parseFromString(processedSvgString, 'image/svg+xml');
const svgElement = svgDoc.documentElement;
if (svgElement.tagName === 'svg') {
// 查找 path 元素
const pathElement = svgElement.querySelector('path');
if (pathElement) {
const pathData = pathElement.getAttribute('d');
if (pathData) {
// 获取 SVG 元素的变换参数
const centerCoords = svgPath.coordinates?.[0] || [0, 0];
const center = [centerCoords[0], centerCoords[1]];
const userScale = svgPath.metadata.scale || 1;
const direction = svgPath.metadata?.direction || 0;
const originalWidth = parseFloat(svgElement.getAttribute('width') || '76');
const originalHeight = parseFloat(svgElement.getAttribute('height') || '68');
// 应用变换到路径数据
const transformedPathData = this.transformSvgPath(pathData, center, userScale, direction, originalWidth, originalHeight);
d += transformedPathData + ' ';
}
}
}
}
});
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', d);
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
clipPath.setAttribute('id', clipPathId);
clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
clipPath.appendChild(path);
defs.appendChild(clipPath);
return clipPathId;
}
/**
* SVG渲染方法
*/
drawSVG(svgGroup, scale, lineScale) {
if (!this.visible || this.elements.length === 0) {
return;
}
this.scale = scale || 1;
this.lineScale = lineScale || 1;
svgGroup.style.isolation = 'isolate';
// 1. 创建分区并集 clipPath
const clipPathId = this.createUnionClipPath(svgGroup);
// 2. 创建一个组,应用 clipPath
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.setAttribute('clip-path', `url(#${clipPathId})`);
group.setAttribute('opacity', '0.5'); // 统一透明度,防止叠加脏乱
// 3. 优化渲染:按样式分组并合并路径
this.renderOptimizedPaths(group);
svgGroup.appendChild(group);
}
/**
* 优化渲染:按样式分组并合并路径,减少 DOM 节点数量
*/
renderOptimizedPaths(group) {
// 按样式分组存储路径数据
const styleGroups = new Map();
// 收集所有路径数据并按样式分组
for (const element of this.elements) {
// 类型断言:PathLayer 中的 elements 实际上是 PathElements 结构
const pathElement = element;
const { id, elements } = pathElement;
this.boundaryPaths[id] = [];
elements.forEach((pathElement) => {
const { coordinates, style } = pathElement;
if (coordinates.length < 2)
return;
// 生成样式键(用于分组)
const styleKey = this.generateStyleKey(style);
// 构建路径数据
let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
for (let i = 1; i < coordinates.length; i++) {
pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
}
// 按样式分组存储
if (!styleGroups.has(styleKey)) {
styleGroups.set(styleKey, { pathData: [], elements: [] });
}
styleGroups.get(styleKey).pathData.push(pathData);
styleGroups.get(styleKey).elements.push(pathElement);
});
}
// 为每种样式创建一个合并的 path 元素
styleGroups.forEach((groupData) => {
const { pathData, elements } = groupData;
if (pathData.length === 0)
return;
// 使用第一个元素的样式作为该组的样式
const firstElement = elements[0];
const style = firstElement.style;
// 创建合并的 path 元素
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// 合并所有路径数据
const mergedPathData = pathData.join(' ');
path.setAttribute('d', mergedPathData);
// 设置样式属性
path.setAttribute('fill', 'none');
path.setAttribute('stroke', style.lineColor || '#000000');
path.setAttribute('mix-blend-mode', 'normal');
const lineWidth = Math.max(style.lineWidth || 1, 0.5);
path.setAttribute('stroke-width', lineWidth.toString());
path.setAttribute('stroke-linecap', 'round');
path.setAttribute('stroke-linejoin', 'round');
path.classList.add('vector-path');
// 将合并的 path 添加到组中
group.appendChild(path);
// 保存引用到 boundaryPaths 中(保持兼容性)
elements.forEach((element) => {
const { id } = element;
if (!this.boundaryPaths[id]) {
this.boundaryPaths[id] = [];
}
this.boundaryPaths[id].push(path);
});
});
}
/**
* 变换 SVG 路径数据
*/
transformSvgPath(pathData, center, scale, direction, originalWidth, originalHeight) {
// 解析路径数据并应用变换
const commands = pathData.match(/[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*/g) || [];
let transformedCommands = [];
for (const command of commands) {
const type = command[0];
const params = command
.slice(1)
.trim()
.split(/[\s,]+/)
.filter(Boolean)
.map(Number);
if (type === 'Z' || type === 'z') {
// 闭合路径,不需要变换
transformedCommands.push(command);
continue;
}
// 处理坐标参数
let transformedParams = [];
for (let i = 0; i < params.length; i += 2) {
if (i + 1 < params.length) {
let x = params[i];
let y = params[i + 1];
// 应用变换:先平移到中心,然后缩放、旋转,最后平移到目标位置
//