425 lines
15 KiB
C#
425 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Autodesk.Navisworks.Api;
|
||
|
||
namespace NavisworksTransport
|
||
{
|
||
/// <summary>
|
||
/// 坐标转换工具类
|
||
/// 负责2D地图坐标与3D世界坐标之间的转换
|
||
/// </summary>
|
||
public class CoordinateConverter
|
||
{
|
||
private ChannelBounds _channelBounds;
|
||
private double _mapWidth;
|
||
private double _mapHeight;
|
||
private double _defaultElevation;
|
||
private double _zoomFactor = 1.0;
|
||
private double _offsetX = 0.0;
|
||
private double _offsetY = 0.0;
|
||
private const double _marginRatio = 0.1; // 10%边距
|
||
|
||
/// <summary>
|
||
/// 地图宽度(像素)
|
||
/// </summary>
|
||
public double MapWidth
|
||
{
|
||
get { return _mapWidth; }
|
||
set { _mapWidth = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 地图高度(像素)
|
||
/// </summary>
|
||
public double MapHeight
|
||
{
|
||
get { return _mapHeight; }
|
||
set { _mapHeight = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通道边界信息
|
||
/// </summary>
|
||
public ChannelBounds ChannelBounds
|
||
{
|
||
get { return _channelBounds; }
|
||
set { _channelBounds = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 默认高程(Z坐标)
|
||
/// </summary>
|
||
public double DefaultElevation
|
||
{
|
||
get { return _defaultElevation; }
|
||
set { _defaultElevation = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 当前缩放因子
|
||
/// </summary>
|
||
public double ZoomFactor
|
||
{
|
||
get { return _zoomFactor; }
|
||
set { _zoomFactor = Math.Max(0.1, Math.Min(50.0, value)); } // 限制缩放范围
|
||
}
|
||
|
||
/// <summary>
|
||
/// X轴偏移量
|
||
/// </summary>
|
||
public double OffsetX
|
||
{
|
||
get { return _offsetX; }
|
||
set { _offsetX = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Y轴偏移量
|
||
/// </summary>
|
||
public double OffsetY
|
||
{
|
||
get { return _offsetY; }
|
||
set { _offsetY = value; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="channelBounds">通道边界信息</param>
|
||
/// <param name="mapWidth">地图窗口宽度</param>
|
||
/// <param name="mapHeight">地图窗口高度</param>
|
||
public CoordinateConverter(ChannelBounds channelBounds, double mapWidth, double mapHeight)
|
||
{
|
||
_channelBounds = channelBounds ?? throw new ArgumentNullException(nameof(channelBounds));
|
||
_mapWidth = mapWidth;
|
||
_mapHeight = mapHeight;
|
||
|
||
// 默认使用通道底部高程
|
||
_defaultElevation = channelBounds.MinPoint.Z + 0.1; // 稍微抬高一点避免在地面上
|
||
|
||
// 自动计算最佳视图
|
||
CalculateBestFitView();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算最佳适应视图
|
||
/// </summary>
|
||
public void CalculateBestFitView()
|
||
{
|
||
if (_channelBounds == null) return;
|
||
|
||
// 计算世界坐标的实际尺寸
|
||
double worldWidth = _channelBounds.MaxPoint.X - _channelBounds.MinPoint.X;
|
||
double worldHeight = _channelBounds.MaxPoint.Y - _channelBounds.MinPoint.Y;
|
||
|
||
// 如果世界尺寸太小,设置最小显示尺寸
|
||
double minWorldSize = 1.0; // 最小1米
|
||
if (worldWidth < minWorldSize) worldWidth = minWorldSize;
|
||
if (worldHeight < minWorldSize) worldHeight = minWorldSize;
|
||
|
||
// 重置缩放和偏移(现在使用简化的坐标转换)
|
||
_zoomFactor = 1.0;
|
||
_offsetX = 0.0;
|
||
_offsetY = 0.0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将2D地图坐标转换为3D世界坐标
|
||
/// </summary>
|
||
/// <param name="mapPoint">2D地图坐标(像素坐标)</param>
|
||
/// <returns>3D世界坐标</returns>
|
||
public Point3D MapToWorld(MapPoint2D mapPoint)
|
||
{
|
||
if (mapPoint == null)
|
||
throw new ArgumentNullException(nameof(mapPoint));
|
||
|
||
// 计算可用的地图尺寸(减去边距)
|
||
double availableWidth = _mapWidth * (1.0 - 2 * _marginRatio);
|
||
double availableHeight = _mapHeight * (1.0 - 2 * _marginRatio);
|
||
|
||
// 从地图坐标计算相对位置
|
||
double relativeX = (mapPoint.X - _mapWidth * _marginRatio) / availableWidth;
|
||
double relativeY = 1.0 - ((mapPoint.Y - _mapHeight * _marginRatio) / availableHeight); // Y轴翻转
|
||
|
||
// 转换为世界坐标
|
||
double worldX = _channelBounds.MinPoint.X + relativeX * (_channelBounds.MaxPoint.X - _channelBounds.MinPoint.X);
|
||
double worldY = _channelBounds.MinPoint.Y + relativeY * (_channelBounds.MaxPoint.Y - _channelBounds.MinPoint.Y);
|
||
double worldZ = _defaultElevation;
|
||
|
||
return new Point3D(worldX, worldY, worldZ);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将3D世界坐标转换为2D地图坐标
|
||
/// </summary>
|
||
/// <param name="worldPoint">3D世界坐标</param>
|
||
/// <returns>2D地图坐标(像素坐标)</returns>
|
||
public MapPoint2D WorldToMap(Point3D worldPoint)
|
||
{
|
||
if (worldPoint == null)
|
||
throw new ArgumentNullException(nameof(worldPoint));
|
||
|
||
// 计算世界坐标的范围
|
||
double worldWidth = _channelBounds.MaxPoint.X - _channelBounds.MinPoint.X;
|
||
double worldHeight = _channelBounds.MaxPoint.Y - _channelBounds.MinPoint.Y;
|
||
|
||
// 计算在通道范围内的相对位置(0-1之间)
|
||
double relativeX = (worldPoint.X - _channelBounds.MinPoint.X) / worldWidth;
|
||
double relativeY = (worldPoint.Y - _channelBounds.MinPoint.Y) / worldHeight;
|
||
|
||
// 计算可用的地图尺寸(减去边距)
|
||
double availableWidth = _mapWidth * (1.0 - 2 * _marginRatio);
|
||
double availableHeight = _mapHeight * (1.0 - 2 * _marginRatio);
|
||
|
||
// 转换为地图像素坐标
|
||
double mapX = relativeX * availableWidth + _mapWidth * _marginRatio;
|
||
double mapY = (1.0 - relativeY) * availableHeight + _mapHeight * _marginRatio; // Y轴翻转
|
||
|
||
// 确保坐标在地图范围内
|
||
mapX = Math.Max(0, Math.Min(_mapWidth, mapX));
|
||
mapY = Math.Max(0, Math.Min(_mapHeight, mapY));
|
||
|
||
return new MapPoint2D(mapX, mapY);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量转换2D地图坐标为3D世界坐标
|
||
/// </summary>
|
||
/// <param name="mapPoints">2D地图坐标集合</param>
|
||
/// <returns>3D世界坐标集合</returns>
|
||
public List<Point3D> MapToWorldBatch(IEnumerable<MapPoint2D> mapPoints)
|
||
{
|
||
if (mapPoints == null)
|
||
throw new ArgumentNullException(nameof(mapPoints));
|
||
|
||
var worldPoints = new List<Point3D>();
|
||
foreach (var mapPoint in mapPoints)
|
||
{
|
||
worldPoints.Add(MapToWorld(mapPoint));
|
||
}
|
||
return worldPoints;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量转换3D世界坐标为2D地图坐标
|
||
/// </summary>
|
||
/// <param name="worldPoints">3D世界坐标集合</param>
|
||
/// <returns>2D地图坐标集合</returns>
|
||
public List<MapPoint2D> WorldToMapBatch(IEnumerable<Point3D> worldPoints)
|
||
{
|
||
if (worldPoints == null)
|
||
throw new ArgumentNullException(nameof(worldPoints));
|
||
|
||
var mapPoints = new List<MapPoint2D>();
|
||
foreach (var worldPoint in worldPoints)
|
||
{
|
||
mapPoints.Add(WorldToMap(worldPoint));
|
||
}
|
||
return mapPoints;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查2D地图坐标是否在有效范围内
|
||
/// </summary>
|
||
/// <param name="mapPoint">2D地图坐标</param>
|
||
/// <returns>是否在有效范围内</returns>
|
||
public bool IsValidMapPoint(MapPoint2D mapPoint)
|
||
{
|
||
if (mapPoint == null) return false;
|
||
|
||
return mapPoint.X >= 0 && mapPoint.X <= _mapWidth &&
|
||
mapPoint.Y >= 0 && mapPoint.Y <= _mapHeight;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查3D世界坐标是否在通道范围内
|
||
/// </summary>
|
||
/// <param name="worldPoint">3D世界坐标</param>
|
||
/// <returns>是否在通道范围内</returns>
|
||
public bool IsValidWorldPoint(Point3D worldPoint)
|
||
{
|
||
if (worldPoint == null) return false;
|
||
|
||
return worldPoint.X >= _channelBounds.MinPoint.X && worldPoint.X <= _channelBounds.MaxPoint.X &&
|
||
worldPoint.Y >= _channelBounds.MinPoint.Y && worldPoint.Y <= _channelBounds.MaxPoint.Y;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算地图缩放比例(世界单位/像素)
|
||
/// </summary>
|
||
/// <param name="scaleX">X轴缩放比例</param>
|
||
/// <param name="scaleY">Y轴缩放比例</param>
|
||
public void GetMapScale(out double scaleX, out double scaleY)
|
||
{
|
||
double worldWidth = _channelBounds.MaxPoint.X - _channelBounds.MinPoint.X;
|
||
double worldHeight = _channelBounds.MaxPoint.Y - _channelBounds.MinPoint.Y;
|
||
|
||
scaleX = worldWidth / _mapWidth;
|
||
scaleY = worldHeight / _mapHeight;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取地图中心点的世界坐标
|
||
/// </summary>
|
||
/// <returns>地图中心点的3D世界坐标</returns>
|
||
public Point3D GetMapCenterInWorld()
|
||
{
|
||
var mapCenter = new MapPoint2D(_mapWidth / 2.0, _mapHeight / 2.0);
|
||
return MapToWorld(mapCenter);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据通道高程调整3D坐标的Z值
|
||
/// </summary>
|
||
/// <param name="worldPoint">3D世界坐标</param>
|
||
/// <param name="elevationOffset">高程偏移量(相对于通道底部)</param>
|
||
/// <returns>调整后的3D坐标</returns>
|
||
public Point3D AdjustElevation(Point3D worldPoint, double elevationOffset = 0.0)
|
||
{
|
||
if (worldPoint == null)
|
||
throw new ArgumentNullException(nameof(worldPoint));
|
||
|
||
double adjustedZ = _channelBounds.MinPoint.Z + elevationOffset;
|
||
return new Point3D(worldPoint.X, worldPoint.Y, adjustedZ);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算两个地图点之间的像素距离
|
||
/// </summary>
|
||
/// <param name="point1">第一个地图点</param>
|
||
/// <param name="point2">第二个地图点</param>
|
||
/// <returns>像素距离</returns>
|
||
public double CalculateMapDistance(MapPoint2D point1, MapPoint2D point2)
|
||
{
|
||
if (point1 == null || point2 == null)
|
||
throw new ArgumentNullException("地图点不能为空");
|
||
|
||
double dx = point2.X - point1.X;
|
||
double dy = point2.Y - point1.Y;
|
||
return Math.Sqrt(dx * dx + dy * dy);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算两个世界坐标点之间的距离
|
||
/// </summary>
|
||
/// <param name="point1">第一个世界坐标点</param>
|
||
/// <param name="point2">第二个世界坐标点</param>
|
||
/// <returns>世界距离(米)</returns>
|
||
public double CalculateWorldDistance(Point3D point1, Point3D point2)
|
||
{
|
||
if (point1 == null || point2 == null)
|
||
throw new ArgumentNullException("世界坐标点不能为空");
|
||
|
||
double dx = point2.X - point1.X;
|
||
double dy = point2.Y - point1.Y;
|
||
double dz = point2.Z - point1.Z;
|
||
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新地图尺寸
|
||
/// </summary>
|
||
/// <param name="newWidth">新的地图宽度</param>
|
||
/// <param name="newHeight">新的地图高度</param>
|
||
public void UpdateMapSize(double newWidth, double newHeight)
|
||
{
|
||
_mapWidth = newWidth;
|
||
_mapHeight = newHeight;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新通道边界
|
||
/// </summary>
|
||
/// <param name="newChannelBounds">新的通道边界</param>
|
||
public void UpdateChannelBounds(ChannelBounds newChannelBounds)
|
||
{
|
||
_channelBounds = newChannelBounds ?? throw new ArgumentNullException(nameof(newChannelBounds));
|
||
|
||
// 更新默认高程
|
||
_defaultElevation = _channelBounds.MinPoint.Z + 0.1;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取转换器信息摘要
|
||
/// </summary>
|
||
/// <returns>转换器信息字符串</returns>
|
||
public override string ToString()
|
||
{
|
||
double scaleX, scaleY;
|
||
GetMapScale(out scaleX, out scaleY);
|
||
return $"坐标转换器 - 地图尺寸:{_mapWidth}x{_mapHeight}, " +
|
||
$"世界范围:({_channelBounds.MinPoint.X:F2},{_channelBounds.MinPoint.Y:F2}) - " +
|
||
$"({_channelBounds.MaxPoint.X:F2},{_channelBounds.MaxPoint.Y:F2}), " +
|
||
$"缩放比例:{scaleX:F4},{scaleY:F4}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 放大视图
|
||
/// </summary>
|
||
/// <param name="factor">放大倍数</param>
|
||
/// <param name="centerPoint">放大中心点(地图坐标)</param>
|
||
public void ZoomIn(double factor = 1.5, MapPoint2D centerPoint = null)
|
||
{
|
||
if (centerPoint == null)
|
||
{
|
||
centerPoint = new MapPoint2D(_mapWidth / 2.0, _mapHeight / 2.0);
|
||
}
|
||
|
||
// 保存当前中心点在世界坐标中的位置
|
||
var worldCenter = MapToWorld(centerPoint);
|
||
|
||
// 应用缩放
|
||
_zoomFactor *= factor;
|
||
_zoomFactor = Math.Min(50.0, _zoomFactor); // 限制最大缩放
|
||
|
||
// 重新计算偏移以保持中心点位置
|
||
var newMapCenter = WorldToMap(worldCenter);
|
||
_offsetX += centerPoint.X - newMapCenter.X;
|
||
_offsetY += centerPoint.Y - newMapCenter.Y;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 缩小视图
|
||
/// </summary>
|
||
/// <param name="factor">缩小倍数</param>
|
||
/// <param name="centerPoint">缩小中心点(地图坐标)</param>
|
||
public void ZoomOut(double factor = 1.5, MapPoint2D centerPoint = null)
|
||
{
|
||
if (centerPoint == null)
|
||
{
|
||
centerPoint = new MapPoint2D(_mapWidth / 2.0, _mapHeight / 2.0);
|
||
}
|
||
|
||
// 保存当前中心点在世界坐标中的位置
|
||
var worldCenter = MapToWorld(centerPoint);
|
||
|
||
// 应用缩放
|
||
_zoomFactor /= factor;
|
||
_zoomFactor = Math.Max(0.1, _zoomFactor); // 限制最小缩放
|
||
|
||
// 重新计算偏移以保持中心点位置
|
||
var newMapCenter = WorldToMap(worldCenter);
|
||
_offsetX += centerPoint.X - newMapCenter.X;
|
||
_offsetY += centerPoint.Y - newMapCenter.Y;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置视图到最佳适应
|
||
/// </summary>
|
||
public void ResetView()
|
||
{
|
||
CalculateBestFitView();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 平移视图
|
||
/// </summary>
|
||
/// <param name="deltaX">X轴移动距离(像素)</param>
|
||
/// <param name="deltaY">Y轴移动距离(像素)</param>
|
||
public void Pan(double deltaX, double deltaY)
|
||
{
|
||
_offsetX += deltaX;
|
||
_offsetY += deltaY;
|
||
}
|
||
}
|
||
} |