1.开篇

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

我的文章思路,代码上传到我的github上面,github地址:https://github.com/HuHongYong/ATtuingMap,欢迎大家star aaarticlea/png;base64,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" alt="" width="12" height="12" /> 一下,每一篇文章对应的代码生成released版本,方便后期找到文章对应的版本版本代码,如下:

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功能。项目的核心结构如下:

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(, );
//读取四个字节,检查文件头
if (brShapeFile.ReadInt32() != )
{
//文件真实的编码是9994,
//170328064的16进制为0x0a27,交换字节顺序后就是0x270a,十进制就是9994了
throw (new ApplicationException("无效的Shapefile文件 (.shp)"));
}
//五个没有被使用的int32整数
brShapeFile.BaseStream.Seek(, );
//获取文件长度,包括文件头
_FileSize = * SwapByteOrder(brShapeFile.ReadInt32());
//读取几何类型
_ShapeType = (ShapeTypeEnum)brShapeFile.ReadInt32();
//读取数据的外包矩形
brShapeFile.BaseStream.Seek(, );
_Envelope = new BoundingBox(brShapeFile.ReadDouble(), brShapeFile.ReadDouble(), brShapeFile.ReadDouble(),
brShapeFile.ReadDouble()); //通过.shp文件获取数据条数
// 跳过文件头读取
brShapeFile.BaseStream.Seek(, );
// 几何数据记录开始位置
long offset = ; //遍历数据建立功能包含在数据文件的数量
while (offset < _FileSize)
{
++_FeatureCount; brShapeFile.BaseStream.Seek(offset + , ); //跳过长度
int data_length = * SwapByteOrder(brShapeFile.ReadInt32()); if ((offset + data_length) > _FileSize)
{
--_FeatureCount;
} offset += data_length; // 添加记录数据长度
offset += ; // +添加每条数据记录头的大小
}
_OffsetOfRecord = new int[_FeatureCount];
//brShapeFile.Close();
//fsShapeFile.Close();
}

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

/// <summary>
/// 生成矢量文件索引
/// </summary>
private void PopulateIndexes() {
//记录当前位置的指针
long old_position = brShapeFile.BaseStream.Position;
//跳过文件头
brShapeFile.BaseStream.Seek(, );
//矢量文件记录开始位置
long offset = ;
for (int x = ; x < _FeatureCount; ++x)
{
_OffsetOfRecord[x] = (int)offset; brShapeFile.BaseStream.Seek(offset + , ); //跳过的长度
int data_length = * SwapByteOrder(brShapeFile.ReadInt32());
offset += data_length; // 添加记录数据长度
offset += ; // +添加每条数据记录头的大小
} // 返回指针的原始位置
brShapeFile.BaseStream.Seek(old_position, ); }
/// <summary>
/// 从.shp文件中读取并解析几何对象
/// </summary>
/// <param name="oid"></param>
/// <returns></returns>
private Geometry ReadGeometry(int oid) {
brShapeFile.BaseStream.Seek(_OffsetOfRecord[oid] + , );
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.空间坐标外包矩形宽高比(宽/高)>画布屏幕坐标宽高比(宽/高) 如下图

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

/// <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.空间坐标外包矩形宽高比(宽/高)<画布屏幕坐标宽高比(宽/高) 如下图

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

/// <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 / ,
(int)pp.Y - height / , width, height);
}

7总结

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

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

作者:ATtuing

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

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

一步一步手写GIS开源项目-(1)500行代码实现基础GIS展示功能的更多相关文章

  1. 一步一步手写GIS开源项目-(2)地图平移缩放实现

    系列文章目录 一步一步手写GIS开源项目-(1)500行代码实现基础GIS展示功能 一步一步手写GIS开源项目-(2)地图平移缩放实现 项目github地址:https://github.com/Hu ...

  2. .NET平台开源项目速览(13)机器学习组件Accord.NET框架功能介绍

    Accord.NET Framework是在AForge.NET项目的基础上封装和进一步开发而来.因为AForge.NET更注重与一些底层和广度,而Accord.NET Framework更注重与机器 ...

  3. 手写一个HTTP框架:两个类实现基本的IoC功能

    jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架 国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章&l ...

  4. pytorch 手写数字识别项目 增量式训练

    dataset.py ''' 准备数据集 ''' import torch from torch.utils.data import DataLoader from torchvision.datas ...

  5. [500lines]500行代码写web server

    项目地址:https://github.com/aosabook/500lines/tree/master/web-server.作者是来自Mozilla的Greg Wilson.项目是用py2写成. ...

  6. 为企业应用开发提速,写给企业IT部门的低代码开发基础知识

    简介:应用程序开发长期以来一直是IT部门和业务部门面临的问题. IT部门总是被新的应用程序需求弄得不堪重负.他们不可能完成业务部门想要完成的每一个项目. 同时,业务部门的用户厌倦了等待,并开始完全绕过 ...

  7. 基于Apache Zookeeper手写实现动态配置中心(纯代码实践)

    相信大家都知道,每个项目中会有一些配置信息放在一个独立的properties文件中,比如application.properties.这个文件中会放一些常量的配置,比如数据库连接信息.线程池大小.限流 ...

  8. javascript写贪吃蛇游戏(20行代码!)

    <!doctype html> <html> <body> <canvas id="can" width="400" ...

  9. 非常好的开源C项目tinyhttpd(500行代码)

    编译命令 gcc -W -Wall -lpthread -o httpd httpd.c 源码 #include <stdio.h> #include <sys/socket.h&g ...

随机推荐

  1. 【洛谷P2270】奶牛的运算

    题目链接 不难发现,每加一个括号,就相当于把括号内一段区间中的符号反转,于是就是看n-1个符号经过k次区间反转后的状态数,用插板法搞一搞就可以了 #include<iostream> #i ...

  2. PHP strtok() 函数

    我们仅在第一次调用 strtok() 函数时使用了 string 参数.在首次调用后,该函数仅需要 split 参数,这是因为它清楚自己在当前字符串中所在的位置. 如需分割一个新的字符串,请再次调用带 ...

  3. severless扫盲

    最近老是遇到这个话题,抽一点时间系统的学习下,severless是属于云计算部分,但是和前端息息相关(大家都这么说)- serverless是什么? 字面意思是无服务器,亦或是少服务器的.让用户无需关 ...

  4. 在Visual Studio中直接编译Fluent的UDF

    VS版本:Visual Studio 2013 Fluent版本:Fluent18.2 首先我们启动VS Studio中直接编译Fluent的UDF" title="在Visual ...

  5. /usr/bin/xauth: file /home/user/.Xauthority does not exist

    错误信息如下: /usr/bin/xauth: file /home/user/.Xauthority does not exist 错误原因:是因为添加用户时没有授权对应的目录,仅仅执行了usera ...

  6. 20189220 余超《Linux内核原理与分析》第二周作业

    计算机如何工作的 一.存储程序计算机工作模型 冯诺依曼体系结构:核心思想为存储程序计算机.两个层面: (1)硬件的角度(计算机主板):一个CPU,一块内存,之间有总线连接.CPU内部有一个IP计算器, ...

  7. Java 面向对象(九)

    常用类之Random Random类位于 java.util 包中,主要用于生成伪随机数 Random类将种子数作为随机算法的起源数字,计算生成伪随机数,其与生成的随机数字的区间无关 创建Random ...

  8. LDAP -- ldap 的cn, ou, dc的含义

    1. LDAP的存储规则 区分名(DN,Distinguished Name) 和自然界中的树不同,文件系统/LDAP/电话号码簿目录的每一片枝叶都至少有一个独一无二的属性,这一属性可以帮助我们来区别 ...

  9. excel矩阵相乘矩阵求逆

    如何用电子表格计算两个矩阵相乘 https://jingyan.baidu.com/article/219f4bf7c9039cde452d3854.html   工具/原料 Microsoft Ex ...

  10. 转 Shell判断字符串包含关系的几种方法

    https://blog.csdn.net/rznice/article/details/71086839 Shell中判断字符串包含关系的方法: 1.通过grep来判断:12str1="a ...