FreeMicaps开发讲解二: 图层

FreeMicaps开发讲解二: 图层

 

       上一讲FreeMicaps的地图框架进行了介绍,未涉及到具体数据和绘制,这一讲将对数据读取、地图渲染做讲解。

FreeMicaps为插件式架构,用户可按它的插件接口编写代码对所支持的数据类型进行扩展。数据类型扩展代码最主要的就是是针对新增数据编写相应的图层类,所以本节偏重于图层类的具体实现,为插件开发做准备。

       再次对地图框架进行总结,N个地图元素->图层,N个图层->天气图,天气图+UI->地图视图。结构图概括如下:

 

 

       MapView是从PaintBox继承下来的一个类,利用IMapTool处理其鼠标和键盘操作;当它进行绘制时,调用WeatherMapRender()方法,WeatherMapRender遍历图层调调用图层的Render方法,各图层又遍历它们的地图元素调用地图元素的Render方法,最终组合形成成一张地图。WeatherMapRender()方法如下:

        /// <summary>

        /// 绘制所有图层

        /// </summary>

        /// <param name="graphics"></param>       

        public void Render(Graphics graphics)

        {

            for (int i = 0; i < Layers.Count; i++)

            {

                CustomLayer layer = Layers[i];

                if (_StopRending)

                {

                    break;

                }

                layer.Render(graphics);

             }

}

可以看出,地图绘制,就演变为图层的绘制,图层的绘制,又归结到了地图元素的绘制。对于本系统所需的天气图绘制来说,地图元素不光是地图的点、线、面、标注、栅格,还包括各类形式各异的符号、图形等,根据不同数据类型有不同的表现。所以地图的绘制就从图层讲起。

一、图层的基类-CustomLayer

       各种图层虽然形式多样,但其核心只是数据加载和数据渲染过程不同,而数据加载还可以归为数据渲染里,数据可以在数据渲染过程中根据需要加载。所以,图层抽象类的核心方法只有一个,即Render(Graphics graphics)方法,在WeatherMap的地图渲染中,也只是调用了图层的Render()方法。

虽说核心如此,但图层的抽象类不能这么简单,还是需要增加一些属性,如数据源字符串、是否可视、图层名称等,另外,还增加了一个LayerStyle的属性,此属性里包含了图层的绘制颜色、字体等参数,便于从外部控制图层的渲染参数。最终形成如下抽象类CustomLayer

 

CustomLayerRender()方法为虚方法,可以在子类中进行重写以实现具体图层的具体绘制过程。你可能还会注意到,还有两个Protected的抽象方法DoLoad()DoRender(),这两又是干什么的呢?刚才我们说了,数据可在图层渲染过程按需进行加载,在CustomLayerRender方法里,首先判断了数据是否数据是否已加载,如果没加载则调用抽象方法DoLoad(),这个过程可让数据只在第一次渲染时加载,子类不用管何时该加载,只需分别实现数据加载方法DoLoad()和数据渲染方法DoRender()就行了。使用这种方法的好处是对于小型地图可以加快地图渲染速度,但对大型地图,如一张地图几十上百兆就不适合了,解决办法是自己实现Render()方法控制数据加载。

CustomLayerRender方法如下:

/// <summary>

        /// 渲染图层

        /// </summary>

        /// <param name="graphics"></param>       

        public virtual void Render(Graphics graphics)

        {

            //读入数据

            if (!IsLoaded)

            {

                _Loaded = true;

                DoLoad();

            }

            //绘图

            DoRender(graphics);

    }

二、图层子类简单实现

       第一节说过,对每一种数据类型,都对应一个CustomLayer的子类,所以要实现某种图层,可以从CustomLayer(或CustomLayer的资料)继承一个一个类,CustomLayer已为图层的数据加载和渲染搭好框架,在子类中仅根据需要实现DoLoad()DoRender()方法即可。

       要说完全所有图层都直接从CustomLayer继承,显然是不合适的,如Shp格式地图、Micaps格式地图、Bln格式地图,它们的渲染过程是一样的,只是读数据过程不同,所以FreeMicaps内核还提供了MapLayer(矢量地图图层基类)、ImageLayer(栅格地图基类)、HighLayer(高空填图基类)、SurfaceLayer(地面填图基类)、GridLayer(格点数据基类)等,这些基类继承CustomLayer,已实现DoRender()方法,开发新图层只需根据图层种类继承上面的图层实现DoLoad()方法即可。

       举例来说,MapLayer类如下所示:

       /// <summary>

    /// 地图图层抽象基类(矢量图层,点线面)

    /// </summary>

    public abstract class MapLayer : CustomLayer

    {

        /// <summary>

        /// 构造函数

        /// </summary>

        /// <param name="source">数据源字符串</param>

        public MapLayer(string source)

            : base(source)

        {

            LayerStyle = new MapStyle();

        }

        /// <summary>

        /// 地图元素

        /// </summary>

        protected IList<MapModel> MapObjects = new List<MapModel>();

        /// <summary>

        /// 载入数据抽象方法(DoLoad方法调用)

        /// </summary>

        protected abstract void LoadData();

        /// <summary>

        /// 载入数据

        /// </summary>

        protected override void DoLoad()

        {

            MapObjects.Clear();

            LoadData();

            Process();

        }

        private void Process()

        {

            //投影变换

            foreach (MapModel mo in MapObjects)

            {

                for (int i = 0; i < mo.Points.Length; i++)

                {

                    mo.Points[i] = Map.WorldToProj(mo.Points[i]);

                }

            }

        }

        /// <summary>

        /// 渲染图层

        /// </summary>

        /// <param name="graphics"></param>

        protected override void DoRender(Graphics graphics)

        {

            //MapStyle style = LayerStyle as MapStyle;

            MapRender itemRender = new MapRender(this, LayerStyle);

            MapStyle ms = LayerStyle as MapStyle;

            if (ms != null) itemRender.IsMask = ms.IsMask;

            foreach (MapModel mo in MapObjects)

            {

                itemRender.DrawObj = mo;

                itemRender.Render(graphics);

            }

        }

}

MapLayer图层类中,为了再次加快图层渲染速度,在读取数据后即进行地图元素坐标投影转换,避免在每次地图渲染中都进行计算。为了使流程更清楚,又增加了抽象方法LoadData()方法,继承MapLayer的图层实现此方法来读取数据。

为了说明如何增加一种新地图格式支持,我花半小时写了一个BLN格式(surfer格式地图)图层类,代码如下:

    /// <summary>

    /// bln格式图层(地图)

    /// </summary>

    public class BlnLayer : MapLayer, IEdit

    {

        /// <summary>

        ///构造方法

        /// </summary>

        /// <param name="source"></param>

        public BlnLayer(string source)

            : base(source)

        {

        }

 

        /// <summary>

        /// 重载读取数据抽象方法

        /// </summary>

        protected override void LoadData()

        {

            //读取数据

            ReadFile(DataSource);

            //如果图层名为noname,则将文件名作为图层名

            if (LayerName.ToLower() == "noname") LayerName = Path.GetFileName(DataSource);

        }

 

        /// <summary>

        /// 读取数据

        /// </summary>

        /// <param name="fn">文件名</param>

        private void ReadFile(string fn)

        {

            using (FileStream fs = new FileStream(fn, FileMode.Open, FileAccess.Read, FileShare.Read))

            {

                using (StreamReader ws = new StreamReader(fs, Encoding.Default))

                {

                    while (!ws.EndOfStream)

                    {

                        //数据行

                        string line = "";

                        //读取地图元素头,并跳过空行

                        do

                        {

                            line = ws.ReadLine();

                        }

                        while (string.IsNullOrEmpty(line));

                        //分隔数据行

                        string[] items = line.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

                        //点数

                        int n = Convert.ToInt16(items[0]);

                        //地图元素

                        MapModel mo = new MapModel();

                        //地图元素头为两个

                        if (items.Length > 1)

                        {

                            int t = Convert.ToInt16(items[1]);

                            if (t == 1)

                            {

                                //线

                                mo.ObjectType = ShapeType.PolyLine;

                            }

                            else

                            {

                                //

                                mo.ObjectType = ShapeType.Polygon;

                            }

                        }

                        else

                        {

                            //线

                            mo.ObjectType = ShapeType.PolyLine;

                        }

                        //地图元素点数

                        mo.Points = new System.Drawing.PointF[n];

                        //读取经纬度

                        for (int i = 0; i < n; i++)

                        {

                            PointF ptf = new PointF();

                            line = "";

                            //读入一行,并跳过空行

                            do

                            {

                                line = ws.ReadLine();

                            }

                            while (string.IsNullOrEmpty(line));

                            //分隔数据行

                            items = line.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

                            //有两项时

                            if (items.Length == 2)

                            {

                                //经纬度

                                ptf.X = Convert.ToSingle(items[0]);

                                ptf.Y = Convert.ToSingle(items[1]);

                                mo.Points[i] = ptf;

                            }

                        }

                        //加入到列表

                        MapObjects.Add(mo);

                    }

                }

                fs.Close();

            }

        }

}

通过注释应该很容易看懂。代码主要在读数据上面,至于如何绘图的,不用管它,基类已经实现了。Bln格式地图效果如下:

 

三、完全控制绘图

       上节看来,实现一种数据格式图层似乎很简单,选一个图层基类,重载它的读取数据方法就可以了。但是,FreeMicaps提供的图层基类有限,普通的地图、高空地面填图等有基类可继承,如果你想支持一种FreeMicaps没涉及到渲染方式,就没这么简单了。这就需要从CustomLayer图层继承,完全实现数据的读取和绘制。

       前面说了,CustomLayer有两个抽象方法DoLoad()DoRender(),子类实现它们以完成数据读取和渲染。读取数据不用说了,上节的例子已很清楚。绘图方法原型为:

/// <summary>

        /// 渲染图层

        /// </summary>

        /// <param name="graphics">Graphics对象</param>

        protected abstract void DoRender(Graphics graphics);

     函数已传入Graphics对象,可以随心所欲在graphics上画线、画圆、输出字符串、贴图,但对于在地图上绘制,更多的是需要在指定地理坐标(经纬度)上绘制,Graphics对象绘图时传入的是屏幕坐标,这就需要把地理坐标转换为屏幕坐标。复习一下上一章,一开始就说的是坐标转换。CustomLayer有一个WeatherMap类的实例Map,利用它即可完成地理坐标到屏幕坐标的转换。

       为了说明绘制,做个试验,在前面BLN格式图层上增加代码实现在指定经纬度画一个圆。给图层增加方法DoRender(),注意,DoRender()是虚方法,重载时要加override

protected override void DoRender(Graphics graphics)

        {

            //基类绘图

            base.DoRender(graphics);

            //经纬度(110,35)

            PointF ptf = new PointF(110, 35);

            //转换为屏幕坐标

            ptf = Map.WorldToScreen(ptf);

            //画个半径为100的圆

            int d = 100;

            graphics.DrawEllipse(Pens.Red, new RectangleF(ptf.X - d, ptf.Y - d, d*2, d*2));

        }

       运行后效果如下:

 

 

       虽然只是画了一个圈,但足能说明绘制思路,换成标注、图片已不是问题。还可以利用系统提供的符号绘制类画出风、天气现象、云等符号。

看到这里,写一个你需要的图层应该不是难事了。从CustomLayer继承一个子类,实现DoLoad()DoRender()方法,DoLoad()里写读取数据代码,DoRender()里写绘图代码,绘制时使用Map对象进行经纬度到屏幕坐标转换。

 

图层开发基本方法就到这里,后面还将进一步继续讲解图层开发的高级功能。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值