一步一步手写GIS开源项目-1500行代码实现基础GIS展示功能

1.开篇

       大学毕业工作已经两年了,上学那会就很想研读一份开源GIS的源码,苦于自己知识和理解有限,而市面上也没有什么由浅入深讲解开源gis原理的书籍,大多都是开源项目简介以及项目的简单应用。对于初级程序员想读懂一个成熟的GIS开源项目的困难点主要有三点,1.开发经验和gis原理理解不足。2.一个开源项目是一个循序渐进的过程,如果不是从项目很小的时候跟进,等项目持续更新几年后逻辑就会变得很复杂,小白很难通过Dbug调试来理清楚整个项目的原理。3.GIS属于小行业,国内基本没有国人主导的开源GIS项目,这方面的文章书籍比较欠缺。所以我的想法是通过重写一个GIS项目,还原到项目的初始状态,由浅入深人讲解GIS原理,对于GIS开发从业者不仅要知其然,更要知其所以然。了解GIS底层的开发技术提升自己的竞争力。

     我的文章思路,代码上传到我的github上面,github地址:https://github.com/HuHongYong/ATtuingMap,欢迎大家star 一下,每一篇文章对应的代码生成released版本,方便后期找到文章对应的版本版本代码,如下:

image

2.开源gis项目的选择

      本次开源项目的选择是SharpMap,选择这个项目的原因主要有两点,1.SharpMap是一套简单易用的小型GIS平台,核心代码1万多行,可以用于开发桌面GIS应用和简单的BS程序。支持多种GIS数据格式,支持空间查询,可输出精美的地图。可了解GIS核心原理。2.开发语言.net,本人工作中使用的核心开发语言,而且现在.net core 3.0开始支持winform、wpf等windows桌面开发技术的跨平台应用的开发,当然这个讲原理就不使用.net core 了,等.net core桌面开发稳定以后可以做一下跨平台。

3.开源GIS最简单实现

     本节500行编写了GIS基本框架,开发环境是vs2017,使用.net framework4.5 ,实现了读取shp点数据,并实现点数据的渲染,以及屏幕坐标向空间坐标转换的基础GIS功能。项目的核心结构如下:

微信截图_20190502095532

1.Map  只包含一个Map类,是该GIS项目的核心类。

2.Geometries 所有点、线、面等几何类的定义和几何类的方法,这些类都继承了抽象类Geometry,有利于与几何类的扩展,移植,复用。

3.Layers 地图的图层类,该类的核心成员就是下面的4、5、6,构成了一个图层对象的整体。

4.Rendering 提供了用于绘制空间数据的功能,使用c#System.Drawing.Graphics进行渲染绘制。

5.Styles  提供图层的样式,例如点的大小、颜色等。

6.Data 数据的读取接口。例如shapefile文件的读取。

7.Utilities 工具类,一些公用的方法。

4.shp点文件的解析与读取 

       shape文件由ESRI开发,一个ESRI(Environmental Systems Research Institute)的shape文件包括一个主文件,一个索引文件,和一个dBASE表。其中主文件的后缀就是.shp。

      .shp文件包含文件头,数据记录两部分。文件头是固定的100个字节组成,结构如下,数据记录包含着坐标记录信息是文件的数据核心。

未命名文件

       .shp文件头解析,使用的是c# BinaryReader读取字节流,brShapeFile.BaseStream.Seek(36, 0)这个方法是指定位到第36个字节,接着brShapeFile.ReadInt32()是指从第37个字节开始读4个字节。

/// <summary>
/// 读取和解析.shp文件的文件头
/// </summary>
private void ParseHeader (){
     fsShapeFile = new FileStream(_Filename, FileMode.Open, FileAccess.Read);
     brShapeFile = new BinaryReader(fsShapeFile, Encoding.Unicode);
     brShapeFile.BaseStream.Seek(0, 0);
     //读取四个字节,检查文件头
     if (brShapeFile.ReadInt32() != 170328064)
     {
         //文件真实的编码是9994,
         //170328064的16进制为0x0a27,交换字节顺序后就是0x270a,十进制就是9994了
         throw (new ApplicationException("无效的Shapefile文件 (.shp)"));
     }
     //五个没有被使用的int32整数
     brShapeFile.BaseStream.Seek(24, 0);
     //获取文件长度,包括文件头
     _FileSize = 2 * SwapByteOrder(brShapeFile.ReadInt32());
     //读取几何类型
     _ShapeType = (ShapeTypeEnum)brShapeFile.ReadInt32();
     //读取数据的外包矩形
     brShapeFile.BaseStream.Seek(36, 0);
     _Envelope = new BoundingBox(brShapeFile.ReadDouble(), brShapeFile.ReadDouble(), brShapeFile.ReadDouble(),
                     brShapeFile.ReadDouble());

    //通过.shp文件获取数据条数
     // 跳过文件头读取
     brShapeFile.BaseStream.Seek(100, 0);
     // 几何数据记录开始位置
     long offset = 100;

    //遍历数据建立功能包含在数据文件的数量
     while (offset < _FileSize)
     {
         ++_FeatureCount;

        brShapeFile.BaseStream.Seek(offset + 4, 0); //跳过长度
         int data_length = 2 * SwapByteOrder(brShapeFile.ReadInt32());

        if ((offset + data_length) > _FileSize)
         {
             --_FeatureCount;
         }

        offset += data_length; // 添加记录数据长度
         offset += 8; //  +添加每条数据记录头的大小
     }
     _OffsetOfRecord = new int[_FeatureCount];
     //brShapeFile.Close();
     //fsShapeFile.Close();
}

       读取数据记录,由于本次不读取.shx索引文件,我们读取数据生成一个索引数组,方便我们读取数据,通过索引值来读取对应得点数据,代码如下:

/// <summary>
/// 生成矢量文件索引
/// </summary>
private void PopulateIndexes() {
     //记录当前位置的指针
     long old_position = brShapeFile.BaseStream.Position;
     //跳过文件头
     brShapeFile.BaseStream.Seek(100, 0);
     //矢量文件记录开始位置
     long offset = 100;
     for (int x = 0; x < _FeatureCount; ++x)
     {
         _OffsetOfRecord[x] = (int)offset;

        brShapeFile.BaseStream.Seek(offset + 4, 0); //跳过的长度
         int data_length = 2 * SwapByteOrder(brShapeFile.ReadInt32());
         offset += data_length; // 添加记录数据长度
         offset += 8; //   +添加每条数据记录头的大小
     }

    // 返回指针的原始位置
     brShapeFile.BaseStream.Seek(old_position, 0);

}
/// <summary>
/// 从.shp文件中读取并解析几何对象
/// </summary>
/// <param name="oid"></param>
/// <returns></returns>
private Geometry ReadGeometry(int oid) {
     brShapeFile.BaseStream.Seek(_OffsetOfRecord[oid] + 8, 0);
     ShapeTypeEnum type = (ShapeTypeEnum)brShapeFile.ReadInt32(); //Shape type
     if (type== ShapeTypeEnum.Null)
     {
         return null;
     }
     if (type==ShapeTypeEnum.Point)
     {
         return new Point(brShapeFile.ReadDouble(), brShapeFile.ReadDouble());
     }
     else
     {
         throw (new ApplicationException("Shapefile 文件类型 " + _ShapeType.ToString() + " 不支持"));
     }

}

5.屏幕坐标与空间坐标转换

      这一节主要讲一下如何全图展现所有数据。全图显示存在两种状态

1.空间坐标外包矩形宽高比(宽/高)>画布屏幕坐标宽高比(宽/高) 如下图

test

该状态下展示就以坐标点左右方向占满整个宽度,真实坐标点转为屏幕坐标点的函数。

/// <summary>
/// 空间坐标转屏幕坐标
/// </summary>
/// <param name="p"></param>
/// <param name="map"></param>
/// <returns></returns>
public static PointF WorldtoMap(Point p, Map map)
{
     PointF result = new System.Drawing.Point();

    //在该种情况下,求出的height值为,除去上下空白坐标的高度,如上图所示
     double height = (map.Zoom * map.Size.Height) / map.Size.Width;
     double left = map.Center.X - map.Zoom * 0.5;
     double top = map.Center.Y + height * 0.5 * map.PixelAspectRatio;
     result.X = (float)((p.X - left) / map.PixelWidth);
     result.Y = (float)((top - p.Y) / map.PixelHeight);
     return result;
}

2.空间坐标外包矩形宽高比(宽/高)<画布屏幕坐标宽高比(宽/高) 如下图

test1

该状态下展示就以坐标点左右方向占满整个宽度,真实坐标点转为屏幕坐标点的函数。

/// <summary>
/// 空间坐标转屏幕坐标
/// </summary>
/// <param name="p"></param>
/// <param name="map"></param>
/// <returns></returns>
public static PointF WorldtoMap(Point p, Map map)
{
    PointF result = new System.Drawing.Point();

    //在该种情况下,求出的height值为,整个屏幕高度,如上图所示
     double height = (map.Zoom * map.Size.Height) / map.Size.Width;
     double left = map.Center.X - map.Zoom * 0.5;
     double top = map.Center.Y + height * 0.5 * map.PixelAspectRatio;
     result.X = (float)((p.X - left) / map.PixelWidth);
     result.Y = (float)((top - p.Y) / map.PixelHeight);
     return result;
}

6.绘制点坐标

使用c#System.Drawing.Graphics进行渲染绘制点。

      /// <summary>
       /// 在地图上绘制点
       /// </summary>
       public static void DrawPoint(Graphics g, Point point, Brush b, float size,  Map map) {
           if (point == null)
               return;
           PointF pp = Transform.WorldtoMap(point, map);
           Matrix startingTransform = g.Transform;
           float width = size;
           float height = size;
           g.FillEllipse(b, (int)pp.X - width / 2,
                       (int)pp.Y - height / 2 , width, height);
       }

7总结

      第一节简单的讲了一下.shp数据的读取,以及全图情况下空间坐标与屏幕坐标相互转换。当然只讲了核心功能,具体不明白的可以调试代码进行自己探索。下一节主要讲一下GIS平移缩放问题核心功能。

github项目地址:https://github.com/HuHongYong/ATtuingMap 

作者:ATtuing

出处:http://www.cnblogs.com/ATtuing

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

原文地址:https://www.cnblogs.com/ATtuing/p/10807472.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


https://www.osgeo.cn/qgis-tutorial/overview.html https://www.osgeo.cn/pygis/  《Python与开源GIS》配套资源(扫码关注微信公众号(gislite),输入“Python与开源GIS”获取下载方式)《Python与开源GIS》配套资源(扫码关注微信公众号(gislite),输入“Python与开源GIS”获取下
设计方案是工程建设最关键的环节,也是影响城市规划的基本因素,方案设计水平的高低更是直接影响着项目的经济和社会效益。在新基建的牵引下,可视化、数字化、信息化转型逐渐成为工程行业共识。因此,BIM已成为各行业尤其是高速公路等带状工程项目解决实际问题的重要生产工具。图新地球w
BIM与GIS的区别与联系http://www.bimcn.org/cjwt/2018111516177.html链接:Link
成功有感之给年轻人的10个忠告1、努力工作要努力,随随便便过日子过四五年也是过,稍微努力的过四五年也是过,努力的过四五年也是过,何不努力好好的干。如果努力的过好毕业后的四五年,这对我们以后的人生非常有帮助。2、虚心学习多与比自己大的人(长辈)/成功人士交流学习,要虚心听
鉴于陆地多边形为ShapelyMultiPolygon,我想找到代表例如多边形的多边形(多边形).海岸线周围12海里的缓冲区.使用Shapely缓冲区方法不起作用,因为它使用欧几里德计算.有人能告诉我如何计算python中的测地缓冲区吗?解决方法:这不是一个形状问题,因为在其文档中明确地说明该库仅用于
背景与宣言传统的GISC/S开发已经很被别人不屑了,在时代的洪流下,我深知,再敝扫自珍就是断了自己的活路。二维地图已经是高峰和逐渐被遗弃了,如果再深耕也仅仅是追赶先进步伐的份了。三维WebGIS也才开始几年而已,在这个点上还能有所作为。所以,从今天开始,我决定,在去年开始的WebGIS开发的
我正在运行一个函数,如果它被加载,需要关闭一个Dojo对话框.如何检查dojo对话框是否正在运行?如果未定义,我是否使用纯JavaScript并按ID检查?if(dijit.byId("blah")!==undefined){destroyRecursivedijit;}或者我使用对话框对象的属性,如:isFocusablemethodisLoade
1.中国大陆ArcGIS交流群(名字很怪,数据很多)群号828589843 群主制作的乔峰地球是个不错的东西,详情请看博文 【高速版收费】如何免费下载谷歌地图影像,谷歌地球影像,谷歌高程,以及高德建筑轮廓?-qf810404的博客-CSDN博客 https://blog.csdn.net/qf810404/article/details
1.开篇      大学毕业工作已经两年了,上学那会就很想研读一份开源GIS的源码,苦于自己知识和理解有限,而市面上也没有什么由浅入深讲解开源gis原理的书籍,大多都是开源项目简介以及项目的简单应用。对于初级程序员想读懂一个成熟的GIS开源项目的困难点主要有三点,1.开发经验和gis
一、ArcGIS10概述1.1总览 ArcGIS是地理信息系统平台软件,主要用于创建和使用地图,编辑和管理地理数据,分析和共享地理信息,并在一系列应用中使用地图和地理信息。功能定位:a.地图:ArcGIS地图不仅包括构建地图是用到的地理数据,还包括用来获取所需结果的分析工具
栅格数据用一个规则格网来描述与每一个格网单元位置相对应的空间现象特征的位置和取值。在概念上,空间现象的变化由格网单元值的变化来反映。地理信息系统中许多数据都用栅格格式来表示。栅格数据在许多方面是矢量数据的补充,将两种数据相结合是GIS项目的一个普遍特征。一、栅格数据
我正在为Android开发室内定位系统.我需要绘制建筑物的自定义平面图并在其上覆盖指针.在这种情况下,我应该使用哪个软件/API来绘制自定义平面图?提前致谢.解决方法:我最近做了一个图书馆平面图,作为我的硕士论文的一部分.我选择了一个简单的自定义Drawable,它只是缩放了一个Bitmap
环境如下:**Windows7**PATH=C:\Python27\;C:\Python27\Scripts;C:\Python27\Lib\site-packages\MySQLdb;C:\ProgramFiles\MySQL\MySQLServer5.5\bin;C:\OSGeo4W\bin**python2.7**'C:\\Python27\\lib\\site-packages\\bitstring-2.1.1-
我想用python/shapely计算一个点和一个国家边界之间的距离.它应该工作得很好point.distance(poly),例如在这里演示FindCoordinateofClosestPointonPolygonShapely但使用geopandas我面临的问题是:‘GeoSeries’对象没有’_geom’属性我处理数据有什么问题?我的边界数据集来
我使用sharpmap从MSSQL渲染边框(几何)作为PNG图像.这一切都运作良好,除了国家在平面图像格式上看起来太“宽”.据我了解,我需要创建EPSG转换:3857投影,但我不知道该怎么做.这是我的代码varmap=newMap(newSize(request.Width,request.Height));map.BackColor=Color.T
一、绪论空间信息移动服务(移动GIS)概念?移动GIS=空间信息服务+移动方式提供空间信息移动服务产生的背景:地理信息系统+手机(移动通信)+GNSS实时定位国际地理信息系统界将地理信息系统、GPS和无线互联网一体化的技术称为“移动地理信息系统”(MobileGeographicalInformationSy
GIS地理工具案例教程——批量合并影像商务合作,科技咨询,版权转让:向日葵,135—4855__4328,xiexiaokui#qq.com描述:合并目录下的所有影像功能:对指定工作空间下的栅格数据,进行批量镶嵌优点:速度极快,合并成百上千张影像(由硬件决定)使用简单,智能提示,默认参数稳定高效,使用模型,无手写代码,永无bu
官方版的QGIS是一个经过打包的GIS工具集合,不仅包括了QGIS及插件,还包括GRASS,sagaGIS等扩展功能。是一个功能全面而且强大的工具集合。但就是有时候感觉不够轻便,功能太多有时候查找起来也不方便。 而且有时候急用的时候,安装起来太费时间。我试着打包了一个绿色版的供大家学习
GIS地理工具案例教程——批量去除多边形的重叠部分商务合作,科技咨询,版权转让:向日葵,135—4855__4328,xiexiaokui#qq.com问题:几乎所有的手工生产的数据,都存在多边形重叠(overlap)的拓扑错误。对于大数据集,手动编辑处理所有的重叠多边形,需要经年累月的时间。解决方法:通过制作空间分析
我有一张地图的图像文件和一条用粗体红线标记的弯曲道路(宽度超过1像素,图像上没有其他红色物体).有人可以建议步骤如何将这条道路识别为一条线,然后将其转换为函数y=f(x),这样我就可以测量精确的距离.我不知道从哪里开始……谢谢.解决方法:逐个像素地浏览图像并检查每个图像的