// utils.js
/**
* 通过判断type返回目标图片的地址
* @param {String} type 图片类型
* @returns {String} url 目标图片的地址
*/
export function setImgUrl(type) {
let url;
switch (type) {
case "track":
url = require("./image/car.png");
break;
case "gantry":
url = require("./image/equipmentIcon.png");
break;
case "station":
url = require("./image/dataIcon.png");
break;
case "hub":
url = require("./image/userIcon.png");
break;
case "hh-station":
url = require("./image/homeIcon.png");
break;
case "icon-hub":
url = require("./image/password.png");
break;
default:
url = require("./image/user.png");
break;
}
return url;
}
/**
* 为数据对象添加位置标记字段
* @param {Object.} data 原始数据对象,键为分组标识,值为对象数组
* @returns {Object.} 处理后的新对象,数组元素添加 position 字段(start/end/transition)
*/
export function addPositionFields(data) {
const result = {};
// 遍历对象的每个属性
for (const key in data) {
if (data.hasOwnProperty(key)) {
const array = data[key];
result[key] = [];
// 处理数组中的每个元素
for (let i = 0; i < array.length; i++) {
const item = { ...array[i] }; // 创建新对象避免修改原数据
// 根据位置设置 position 字段
if (i === 0) {
item.position = 'start';
} else if (i === array.length - 1) {
item.position = 'end';
} else {
item.position = 'transition';
}
result[key].push(item);
}
}
}
return result;
}
/**
* 为数据对象添加链式连接关系
* @param {Object.} data 原始数据对象,键为分组标识,值为对象数组
* @returns {Object.} 处理后的新对象,数组元素添加 source(当前节点ID)和 target(下一节点ID)字段,
* 格式为 { 分组标识: [{ source: "key-i", target: "key-(i+1)" }, ...] }
* @example
* // 输入
* { group1: [{}, {}, {}] }
* // 输出
* {
* group1: [
* { source: 'group1-0', target: 'group1-1' },
* { source: 'group1-1', target: 'group1-2' },
* { source: 'group1-2', target: null }
* ]
* }
*/
export function setSourceAndTarget(data) {
const result = {};
// 遍历对象的每个属性
for (const key in data) {
if (data.hasOwnProperty(key)) {
const array = data[key];
result[key] = [];
// 处理数组中的每个元素
for (let i = 0; i < array.length; i++) {
const item = { ...array[i] }; // 创建新对象避免修改原数据
// 设置节点连接关系:
// - 非末尾节点:target指向下一节点(key-(i+1))
// - 末尾节点:target设为null表示终点
if (i !== array.length - 1) {
item.source = `${key}-${i}`;
item.target = `${key}-${i + 1}`;
} else {
item.source = `${key}-${i}`;
item.target = null;
}
result[key].push(item);
}
}
}
return result;
}
/**
* 格式化并去重点位数据
* @param {Object.} pointData 原始点位数据对象,键为分组标识,值为点位数组
* @returns {Array} 去重后的点位数组(根据 code 字段去重)
*/
export function pointFormat(pointData) {
// let _pointData = [];
// for (let key in pointData) {
// const gsPointList = pointData[key];
// // 获取点位数据(去重)
// gsPointList.forEach((ele) => {
// const info = _pointData.find(
// (item) => item.code === ele.code
// );
// if (!info) {
// _pointData.push(ele);
// }
// });
// }
// return _pointData;
const uniquePoints = new Set();
const result = [];
for (const gsPointList of Object.values(pointData)) {
for (const point of gsPointList) {
if (!uniquePoints.has(point.code)) {
uniquePoints.add(point.code);
result.push(point);
}
}
}
return result;
}
/**
* 将地理坐标转换为屏幕坐标
* @param {Array
export function pointCoordinateSwitch(data, width, height) {
// 过滤无效点位
const _data = data.filter((ele) => ele.lat && ele.lon);
if (!_data.length) return [];
// 初始化最大最小值
let latMin = Infinity,
latMax = -Infinity;
let lonMin = Infinity,
lonMax = -Infinity;
// 单次遍历数组,计算最大最小值
for (let i = 0; i < data.length; i++) {
const { lat, lon } = data[i];
if (lat < latMin) latMin = lat;
if (lat > latMax) latMax = lat;
if (lon < lonMin) lonMin = lon;
if (lon > lonMax) lonMax = lon;
}
// 此处 减去 200 为了保证点位都显示在容器内,后续点位的横纵坐标 +100
width -= 200;
height -= 200;
return data.map((ele) => ({
...ele,
imgType: "square",
size: 10,
x: ((ele.lon - lonMin) / (lonMax - lonMin)) * width + 100,
y:
height -
((ele.lat - latMin) / (latMax - latMin)) * height +
100,
}));
}
/**
* 将图片中的相对坐标转换为页面中的绝对坐标
* @param {Array} points - 点位数组,每个点位包含x,y坐标(相对图片的坐标)
* @param {Object} imageInfo - 图片信息对象
* @param {number} imageInfo.width - 图片原始宽度
* @param {number} imageInfo.height - 图片原始高度
* @param {Object} containerInfo - 容器信息对象
* @param {number} containerInfo.width - 页面容器宽度
* @param {number} containerInfo.height - 页面容器高度
* @param {string} [mode='contain'] - 图片适配模式:'contain'(默认)|'fill'
* @param {number} [margin=100] - 四周边距(像素)
* @returns {Array} - 转换后的坐标数组
*/
export function convertImageCoordsToPageCoords(points, imageInfo, containerInfo, mode = 'contain', margin = 100) {
const { width: imgWidth, height: imgHeight } = imageInfo;
let { width: containerWidth, height: containerHeight } = containerInfo;
// 应用边距,调整有效容器尺寸
const effectiveWidth = Math.max(containerWidth - 2 * margin, 1);
const effectiveHeight = Math.max(containerHeight - 2 * margin, 1);
// 计算图片在有效容器区域中的实际显示尺寸和位置
let displayWidth, displayHeight, offsetX = margin, offsetY = margin;
const imgRatio = imgWidth / imgHeight;
const containerRatio = effectiveWidth / effectiveHeight;
if (mode === 'fill') {
// 填充模式,直接拉伸填满有效容器区域
displayWidth = effectiveWidth;
displayHeight = effectiveHeight;
} else {
// 默认contain模式,保持比例完整显示在有效容器区域内
if (imgRatio > containerRatio) {
displayWidth = effectiveWidth;
displayHeight = displayWidth / imgRatio;
offsetY += (effectiveHeight - displayHeight) / 2;
} else {
displayHeight = effectiveHeight;
displayWidth = displayHeight * imgRatio;
offsetX += (effectiveWidth - displayWidth) / 2;
}
}
// 计算缩放比例
const scaleX = displayWidth / imgWidth;
const scaleY = displayHeight / imgHeight;
// 转换每个点的坐标
return points.map(point => {
return {
...point,
x: offsetX + (point.x * scaleX),
y: offsetY + (point.y * scaleY),
// 保留原始数据
originalX: point.x,
originalY: point.y,
};
});
}
/**
* 生成站点连接线基础数据
* @param {Array} linkData 原始链路数据,需包含 code(站点编码)和 target(目标站点编码)字段
* @returns {Array} 连接线数组,每个元素包含:
* - source: 源站点对象
* - target: 目标站点对象
* - gsName: 所属高速路名称
*/
function getLineData(linkData) {
const res = [];
// 创建一个站点代码与站点对象的映射
const stationMap = linkData.reduce((map, station) => {
map[station.code] = station;
return map;
}, {});
// 遍历原始的站点列表来构建最终的结果
for (let i = 0; i < linkData.length; i++) {
const currentStation = linkData[i];
const targetCode = currentStation.target;
// 如果目标站点存在
if (targetCode && stationMap[targetCode]) {
const targetStation = stationMap[targetCode];
// 创建一个新的对象,将source和target配对
res.push({
source: currentStation,
target: targetStation,
gsName: currentStation.gsName,
});
// 标记该站点的目标站点为null,防止重复配对
stationMap[targetCode] = null;
}
}
return res;
}
/**
* 生成高速公路连接线数据集
* @param {Object.} pointObj 分组点位对象,键为高速路名称,值为该路段点位数组
* @param {Array} pointData 全量点位数据集,用于查找箭头标记点
* @returns {Array} 包含完整坐标信息的连接线数组,每个元素包含:
* - gsName: 高速路名称
* - source: 起点坐标及元数据
* - target: 终点坐标及元数据
*/
export function getAllLinkLineHaveArrowData(pointObj, pointData) {
// 获取连接线数据(遍历原数据集合,由于箭头点位在原数据集合中
// 不存在,所以需要在遍历时为每一条高速添加上箭头点位)
let _lineData = [];
for (let key in pointObj) {
let gsArrowPoint = pointData.find(
(ele) => ele.gsName === key && !ele.type
);
gsArrowPoint.source = gsArrowPoint.code;
// 修改箭头点位前面的一个点位的target值
pointObj[key][pointObj[key].length - 1].target =
gsArrowPoint.code;
pointObj[key].push(gsArrowPoint);
_lineData.push(...getLineData(pointObj[key]));
}
// 根据已获取到的连线数据,结合点位数据,设置x,y坐标
return _lineData.map((ele) => {
const _target = pointData.find(
(item) => item.code === ele.target.code
);
const _source = pointData.find(
(item) => item.code === ele.source.code
);
return {
gsName: ele.gsName,
source: { ...ele.source, x: _source.x, y: _source.y },
target: { ...ele.target, x: _target.x, y: _target.y },
};
});
}
export function getAllLinkLineNoArrowData(pointObj, pointData) {
// 获取连接线数据(遍历原数据集合,由于箭头点位在原数据集合中
// 不存在,所以需要在遍历时为每一条高速添加上箭头点位)
let _lineData = [];
for (let key in pointObj) {
let gsArrowPoint = pointData.find(
(ele) => ele.gsName === key
);
gsArrowPoint.source = gsArrowPoint.code;
_lineData.push(...getLineData(pointObj[key]));
}
// 根据已获取到的连线数据,结合点位数据,设置x,y坐标
return _lineData.map((ele) => {
const _target = pointData.find(
(item) => item.code === ele.target.code
);
const _source = pointData.find(
(item) => item.code === ele.source.code
);
return {
gsName: ele.gsName,
source: { ...ele.source, x: _source.x, y: _source.y },
target: { ...ele.target, x: _target.x, y: _target.y },
};
});
}
export function calculateGSNamePosition(lastOne,
lastTwo,
label,
distance,
direction,
type) {
// 计算lastOne到lastTwo的向量
const vx = lastOne.x - lastTwo.x;
const vy = lastOne.y - lastTwo.y;
// 计算lastOne到lastTwo的距离
const dist = Math.sqrt(vx * vx + vy * vy);
// 计算单位向量
const unitX = vx / dist;
const unitY = vy / dist;
let newX, newY;
if (direction === "front") {
// 计算反向单位向量
const reverseUnitX = -unitX;
const reverseUnitY = -unitY;
// 根据反向单位向量计算前一个点的位置,前一个点距离lastOne的横纵坐标为指定的距离
newX = lastOne.x - reverseUnitX * distance;
newY = lastOne.y - reverseUnitY * distance;
} else if (direction === "after") {
// 根据单位向量计算a3的位置,a3距离lastOne的横纵坐标都为200
newX = lastOne.x + unitX * distance;
newY = lastOne.y + unitY * distance;
}
if (type === "text") {
return { x: newX, y: newY + 4, label, gsName: label };
} else if (type === "arrow") {
const num =
new Date().getTime() + parseInt(Math.random() * 10000);
return {
x: newX,
y: newY,
// type: "station",
type: null,
gsName: label,
code: num,
source: lastOne.code,
target: null,
};
}
}
export function addArrowPoint(data) {
// 创建一个新的数组来存储最终的结果
const result = [];
// 遍历原始数组
for (let i = 0; i < data.length; i++) {
// 当前项
const current = data[i];
// // 如果position为"start",先插入type为"arrow"的数据
// if (current.position === "start") {
// if (data[i + 1].gsName === current.gsName) {
// // 计算首部箭头坐标
// const frontArrow = this.calculateGSNamePosition(
// current,
// data[i + 1],
// current.gsName,
// 80,
// "front",
// "arrow"
// );
// result.push(frontArrow); // 插入箭头数据
// }
// }
// 插入当前项
result.push(current);
// 如果position为"end",再插入type为"arrow"的数据
if (current.position === "end") {
if (data[i - 1].gsName === current.gsName) {
// 计算尾部箭头坐标
const afterArrow = calculateGSNamePosition(
current,
data[i - 1],
current.gsName,
50,
"after",
"arrow"
);
result.push(afterArrow); // 插入箭头数据
current.target = afterArrow.code;
}
}
}
return result;
}
export function calculateMidPoints(trackList) {
const midPoints = [];
for (let i = 0; i < trackList.length - 1; i++) {
const currentPoint = trackList[i];
const nextPoint = trackList[i + 1];
// 检查当前点和下一个点的type和position是否符合条件
if (
currentPoint.type &&
nextPoint.type &&
currentPoint.position !== "end"
) {
// 计算中间点
const midLat = (currentPoint.x + nextPoint.x) / 2;
const midLon = (currentPoint.y + nextPoint.y) / 2;
// 将中间点信息存储到数组中
midPoints.push({
x: midLat,
y: midLon,
label: `${(currentPoint.code + nextPoint.code) / 2}`,
code: (currentPoint.code + nextPoint.code) / 2,
type: "gantry",
gsName: (currentPoint.code + nextPoint.code) / 2,
position: "mid",
});
}
}
return midPoints;
}
// data.js
export const colorList = [
"#409EFF",
"#67C23A",
// "#E6A23C",
// "#F56C6C",
// "#909399",
]
export const pointData = Object.freeze({
'申嘉湖': [
{
lat: 30.577337,
lon: 120.293738,
label: "新市枢纽",
code: 3206,
source: 3206,
target: 4053,
road: 3,
type: "icon-hub",
gsName: "申嘉湖",
},
{
lat: 30.552239,
lon: 120.363261,
label: "洲泉",
code: 4053,
source: 4053,
target: 4055,
road: 3,
type: "gantry",
gsName: "申嘉湖",
},
{
lat: 30.561971,
lon: 120.415528,
label: "崇福北",
code: 4055,
source: 4055,
target: 4057,
road: 3,
type: "gantry",
gsName: "申嘉湖",
},
{
lat: 30.560235,
lon: 120.456098,
label: "凤鸣",
code: 4057,
source: 4057,
target: 4009,
road: 3,
type: "gantry",
gsName: "申嘉湖",
},
{
lat: 30.559178,
lon: 120.487902,
label: "凤鸣枢纽",
code: 4009,
source: 4009,
target: null,
road: [1, 2, 3],
type: "icon-hub",
gsName: "申嘉湖",
},
],
'申苏浙皖': [
{
lat: 30.719011,
lon: 120.455571,
label: "练市枢纽",
code: 3203,
source: 3203,
target: 4447,
road: 1,
type: "icon-hub",
gsName: "申苏浙皖",
},
{
lat: 30.656565,
lon: 120.475788,
label: "乌镇南",
code: 4447,
source: 4447,
target: 4449,
road: 1,
type: "gantry",
gsName: "申苏浙皖",
},
{
lat: 30.600793,
lon: 120.482239,
label: "梧桐",
code: 4449,
source: 4449,
target: 4006,
road: 1,
type: "gantry",
gsName: "申苏浙皖",
},
{
lat: 30.559178,
lon: 120.487902,
label: "凤鸣枢纽",
code: 4006,
source: 4006,
target: 4451,
road: [1, 2, 3],
type: "icon-hub",
gsName: "申苏浙皖",
},
{
lat: 30.542289,
lon: 120.488243,
label: "崇福",
code: 4451,
source: 4451,
target: 4005,
road: 2,
type: "gantry",
gsName: "申苏浙皖",
},
{
lat: 30.509447,
lon: 120.510563,
label: "骑塘枢纽",
code: 4005,
source: 4005,
target: null,
road: 2,
type: "icon-hub",
gsName: "申苏浙皖",
},
],
});
export const doubleTrackPointData = Object.freeze({
'申嘉湖': [
{
lat: 30.577337,
lon: 120.293738,
label: "新市枢纽",
code: 3206,
source: 3206,
target: 4053,
road: 3,
type: "icon-hub",
gsName: "申嘉湖",
},
{
lat: 30.552239,
lon: 120.363261,
label: "洲泉",
code: 4053,
source: 4053,
target: 4055,
road: 3,
type: "gantry",
gsName: "申嘉湖",
},
{
lat: 30.561971,
lon: 120.415528,
label: "崇福北",
code: 4055,
source: 4055,
target: 4057,
road: 3,
type: "gantry",
gsName: "申嘉湖",
},
{
lat: 30.560235,
lon: 120.456098,
label: "凤鸣",
code: 4057,
source: 4057,
target: 4009,
road: 3,
type: "gantry",
gsName: "申嘉湖",
},
{
lat: 30.559178,
lon: 120.487902,
label: "凤鸣枢纽",
code: 4009,
source: 4009,
target: null,
road: [1, 2, 3],
type: "icon-hub",
gsName: "申嘉湖",
},
],
'申苏浙皖': [
{
lat: 30.719011,
lon: 120.455571,
label: "练市枢纽",
code: 3203,
source: 3203,
target: 4447,
road: 1,
type: "icon-hub",
gsName: "申苏浙皖",
},
{
lat: 30.656565,
lon: 120.475788,
label: "乌镇南",
code: 4447,
source: 4447,
target: 4449,
road: 1,
type: "gantry",
gsName: "申苏浙皖",
},
{
lat: 30.600793,
lon: 120.482239,
label: "梧桐",
code: 4449,
source: 4449,
target: 4006,
road: 1,
type: "gantry",
gsName: "申苏浙皖",
},
{
lat: 30.559178,
lon: 120.487902,
label: "凤鸣枢纽",
code: 4006,
source: 4006,
target: 4451,
road: [1, 2, 3],
type: "icon-hub",
gsName: "申苏浙皖",
},
{
lat: 30.542289,
lon: 120.488243,
label: "崇福",
code: 4451,
source: 4451,
target: 4005,
road: 2,
type: "gantry",
gsName: "申苏浙皖",
},
{
lat: 30.509447,
lon: 120.510563,
label: "骑塘枢纽",
code: 4005,
source: 4005,
target: null,
road: 2,
type: "icon-hub",
gsName: "申苏浙皖",
},
],
});
// index.vue
类型:
// canvasSecond.vue
// canvasRender.vue
轨迹:
{{ item.name }}
{{ text }}
transform 0.5s ease;
.el-icon-close {
position: absolute;
top: 0;
right: 0;
font-size: 16px;
}
.arrow {
position: absolute;
width: 0;
height: 0;
bottom: -8px;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 8px solid #fff; /* 这个颜色就是倒三角形的颜色 /
}
}
::v-deep .visible {
opacity: 1!important; / 完全显示 /
transform: scale(1)!important; / 正常大小 */
}
::v-deep .opacity-10 {
opacity: 1!important;
}
::v-deep .opacity-2 {
opacity: 0.2;
}
::v-deep .opacity-1 {
opacity: 0.1;
}
.empty {
height: 100%;
width: 100%;
display: flex;
position: absolute;
z-index: 10;
top: 0;
span {
margin: auto;
}
}
}
.action-panel {
height: auto;
width: auto;
box-shadow: 0 4px 15px 0 rgba(0, 0, 0, .1);
position: absolute;
top: 12px;
left: 12px;
border-radius: 4px;
display: flex;
flex-direction: column;
// align-items: center;
background-color: #fff;
padding: 12px;
z-index: 99;
.margin-right-6 {
margin-right: 6px;
}
.container {
height: 40px;
display: flex;
align-items: center;
margin-top: 40px;
.label {
width: 60px;
height: 40px;
line-height: 40px;
font-size: 14px;
text-align: right;
}
.radio-button {
height: 40px;
display: flex;
align-items: center;
}
}
.track-info {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-left: 60px;
.el-radio {
margin-top: 12px;
margin-right: 0!important;
}
}
}
}
```图片可以自行设置,按照对应的名称命名即可,这里就不上传图片