znlgis 博客

GIS开发与技术分享

第十三章:道路与交叉口系统详解

13.1 道路系统概述

13.1.1 道路系统在场布设计中的地位

在建筑工程场地布置设计中,道路系统是连接各个功能区域的关键基础设施。FY_Layout的道路系统提供了从城市道路到施工便道、从路面硬化到出土运输等全方位的道路设计能力。

道路系统在FY_Layout中的核心地位体现在:

  • 连通性:道路是场地内各功能区域(如板房区、材料堆场、基坑区域)的交通纽带
  • 安全性:合理的道路布局是施工安全的重要保障
  • 经济性:优化的道路设计可以减少土方运输成本
  • 规范性:道路设计需要满足施工规范和安全标准

13.1.2 道路系统的元素类型

FY_Layout的道路系统包含四种主要元素类型:

元素类型 类名 中文名 主要用途
Road QdRoad 硬化地面 城市道路和主要交通干道
Berm QdBerm 出土道路 土方运输便道
Harden QdHarden 路面硬化 施工场地路面硬化处理
Ground QdGround 硬化地面 场地硬化区域

这四种元素各有侧重,但共享相似的开发模式和技术架构。

13.1.3 道路系统的技术特点

FY_Layout道路系统具有以下技术特点:

  1. 多种创建方式:支持多边形绘制、矩形绘制、已有线段转换三种创建模式
  2. 参数化设计:道路宽度、高程、材质等参数可动态调整
  3. 二三维联动:二维绘图自动生成对应的三维模型
  4. 交叉口处理:支持道路交叉口的自动计算和渲染
  5. 图层管理:每种道路类型自动分配到对应图层

13.2 QdRoad 城市道路实现详解

13.2.1 QdRoad类结构

QdRoad是FY_Layout中最复杂的元素类之一,其源代码超过44KB,体现了城市道路设计的复杂性。

public class QdRoad : DirectComponent
{
    // 道路底部高程
    public double Bottom
    {
        get { return Properties.GetValue<double>("Bottom"); }
        set { SetProps((GetPropId(nameof(Bottom)), value)); }
    }

    // 道路材质信息
    public MaterialInfo Material
    {
        get { return Properties.GetValue<MaterialInfo>("Material"); }
        set { SetProps((GetPropId(nameof(Material)), value)); }
    }

    // 道路轮廓线
    public Polyline2d Outline
    {
        get { return this.BaseCurve as Polyline2d; }
        set { this.BaseCurve = value; }
    }

    // 构造函数
    public QdRoad(QdRoadDef roadDef) : base(roadDef)
    {
        Type = LayoutElementType.Road;
        Outline = new Polyline2d();
    }

    // 计算包围盒
    public override Box2 GetBoundingBox()
    {
        return new Box2().ExpandByPoints(
            GetShapes()[0].Curve2ds
                .SelectMany(n => n.GetPoints()).ToArray());
    }

    // 克隆方法
    public override LcElement Clone()
    {
        var clone = new QdRoad(Definition as QdRoadDef);
        clone.Copy(this);
        return clone;
    }

    // 复制属性
    public override void Copy(LcElement src)
    {
        base.Copy(src);
        var road = (QdRoad)src;
    }
}

13.2.2 RoadAction操作类

RoadAction负责处理城市道路的用户交互操作:

public class RoadAction : DirectComponentAction
{
    private PointInputer pointInputer;
    private CmdTextInputer cmdTextInputer;
    private LcPolyLine OutLoop;

    public RoadAction() { }

    public RoadAction(IDocumentEditor docEditor) : base(docEditor)
    {
        commandCtrl.WriteInfo("命令:Road");
    }

    // 创建道路
    public async void ExecCreate(string[] args = null)
    {
        OutLoop = null;
        commandCtrl.WriteInfo("绘制道路轮廓中...");
        var doc = docRt.Document;

        var plAc = new PolyLineAction(docEditor);
        await plAc.StartCreating();
        if (!await plAc.OtherActionCreating())
        {
            goto End;
        }
        else
        {
            OutLoop = plAc.CurrentPoly;
        }
        CreateRoad();

    End:
        if (OutLoop != null)
        {
            vportRt.ActiveElementSet.RemoveElement(OutLoop);
        }
        pointInputer = null;
        cmdTextInputer = null;
        plAc.EndCreating();
        EndCreating();
    }

    // 创建道路元素
    public void CreateRoad()
    {
        var doc = docRt.Document;
        var roadDef = docRt.GetUseComDef(
            $"{NamespaceKey}.道路工程", "道路", null) as QdRoadDef;
        var road = new QdRoad(roadDef);
        road.Initilize(doc);
        var poly = OutLoop.Clone() as LcPolyLine;
        road.Outline = poly.Curve.Clone() as Polyline2d;
        road.ResetBoundingBox();
        road.Layer = GetLayer().Name;
        road.Bottom = 0;
        road.Material = MaterialManager.GetMaterial(MaterialManager.ConcreteUuid);
        vportRt.ActiveElementSet.InsertElement(road);
        docRt.Action.ClearSelects();
    }
}

13.2.3 道路命令注册

在LayoutCmds.cs中,道路命令的注册如下:

[CommandClass]
public class LayoutCmds
{
    [CommandMethod(Name = "Road", ShortCuts = "ROD")]
    public CommandResult DrawRoad(IDocumentEditor docEditor, string[] args)
    {
        var roadAction = new RoadAction(docEditor);
        roadAction.ExecCreate(args);
        return CommandResult.Succ();
    }
}

13.2.4 道路的中心线与宽度计算

城市道路的核心几何计算涉及中心线偏移和宽度处理:

// 道路中心线偏移计算原理
// 1. 获取道路中心线的各段线段
// 2. 对每段线段进行法向偏移,得到左右边界
// 3. 处理弯道处的交点计算
// 4. 组合形成完整的道路轮廓

// 偏移算法核心(与基坑放坡类似)
public static List<Curve2d> ShapeExtend(List<Curve2d> curves, double width)
{
    var newCurves = new List<Curve2d>();
    for (var i = 0; i < curves.Count; i++)
    {
        var curve = curves[i].Clone();
        if (curve is Line2d line)
        {
            // 对直线段进行法向偏移
            line.Translate(
                line.Dir.Clone()
                    .RotateAround(new Vector2(), -Math.PI / 2)
                    .MultiplyScalar(width));
            newCurves.Add(line);
        }
        else if (curve is Arc2d arc)
        {
            // 对圆弧段调整半径
            if (arc.IsClockwise)
                arc.Radius -= width;
            else
                arc.Radius += width;
            // 将圆弧离散为线段
            var count = Convert.ToInt32(
                Math.Abs((arc.EndAngle - arc.StartAngle) / Math.PI * 16));
            count = Math.Max(5, count);
            var ps = arc.GetPoints(count);
            for (var k = 0; k < count; k++)
            {
                newCurves.Add(new Line2d(ps[k].Clone(), ps[k + 1].Clone()));
            }
        }
    }

    // 处理偏移后线段的交点
    for (var i = 0; i < newCurves.Count; i++)
    {
        var lastCurve = newCurves[i == 0 ? newCurves.Count - 1 : i - 1];
        var nextCurve = newCurves[i == newCurves.Count - 1 ? 0 : i + 1];
        if (newCurves[i] is Line2d line)
        {
            if (lastCurve is Line2d lastLine)
            {
                var cps = Intersect2d.XLineWithXLine(
                    line.Start, line.Dir.Clone().Negate(),
                    lastLine.Start, lastLine.Dir);
                if (cps != null) line.Start = cps;
            }
            if (nextCurve is Line2d nextLine)
            {
                var cpe = Intersect2d.XLineWithXLine(
                    line.Start, line.Dir,
                    nextLine.Start, nextLine.Dir);
                if (cpe != null) line.End = cpe;
            }
        }
    }
    return newCurves;
}

13.3 出土道路(QdBerm)详解

13.3.1 QdBerm类定义

出土道路用于土方运输路线的设计,通常连接基坑与场外出口:

public class QdBerm : DirectComponent
{
    // 底部高程
    public double Bottom
    {
        get { return Properties.GetValue<double>("Bottom"); }
        set { SetProps((GetPropId(nameof(Bottom)), value)); }
    }

    // 材质信息
    public MaterialInfo Material
    {
        get { return Properties.GetValue<MaterialInfo>("Material"); }
        set { SetProps((GetPropId(nameof(Material)), value)); }
    }

    // 道路轮廓
    public Polyline2d Outline
    {
        get { return this.BaseCurve as Polyline2d; }
        set { this.BaseCurve = value; }
    }

    public QdBerm(QdBermDef bermDef) : base(bermDef)
    {
        Type = LayoutElementType.Berm;
        Outline = new Polyline2d();
    }

    public override Box2 GetBoundingBox()
    {
        return new Box2().ExpandByPoints(
            GetShapes()[0].Curve2ds
                .SelectMany(n => n.GetPoints()).ToArray());
    }

    public override LcElement Clone()
    {
        var clone = new QdBerm(Definition as QdBermDef);
        clone.Copy(this);
        return clone;
    }

    public override void Copy(LcElement src)
    {
        base.Copy(src);
        var berm = (QdBerm)src;
    }
}

13.3.2 BermAction操作类

public class BermAction : DirectComponentAction
{
    private PointInputer pointInputer;
    private CmdTextInputer cmdTextInputer;
    private LcPolyLine OutLoop;

    public BermAction() { }

    public BermAction(IDocumentEditor docEditor) : base(docEditor)
    {
        commandCtrl.WriteInfo("命令:Berm");
    }

    // 多边形绘制出土道路
    public async void ExecCreatePoly(string[] args = null)
    {
        OutLoop = null;
        commandCtrl.WriteInfo("绘制出土道路轮廓中...");
        var doc = docRt.Document;
        cmdTextInputer = new CmdTextInputer(docEditor);

        var plAc = new PolyLineAction(docEditor);
        await plAc.StartCreating();
        if (!await plAc.OtherActionCreating())
            goto End;
        else
            OutLoop = plAc.CurrentPoly;

        CreateBerm();

    End:
        if (OutLoop != null)
            vportRt.ActiveElementSet.RemoveElement(OutLoop);
        pointInputer = null;
        cmdTextInputer = null;
        plAc.EndCreating();
        EndCreating();
    }

    // 创建出土道路元素
    public void CreateBerm()
    {
        var doc = docRt.Document;
        var bermDef = docRt.GetUseComDef(
            $"{NamespaceKey}.道路工程", "出土道路", null) as QdBermDef;
        var berm = new QdBerm(bermDef);
        berm.Initilize(doc);
        var poly = OutLoop.Clone() as LcPolyLine;
        berm.Outline = poly.Curve.Clone() as Polyline2d;
        berm.ResetBoundingBox();
        berm.Layer = GetLayer().Name;
        berm.Bottom = 0;
        berm.Material = MaterialManager.GetMaterial(MaterialManager.ConcreteUuid);
        vportRt.ActiveElementSet.InsertElement(berm);
        docRt.Action.ClearSelects();
    }
}

13.3.3 QdBermProvider三维模型生成

出土道路的三维Provider负责生成带有坡面的三维模型:

internal static class QdBermProvider
{
    internal static void RegistProviders()
    {
        // 注册二维形状生成器
        ConvertToProviders(new List<(string uuid, string name, CreateShape creator)>
        {
            ("UUID-HERE", "出土道路", 出土道路)
        });
        // 注册三维实体生成器
        ConvertToProvider("UUID-HERE", nameof(GetSolid_出土道路),
            GetSolid_出土道路, GetSolidMats);
    }

    // 材质获取
    private static MaterialInfo[] GetSolidMats(
        LcComponentDefinition definition,
        LcParameterSet pset,
        SolidCreator creator,
        Solid3d solid)
    {
        return new MaterialInfo[] {
            pset.GetValue<MaterialInfo>("Material")
        };
    }

    // 二维形状生成
    internal static Curve2dGroupCollection 出土道路(
        LcParameterSet pset, ShapeCreator creator)
    {
        var curves = new List<Curve2d>();
        var com = creator.ComIns as DirectComponent;
        var outline = com.BaseCurve as Polyline2d;
        curves = outline.Curve2ds.Clone();
        var baseCurveGrp = new Curve2dGroup {
            Curve2ds = curves.ToListEx()
        };
        return new Curve2dGroupCollection { baseCurveGrp };
    }

    // 三维实体生成
    private static Solid3dCollection GetSolid_出土道路(
        LcComponentDefinition definition,
        LcParameterSet pset,
        SolidCreator creator)
    {
        var outline = pset.GetValue<Polyline2d>("Outline");
        var bottom = pset.GetValue<double>("Bottom");
        var platgeo = CreateBerm(outline);
        platgeo.translate(0, 0, bottom);
        return new Solid3dCollection()
        {
            new Solid3d()
            {
                Name = "Berm",
                Geometry = new LightCAD.MathLib.GeometryData()
                {
                    Verteics = platgeo.attributes.position.array,
                    Indics = platgeo.index.intArray,
                    Groups = new GeometryGroup[1]
                    {
                        new GeometryGroup
                        {
                            Name = "Geometry",
                            Start = 0,
                            Count = platgeo.index.intArray.Length,
                            MaterialIndex = 0
                        },
                    }
                }
            }
        };
    }

    // 创建出土道路几何体
    private static BufferGeometry CreateBerm(Polyline2d polyline)
    {
        var shape = new Shape(polyline.GetPoints().ToListEx());
        var coodMat = new Matrix4();
        coodMat.MakeBasis(
            new Vector3(1, 0, 0),
            new Vector3(0, 1, 0),
            new Vector3(0, 0, 1));
        return GeoModelUtil.GetStretchGeometryData(
            shape, coodMat, 0, -1).GetBufferGeometry();
    }
}

13.4 路面硬化(QdHarden)详解

13.4.1 QdHarden类定义

路面硬化用于施工场地的道路硬化处理:

public class QdHarden : DirectComponent
{
    public double Bottom
    {
        get { return Properties.GetValue<double>("Bottom"); }
        set { SetProps((GetPropId(nameof(Bottom)), value)); }
    }

    public MaterialInfo Material
    {
        get { return Properties.GetValue<MaterialInfo>("Material"); }
        set { SetProps((GetPropId(nameof(Material)), value)); }
    }

    public Polyline2d Outline
    {
        get { return this.BaseCurve as Polyline2d; }
        set { this.BaseCurve = value; }
    }

    public QdHarden(QdHardenDef hardenDef) : base(hardenDef)
    {
        Type = LayoutElementType.Harden;
        Outline = new Polyline2d();
    }

    public override Box2 GetBoundingBox()
    {
        return new Box2().ExpandByPoints(
            GetShapes()[0].Curve2ds
                .SelectMany(n => n.GetPoints()).ToArray());
    }

    public override LcElement Clone()
    {
        var clone = new QdHarden(Definition as QdHardenDef);
        clone.Copy(this);
        return clone;
    }
}

13.4.2 HardenAction操作类

HardenAction支持三种创建模式,是道路系统中最灵活的操作类之一:

public class HardenAction : DirectComponentAction
{
    private PointInputer pointInputer;
    private CmdTextInputer cmdTextInputer;
    private LcPolyLine OutLoop;

    public HardenAction() { }

    public HardenAction(IDocumentEditor docEditor) : base(docEditor)
    {
        commandCtrl.WriteInfo("命令:Harden");
    }

    // 多边形绘制路面硬化
    public async void ExecCreatePoly(string[] args = null)
    {
        OutLoop = null;
        commandCtrl.WriteInfo("绘制路面硬化轮廓中...");
        var plAc = new PolyLineAction(docEditor);
        await plAc.StartCreating();
        if (!await plAc.OtherActionCreating())
            goto End;
        else
            OutLoop = plAc.CurrentPoly;
        CreateHarden();
    End:
        if (OutLoop != null)
            vportRt.ActiveElementSet.RemoveElement(OutLoop);
        plAc.EndCreating();
        EndCreating();
    }

    // 矩形绘制路面硬化
    public async void ExecCreateRec(string[] args = null)
    {
        OutLoop = null;
        commandCtrl.WriteInfo("绘制路面硬化矩形区域中...");
        var plAc = new PolyLineAction(docEditor);
        await plAc.StartCreating();
        if (!await plAc.OtherActionCreatingRect())
            goto End;
        else
            OutLoop = plAc.CurrentPoly;
        CreateHarden();
    End:
        if (OutLoop != null)
            vportRt.ActiveElementSet.RemoveElement(OutLoop);
        plAc.EndCreating();
        EndCreating();
    }

    // 从已有线段转换为路面硬化
    public async void ExecCreate(string[] args = null)
    {
        var elementInputer = new ElementSetInputer(this.docEditor);
        var result = await elementInputer.Execute("请选择已有闭合线段创建路面硬化:");
        if (elementInputer.isCancelled || result == null)
        {
            this.Cancel();
            return;
        }
        if (result.ValueX != null)
        {
            var eles = result.ValueX as List<LcElement>;
            var lines = new List<LcCurve2d>();
            foreach (var ele in eles)
            {
                if (ele is LcLine line) lines.Add(line);
                else if (ele is LcPolyLine polyLine) lines.Add(polyLine);
                else if (ele is LcArc arc) lines.Add(arc);
            }
            var polys = LcCurveChangeLoop.CheckLoops(lines);
            foreach (var line in polys)
            {
                OutLoop = line;
                CreateHarden();
            }
        }
    }

    // 创建路面硬化元素
    public void CreateHarden()
    {
        var doc = docRt.Document;
        var hardenDef = docRt.GetUseComDef(
            $"{NamespaceKey}.道路工程", "路面硬化", null) as QdHardenDef;
        var harden = new QdHarden(hardenDef);
        harden.Initilize(doc);
        var poly = OutLoop.Clone() as LcPolyLine;
        harden.Outline = poly.Curve.Clone() as Polyline2d;
        harden.ResetBoundingBox();
        harden.Layer = GetLayer().Name;
        harden.Bottom = 0;
        harden.Material = MaterialManager.GetMaterial(
            MaterialManager.ConcreteUuid);
        vportRt.ActiveElementSet.InsertElement(harden);
        docRt.Action.ClearSelects();
    }

    // 获取或创建图层
    private LcLayer GetLayer()
    {
        var layer = docRt.Document.Layers
            .FirstOrDefault(n => n.Name == "Layout_Harden");
        if (layer == null)
        {
            layer = docRt.Document.CreateObject<LcLayer>();
            layer.Name = "Layout_Harden";
            layer.Color = 0x808080; // 灰色
            layer.SetLineType(new LcLineType("ByLayer"));
            layer.Transparency = 0;
            docRt.Document.Layers.Add(layer);
        }
        return layer;
    }
}

13.4.3 QdHardenProvider三维生成

internal static class QdHardenProvider
{
    internal static void RegistProviders()
    {
        ConvertToProviders(new List<(string uuid, string name, CreateShape creator)>
        {
            ("UUID", "路面硬化", 路面硬化)
        });
        ConvertToProvider("UUID", nameof(GetSolid_路面硬化),
            GetSolid_路面硬化, GetSolidMats);
    }

    private static MaterialInfo[] GetSolidMats(
        LcComponentDefinition definition,
        LcParameterSet pset,
        SolidCreator creator,
        Solid3d solid)
    {
        return new MaterialInfo[] {
            pset.GetValue<MaterialInfo>("Material")
        };
    }

    internal static Curve2dGroupCollection 路面硬化(
        LcParameterSet pset, ShapeCreator creator)
    {
        var com = creator.ComIns as DirectComponent;
        var outline = com.BaseCurve as Polyline2d;
        var curves = outline.Curve2ds.Clone();
        var baseCurveGrp = new Curve2dGroup {
            Curve2ds = curves.ToListEx()
        };
        return new Curve2dGroupCollection { baseCurveGrp };
    }

    private static Solid3dCollection GetSolid_路面硬化(
        LcComponentDefinition definition,
        LcParameterSet pset,
        SolidCreator creator)
    {
        var outline = pset.GetValue<Polyline2d>("Outline");
        var bottom = pset.GetValue<double>("Bottom");
        var shape = new Shape(outline.GetPoints().ToListEx());
        var coodMat = new Matrix4();
        coodMat.MakeBasis(
            new Vector3(1, 0, 0),
            new Vector3(0, 1, 0),
            new Vector3(0, 0, 1));
        var platgeo = GeoModelUtil.GetStretchGeometryData(
            shape, coodMat, 0, -1).GetBufferGeometry();
        platgeo.translate(0, 0, bottom);
        return new Solid3dCollection()
        {
            new Solid3d()
            {
                Name = "Harden",
                Geometry = new GeometryData()
                {
                    Verteics = platgeo.attributes.position.array,
                    Indics = platgeo.index.intArray,
                    Groups = new GeometryGroup[1]
                    {
                        new GeometryGroup
                        {
                            Name = "Geometry",
                            Start = 0,
                            Count = platgeo.index.intArray.Length,
                            MaterialIndex = 0
                        }
                    }
                }
            }
        };
    }
}

13.5 硬化地面(QdGround)详解

13.5.1 QdGround类定义

硬化地面与路面硬化类似,但侧重于场地整体的硬化区域设计:

public class QdGround : DirectComponent
{
    public double Bottom
    {
        get { return Properties.GetValue<double>("Bottom"); }
        set { SetProps((GetPropId(nameof(Bottom)), value)); }
    }

    public MaterialInfo Material
    {
        get { return Properties.GetValue<MaterialInfo>("Material"); }
        set { SetProps((GetPropId(nameof(Material)), value)); }
    }

    public Polyline2d Outline
    {
        get { return this.BaseCurve as Polyline2d; }
        set { this.BaseCurve = value; }
    }

    public QdGround(QdGroundDef groundDef) : base(groundDef)
    {
        Type = LayoutElementType.Ground;
        Outline = new Polyline2d();
    }

    public override Box2 GetBoundingBox()
    {
        return new Box2().ExpandByPoints(
            GetShapes()[0].Curve2ds
                .SelectMany(n => n.GetPoints()).ToArray());
    }

    public override LcElement Clone()
    {
        var clone = new QdGround(Definition as QdGroundDef);
        clone.Copy(this);
        return clone;
    }
}

13.5.2 GroundAction操作类

GroundAction同样支持三种创建模式(多边形、矩形、转换),其命令注册如下:

// LayoutCmds.cs 中的命令注册
[CommandMethod(Name = "Ground", ShortCuts = "GOD")]
public CommandResult DrawGround(IDocumentEditor docEditor, string[] args)
{
    var groundAction = new GroundAction(docEditor);
    groundAction.ExecCreatePoly(args);
    return CommandResult.Succ();
}

[CommandMethod(Name = "GroundRec", ShortCuts = "GDRC")]
public CommandResult DrawGroundRec(IDocumentEditor docEditor, string[] args)
{
    var groundAction = new GroundAction(docEditor);
    groundAction.ExecCreateRec(args);
    return CommandResult.Succ();
}

[CommandMethod(Name = "GroundChange", ShortCuts = "GODCH")]
public CommandResult DrawGroundChange(IDocumentEditor docEditor, string[] args)
{
    var groundAction = new GroundAction(docEditor);
    groundAction.ExecCreate(args);
    return CommandResult.Succ();
}

13.5.3 QdGroundProvider

QdGroundProvider在QdLayoutProviderRegist.cs中被注册:

public (ShapeProviderCollection, SolidProviderCollection) GetImportProviders()
{
    // ... 其他Provider注册 ...
    QdGroundProvider.RegistProviders();
    // ...
    return (ShapeProviders, SolidProviders);
}

13.6 交叉口处理系统

13.6.1 QdIntersectionProvider概述

道路交叉口是道路系统中最具挑战性的部分。QdIntersectionProvider负责处理多条道路交汇时的几何计算和三维模型生成。

internal static class QdIntersectionProvider
{
    internal static void RegistProviders()
    {
        // 注册交叉口的形状和实体生成器
        ConvertToProviders(new List<(string uuid, string name, CreateShape creator)>
        {
            ("UUID", "交叉口", 交叉口)
        });
        ConvertToProvider("UUID", nameof(GetSolid_交叉口),
            GetSolid_交叉口, GetSolidMats);
    }
}

13.6.2 交叉口几何计算原理

交叉口处理的核心算法步骤:

  1. 检测交叉:判断哪些道路在几何上相交
  2. 计算交点:使用Intersect2d工具类计算精确交点
  3. 裁剪边界:在交叉区域裁剪各条道路的边界线
  4. 生成过渡:在交叉口区域生成平滑的过渡面
  5. 材质分配:为交叉口区域分配适当的材质
// 交叉口的核心几何计算
// 使用Intersect2d提供的各种相交计算方法:
// - XLineWithXLine: 延长线与延长线的交点
// - CircleWithXLine: 圆与延长线的交点
// - CircleWithCircle: 圆与圆的交点
// - IsPolygonWithLine: 多边形与线段的相交判断
// - IsPolygonWithArc: 多边形与圆弧的相交判断

13.6.3 交叉口三维模型

交叉口的三维模型生成需要考虑:

  • 各路段的高程差异
  • 交叉口区域的平滑过渡
  • 转弯半径的圆角处理
  • 路面标线和标识

13.7 曲线闭合工具(LcCurveChangeLoop)

13.7.1 LcCurveChangeLoop的作用

在道路系统中,LcCurveChangeLoop类负责将用户选择的多条线段自动检测并组合为闭合环路。这是”转换”创建模式(如HardenChange、GroundChange)的核心工具。

public class LcCurveChangeLoop
{
    public static List<LcPolyLine> CheckLoops(List<LcCurve2d> eles)
    {
        var polys = new List<LcPolyLine>();
        for (var i = 0; i < eles.Count; i++)
        {
            var poly = new Polyline2d();
            poly.Curve2ds = new List<Curve2d>();
            var line = eles[i];
            ChangeLineToPolyLine(poly, line);

            // 处理圆形
            if (eles[i] is LcCircle)
            {
                poly.IsClosed = true;
                polys.Add(new LcPolyLine() {
                    Curve2ds = poly.Curve2ds.ToLcListCurve(),
                    Curve = poly
                });
                continue;
            }

            // 检查是否已闭合
            if (poly.Curve2ds.First().GetPoints(1).First()
                .Similarity(poly.Curve2ds.Last().GetPoints(1).Last(), 0))
            {
                poly.IsClosed = true;
                polys.Add(new LcPolyLine() {
                    Curve2ds = poly.Curve2ds.ToLcListCurve(),
                    Curve = poly
                });
                continue;
            }

            // 尝试从剩余线段中找到可以连接的线段
            var flag = false;
            for (var k = eles.Count - 1; k > i; k--)
            {
                for (var j = eles.Count - 1; j > i; j--)
                {
                    var ele = eles[j];
                    if (ChangeLineToPolyLine(poly, ele))
                    {
                        eles.Remove(ele);
                        break;
                    }
                }
                if (poly.Curve2ds.Count > 1 &&
                    poly.Curve2ds.First().GetPoints(1).First()
                        .Similarity(poly.Curve2ds.Last().GetPoints(1).Last(), 0))
                {
                    flag = true;
                    break;
                }
            }

            if (flag)
            {
                poly.IsClosed = true;
                polys.Add(new LcPolyLine() {
                    Curve2ds = poly.Curve2ds.ToLcListCurve(),
                    Curve = poly
                });
            }
        }

        // 初始化所有闭合环路
        foreach (var poly in polys)
        {
            poly.Initilize(eles.First().Document);
        }
        return polys;
    }
}

13.7.2 线段拼接算法

ChangeLineToPolyLine方法负责将各种类型的线段拼接到多段线中:

private static bool ChangeLineToPolyLine(Polyline2d lcPoly, LcCurve2d element)
{
    if (element.Type == BuiltinElementType.Line)
    {
        var line = element as LcLine;
        if (lcPoly.Curve2ds.Count == 0)
        {
            // 第一条线段直接添加
            lcPoly.Curve2ds.Add(new Line2d(line.Start.Clone(), line.End.Clone()));
            return true;
        }
        else
        {
            var lastP = lcPoly.Curve2ds.Last().GetPoints(1).Last();
            if (lastP.Similarity(line.Start, 0))
            {
                // 终点连接起点,正向添加
                lcPoly.Curve2ds.Add(line.Curve);
                return true;
            }
            else if (lastP.Similarity(line.End, 0))
            {
                // 终点连接终点,反向添加
                line.Curve.Reverse();
                lcPoly.Curve2ds.Add(line.Curve);
                return true;
            }
        }
    }
    else if (element.Type == BuiltinElementType.PloyLine)
    {
        // 多段线拼接逻辑
        var poly = element as LcPolyLine;
        // ... 类似的起点终点匹配逻辑
    }
    else if (element.Type == BuiltinElementType.Arc)
    {
        // 圆弧拼接逻辑
        var arc = element as LcArc;
        // ... 类似的起点终点匹配逻辑
    }
    else if (element.Type == BuiltinElementType.Circle)
    {
        // 圆形特殊处理:拆分为两段半圆弧
        var circle = element as LcCircle;
        if (lcPoly.Curve2ds.Count == 0)
        {
            var arc1 = new Arc2d();
            arc1.StartAngle = 0;
            arc1.EndAngle = Math.PI;
            arc1.Center = circle.Center.Clone();
            arc1.Radius = circle.Radius;
            // ... 设置端点
            var arc2 = new Arc2d();
            arc2.StartAngle = Math.PI;
            arc2.EndAngle = Math.PI * 2;
            // ... 设置端点
            lcPoly.Curve2ds.Add(arc1);
            lcPoly.Curve2ds.Add(arc2);
            return true;
        }
    }
    return false;
}

13.8 道路图层管理

13.8.1 图层命名规范

FY_Layout为每种道路类型定义了专用的图层:

道路类型 图层名称 颜色 说明
城市道路 Layout_Road 灰色(0x808080) 主要交通道路
出土道路 Layout_Berm 棕色(0x8B4513) 土方运输道路
路面硬化 Layout_Harden 深灰(0x696969) 硬化路面区域
硬化地面 Layout_Ground 浅灰(0xA9A9A9) 场地硬化区域

13.8.2 图层创建模式

所有道路Action类都遵循相同的图层创建模式:

private LcLayer GetLayer()
{
    // 查找已有图层
    var layer = docRt.Document.Layers
        .FirstOrDefault(n => n.Name == "Layout_Road");
    if (layer == null)
    {
        // 图层不存在时自动创建
        layer = docRt.Document.CreateObject<LcLayer>();
        layer.Name = "Layout_Road";
        layer.Color = 0x808080;
        layer.SetLineType(new LcLineType("ByLayer"));
        layer.Transparency = 0;
        docRt.Document.Layers.Add(layer);
    }
    return layer;
}

13.9 道路系统完整命令一览

13.9.1 命令汇总表

命令名 快捷键 功能说明 Action类 创建方法
Road ROD 城市道路绘制 RoadAction ExecCreate
Berm BRM 出土道路绘制 BermAction ExecCreatePoly
Harden HDR 路面硬化(多边形) HardenAction ExecCreatePoly
HardenRec HDRC 路面硬化(矩形) HardenAction ExecCreateRec
HardenChange HDCH 路面硬化(转换) HardenAction ExecCreate
Ground GOD 硬化地面(多边形) GroundAction ExecCreatePoly
GroundRec GDRC 硬化地面(矩形) GroundAction ExecCreateRec
GroundChange GODCH 硬化地面(转换) GroundAction ExecCreate

13.9.2 命令注册源码

完整的道路命令注册代码(摘自LayoutCmds.cs):

[CommandClass]
public class LayoutCmds
{
    // 城市道路
    [CommandMethod(Name = "Road", ShortCuts = "ROD")]
    public CommandResult DrawRoad(IDocumentEditor docEditor, string[] args)
    {
        var roadAction = new RoadAction(docEditor);
        roadAction.ExecCreate(args);
        return CommandResult.Succ();
    }

    // 出土道路
    [CommandMethod(Name = "Berm", ShortCuts = "BRM")]
    public CommandResult DrawBerm(IDocumentEditor docEditor, string[] args)
    {
        var bermAction = new BermAction(docEditor);
        bermAction.ExecCreatePoly(args);
        return CommandResult.Succ();
    }

    // 路面硬化
    [CommandMethod(Name = "Harden", ShortCuts = "HDR")]
    public CommandResult DrawHarden(IDocumentEditor docEditor, string[] args)
    {
        var hardenAction = new HardenAction(docEditor);
        hardenAction.ExecCreatePoly(args);
        return CommandResult.Succ();
    }

    [CommandMethod(Name = "HardenRec", ShortCuts = "HDRC")]
    public CommandResult DrawHardenRec(IDocumentEditor docEditor, string[] args)
    {
        var hardenAction = new HardenAction(docEditor);
        hardenAction.ExecCreateRec(args);
        return CommandResult.Succ();
    }

    [CommandMethod(Name = "HardenChange", ShortCuts = "HDCH")]
    public CommandResult DrawHardenChange(IDocumentEditor docEditor, string[] args)
    {
        var hardenAction = new HardenAction(docEditor);
        hardenAction.ExecCreate(args);
        return CommandResult.Succ();
    }

    // 硬化地面
    [CommandMethod(Name = "Ground", ShortCuts = "GOD")]
    public CommandResult DrawGround(IDocumentEditor docEditor, string[] args)
    {
        var groundAction = new GroundAction(docEditor);
        groundAction.ExecCreatePoly(args);
        return CommandResult.Succ();
    }

    [CommandMethod(Name = "GroundRec", ShortCuts = "GDRC")]
    public CommandResult DrawGroundRec(IDocumentEditor docEditor, string[] args)
    {
        var groundAction = new GroundAction(docEditor);
        groundAction.ExecCreateRec(args);
        return CommandResult.Succ();
    }

    [CommandMethod(Name = "GroundChange", ShortCuts = "GODCH")]
    public CommandResult DrawGroundChange(IDocumentEditor docEditor, string[] args)
    {
        var groundAction = new GroundAction(docEditor);
        groundAction.ExecCreate(args);
        return CommandResult.Succ();
    }
}

13.10 扩展开发:自定义道路类型

13.10.1 开发步骤概述

创建自定义道路类型需要以下步骤:

  1. 定义元素类(继承DirectComponent)
  2. 定义元素定义类(继承LcComponentDefinition)
  3. 实现操作类(继承DirectComponentAction)
  4. 实现三维操作类(实现IElement3dAction)
  5. 创建Provider(Shape和Solid)
  6. 注册元素类型和命令

13.10.2 示例:创建人行道元素

// 1. 定义元素类型
public static ElementType Sidewalk = new ElementType
{
    Guid = Guid.ParseExact("{YOUR-GUID-HERE}", "B").ToLcGuid(),
    Name = "Sidewalk",
    DispalyName = "人行道",
    ClassType = typeof(QdSidewalk)
};

// 2. 定义元素类
public class QdSidewalk : DirectComponent
{
    public double Bottom
    {
        get { return Properties.GetValue<double>("Bottom"); }
        set { SetProps((GetPropId(nameof(Bottom)), value)); }
    }

    public double Width
    {
        get { return Properties.GetValue<double>("Width"); }
        set { SetProps((GetPropId(nameof(Width)), value)); }
    }

    public MaterialInfo Material
    {
        get { return Properties.GetValue<MaterialInfo>("Material"); }
        set { SetProps((GetPropId(nameof(Material)), value)); }
    }

    public Polyline2d Outline
    {
        get { return this.BaseCurve as Polyline2d; }
        set { this.BaseCurve = value; }
    }

    public QdSidewalk(QdSidewalkDef def) : base(def)
    {
        Type = CustomElementType.Sidewalk;
        Outline = new Polyline2d();
    }

    public override Box2 GetBoundingBox()
    {
        return new Box2().ExpandByPoints(
            GetShapes()[0].Curve2ds
                .SelectMany(n => n.GetPoints()).ToArray());
    }

    public override LcElement Clone()
    {
        var clone = new QdSidewalk(Definition as QdSidewalkDef);
        clone.Copy(this);
        return clone;
    }
}

// 3. 实现操作类
public class SidewalkAction : DirectComponentAction
{
    private LcPolyLine OutLoop;

    public SidewalkAction() { }
    public SidewalkAction(IDocumentEditor docEditor) : base(docEditor)
    {
        commandCtrl.WriteInfo("命令:Sidewalk");
    }

    public async void ExecCreatePoly(string[] args = null)
    {
        OutLoop = null;
        commandCtrl.WriteInfo("绘制人行道轮廓中...");
        var plAc = new PolyLineAction(docEditor);
        await plAc.StartCreating();
        if (!await plAc.OtherActionCreating())
            goto End;
        else
            OutLoop = plAc.CurrentPoly;
        CreateSidewalk();
    End:
        if (OutLoop != null)
            vportRt.ActiveElementSet.RemoveElement(OutLoop);
        plAc.EndCreating();
        EndCreating();
    }

    public void CreateSidewalk()
    {
        var doc = docRt.Document;
        var def = docRt.GetUseComDef(
            $"{NamespaceKey}.道路工程", "人行道", null) as QdSidewalkDef;
        var sidewalk = new QdSidewalk(def);
        sidewalk.Initilize(doc);
        var poly = OutLoop.Clone() as LcPolyLine;
        sidewalk.Outline = poly.Curve.Clone() as Polyline2d;
        sidewalk.ResetBoundingBox();
        sidewalk.Layer = GetLayer().Name;
        sidewalk.Bottom = 0;
        sidewalk.Width = 2000; // 默认2m宽
        sidewalk.Material = MaterialManager.GetMaterial(
            MaterialManager.ConcreteUuid);
        vportRt.ActiveElementSet.InsertElement(sidewalk);
        docRt.Action.ClearSelects();
    }

    private LcLayer GetLayer()
    {
        var layer = docRt.Document.Layers
            .FirstOrDefault(n => n.Name == "Layout_Sidewalk");
        if (layer == null)
        {
            layer = docRt.Document.CreateObject<LcLayer>();
            layer.Name = "Layout_Sidewalk";
            layer.Color = 0xC0C0C0;
            layer.SetLineType(new LcLineType("ByLayer"));
            layer.Transparency = 0;
            docRt.Document.Layers.Add(layer);
        }
        return layer;
    }
}

// 4. 注册命令
[CommandMethod(Name = "Sidewalk", ShortCuts = "SW")]
public CommandResult DrawSidewalk(IDocumentEditor docEditor, string[] args)
{
    var action = new SidewalkAction(docEditor);
    action.ExecCreatePoly(args);
    return CommandResult.Succ();
}

13.10.3 注册到插件系统

在LayoutPlugin.cs的Loaded方法中注册:

public void Loaded()
{
    // 注册元素类型
    LcDocument.RegistElementTypes(new ElementType[] {
        CustomElementType.Sidewalk
    });

    // 注册2D操作
    LcDocument.ElementActions.Add(
        CustomElementType.Sidewalk, new SidewalkAction());

    // 注册3D操作
    LcDocument.Element3dActions.Add(
        CustomElementType.Sidewalk, new Sidewalk3dAction());
}

13.11 本章小结

本章详细介绍了FY_Layout道路与交叉口系统的设计与实现:

  1. 四种道路元素:Road(城市道路)、Berm(出土道路)、Harden(路面硬化)、Ground(硬化地面),各有不同的应用场景和设计特点
  2. 统一的开发模式:所有道路元素都遵循Element → Action → Provider的三层架构
  3. 多种创建方式:多边形绘制、矩形绘制、已有线段转换,满足不同设计场景的需求
  4. 交叉口处理:QdIntersectionProvider专门处理道路交叉口的复杂几何计算
  5. 曲线闭合工具:LcCurveChangeLoop实现了智能的线段自动拼接和闭合检测
  6. 图层管理:每种道路类型都有专用图层,支持分层管理和显示控制
  7. 扩展性:通过遵循现有的开发模式,可以方便地创建自定义道路类型

道路系统是场布设计中最常用的功能之一,掌握其设计模式和实现细节,对于理解和扩展FY_Layout具有重要意义。


上一章:二次开发实战案例 下一章:设备与模板排布系统