《Python地理空间分析指南(第2版)》——1.12 牛刀小试-阿里云开发者社区

开发者社区> 开发与运维> 正文

《Python地理空间分析指南(第2版)》——1.12 牛刀小试

简介:

本节书摘来自异步社区《Python地理空间分析指南(第2版)》一书中的第1章,第1.12节,作者: 【美】Joel Lawhead(莱哈德) 更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.12 牛刀小试

现在,你已经对地理空间分析有了进一步的了解,接下来我们要着手使用Python构建一个名为SimpleGIS的GIS应用了。这个程序将使用地理数据模型构建一个完整的GIS应用,而且可以渲染专题地图,显示不同城市的人口数量。

数据模型还将是结构化的,所以你可以进行一些基本的查询操作。SimpleGIS将会包含科罗拉多州的3个城市及其人口数量。

更重要的一点是,我们将完全使用Pytohn代码实现这个小型系统的构建,从而展示Python语言的强大威力。当然我们还会用到Python标准库中的部分模块,但不会下载任何第三方应用包。

1.12 构建SimpleGIS
代码大致分为两个部分。第一部分是数据模型部分,第二部分是地图数据渲染。对于数据模型部分,我将使用Python的列表(List)实现。Python的列表是一种原生的数据类型,它能够以特定顺序作为存储其他对象的容器。如果你想深入开发自己的脚本,它们还可以映射更复杂的数据结构。

第二部分的代码将会使用Python的turtle图形引擎渲染地图。我们在GIS系统中唯一用到的是世界坐标系转换函数,其作用是将经纬度转换为屏幕坐标格式。所有的图形引擎都有一个原点(0,0),这个点通常是绘图板的左上角或者右下角。turtle主要用途是讲授可视化编程的。turtle的图形画板的原点(0,0)选在了中心点,类似一个图形计算器。图1-21说明turtle模块使用的是笛卡儿坐标系统。从图上可以看到,一些点被绘制在了正负空间上。


0d9f2630b5e41c1e07c8aee095bd654d3948aac1

这也意味着turtle图形引擎支持负的屏幕坐标系统,这在图形画板系统中是很少见的。但是,对于本示例来说,turtle模块主要用来帮助我们快捷地渲染地图。

按部就班
你可以在Python的交互式解释器中运行此程序,还可以将其保存为单独的脚本文件之后再运行它。Python的交互式解释器环境对于学习新的概念非常有利,因为你能够实时地获得错误反馈和未知的程序异常行为。可以很方便地重现这些问题,然后不断尝试改进,直到获得满意的结果为止。

(1)在Python中,经常会在脚本顶部声明导入模块,所以我们将先导入turtle模块,我们将使用Python的import as特性声明导入模块名称及其缩写,这样我们在键入turtle命令时就会方便一些:

import turtle as t

(2)接下来,我们将从声明一些简单的变量开始建立数据模型,用这些变量名代替数字访问列表索引使代码更容易理解。Python的列表是从0开始索引存储对象的。所以如果想访问一个名为myList的列表对象的第一个元素时,我们将以如下方式调用它:

myList[0]

(3)为了让代码更易读,我们可以使用变量名代替常用的索引数:

firstItem = 0
myList[firstItem]

在计算机科学中,将常用的数字放在方便记忆的变量中是普遍的做法。这个变量通常被称为常量。

所以,在我们的示例中,我们声明一些和所有城市相关的常量。所有城市都会有名称、坐标和人口数等属性:

NAME = 0
POINTS = 1
POP = 2

(4)现在,我们将为科罗拉多州建立一组包含城市名、坐标和人口数的列表。需要注意的是,坐标点是嵌套在另外一个列表里面的:

state = ["COLORADO", [[-109, 37],[-109, 41],[-102, 41],[-102,
37]], 5187582]

(5)城市的信息将以嵌套列表的形式存储。每个城市的位置信息由一对经纬度坐标点构成。这些实体构成了我们的GIS数据模型。我们将初始化一个空的列表,然后将每个城市的信息依次添加到列表中:

cities = []
cities.append(["DENVER",[-104.98, 39.74], 634265])
cities.append(["BOULDER",[-105.27, 40.02], 98889])
cities.append(["DURANGO",[-107.88,37.28], 17069])

(6)现在我们需要定义GIS地图尺寸,可以根据你的屏幕分辨率设定任意的宽度和长度:

map_width = 400
map_height = 300

(7)为了保证地图和绘图板尺寸匹配,我们首先需要确定最大的层范围,即州的尺寸。我们将设定地图的全局边框并且将其尺寸限定在州一级。这样我们就能够概览每个点的经纬度,然后和当前的最大和最小x和y的值进行比较。如果它们不在当前的最大或者最小值的范围之内,我们可以适当地做一些调整。最大和最小值分别是:

minx = 180
maxx = -180
miny = 90
maxy = -90
for x,y in state[POINTS]: 
   if x < minx: minx = x
   elif x > maxx: maxx = x
   if y < miny: miny = y 
   elif y > maxy: maxy = y

(8)第2步是计算州和绘图板之间的缩放比例。这个比例是用来将经纬度坐标转化成屏幕坐标的。我们获取州在x和y坐标上的尺寸,然后和地图的尺寸进行比较得到缩放比例:

dist_x = maxx - minx 
dist_y = maxy - miny
x_ratio = map_width /
dist_x y_ratio = map_height / dist_y

(9)接下来是convert()函数,它是我们SimpleGIS中唯一用到的函数。它使用先前的缩放比例将经纬度坐标转换成屏幕坐标。需要注意的是,最后我们将地图的宽度和高度除以2,之后减去这个值。这样做的原因是turtle图形绘板罕见地将原点选在了绘图板的中心点位置。每个地理分析程序都有类似如下代码:

def convert(point):
lon = point[0]
lat = point[1]
x = map_width - ((maxx - lon) * x_ratio) 
y = map_height - ((maxy - lat) * y_ratio)
# turtle图形引擎是以屏幕中心为起点位置的,因此必须把坐标点的位置进行适当的偏移
x = x - (map_width/2)
y = y - (map_height/2)
return [x,y]

(10)现在激动人心的时刻到了!我们已经为我们的GIS系统渲染专题地图做好准备了。turtle模块使用了光标笔的概念,在绘图板上移动光标的过程类似一支笔在一张纸上涂鸦。移动光标时图形系统会绘制一条线。所以你应该在阅读代码时发现,当希望移动到一个新的位置然后准备绘制图形时,我们使用了t.up()和.down()来控制画笔。在本章中我们还有几个重要的步骤。因为科罗拉多州在地图上是一个多边形,所以我们必须在该多边形的第一个点和最后一个点之间绘制条线,以确保这个多边形是闭合的。我们还可以省去这个闭合步骤,直接在科罗拉多州的数据集中添加重复的点。州一级的图形绘制完成后,我们将使用write()方法标记它:

t.up()
first_pixel = None
for point in state[POINTS]:
  pixel = convert(point)
  if not first_pixel:
    first_pixel = pixel
  t.goto(pixel)
  t.down()
t.goto(first_pixel)
t.up()
t.goto([0,0])
t.write(state[NAME], align="center", font=("Arial",16,"bold"))

(11)如果你的代码运行到这里了,那么我们将看到一个非常简单的科罗拉多州地图,如图1-22所示。


<a href=https://yqfile.alicdn.com/0d2de6168072c4a74ebea2a1540ece587b829180.png" >

注意 

如果你正在调试运行这些代码,那么也许你需要临时将下列代码添加到程序的末尾,否则Tkinter窗体在绘制任务执行完毕之后马上就会自动关闭。

t.done()

(12)现在,我们将渲染城市的位置、名称和人口等信息。因为城市是由一组特征列表构成的,我们循环读取它们之后对其进行渲染。与移动光标笔绘制线条不同的是,我们将使用turtle的dot()方法根据SimpleGIS的convert()返回的屏幕坐标绘制小圆点到绘图板上。同时我们还会使用相关的城市名和人口数标记这些小圆点。也许你已经在使用turtle的write()方法时就注意到了,我们必须将人口数转换为字符串形式。为此,我们可以使用Python内置的str()方法:

#绘制城市    
for city in cities:
  pixel = convert(city[POINTS])
  t.up()
  t.goto(pixel)
  # 绘制城市位置
  t.dot(10)
  # 标记城市
  t.write(city[NAME] + ", Pop.: " + str(city[POP]), align="left")
  t.up()
(13)现在
执```  
行最后一个操作来证明我们已经创建了一个真正的GIS应用。我们将执行一个属性查询操作来确定人口最多的城市。接下来,将执行一个空间查询来确定离西部最远的城市。最后,我们将在地图之外的适当区域显示这些查询结果。

(14)对于我们的查询引擎来说,我们将使用Python内置的min()和max()函数。这些函数可以用一个列表作为输入参数,然后返回列表中最大或者最小的值。这些函数还有一个特性,它们支持使用关键字参数对一个复杂对象排序。因为在我们的数据模型中,需要处理嵌套列表,所以可以重复利用这些函数的关键字参数特性。该关键字参数还支持最终值返回之前使用函数对列表进行临时修改赋值。因此,就可以单独比较人口数和位置点等信息。我们完全可以编写一个新的函数返回特定的值,但是可以使用Python的lambda关键字来实现。lambda关键字定义了一个匿名的内部函数。其他Python的函数也可以内部调用,例如字符串函数str(),但是它们不是匿名的。这个临时函数可以将我们感兴趣的值和其他无关信息隔离。

(15)所以,我们的第一个问题是:哪个城市的人口最多?

biggest_city = max(cities, key=lambda city:city[POP])
t.goto(0,-200)
t.write("The biggest city is: " + biggest_city[NAME])

(16)接下来的问题是:哪个城市离西部最远?

western_city = min(cities, key=lambda city:city[POINTS])
t.goto(0,-220)
t.write("The western-most city is: " + western_city[NAME])

(17)在前面的查询中,我们会使用Python的内置函数min()获取最小经度值,因为我们的城市位置信息是由一对经纬度坐标构成的。也许可能包含其他格式多种位置信息,那么可能需要我们对其进行适当修改才能正常工作。但是对于SimpleGIS应用来说,我们使用了常见的位置信息格式,使之尽可能直观。

最后两个命令只是用来做清理工作的。首先,我们隐藏光标。接下来调用turtle的done()方法,这样将会让turtle图形界面一直保持打开状态,直到我们将其关闭为止。

t.pen(shown=False)
t.done()

不管你使用Python交互解释器还是运行一个完整的脚本程序代码,都会看到下面的实时渲染地图,如图1-23所示。
<div style="text-align: center">
 <img src="https://yqfile.alicdn.com/385fe0b4ec6d5a08de58517c4fee099dca828dad.png " >
</div>



恭喜!你沿着旧石器时代猎人、GIS之父Roger Tomlinson博士、地理空间先驱Howard Fisher和改变游戏规则的人道主义程序员的足迹,创建了一个支持功能扩展和技术完备的地理信息系统。它只用了不到60行Python代码!你将很难找到像Python这样编程语言,它只使用核心库和很少且易读的代码就能够构建一个完整的GIS应用。即使你找到了,那么它也很可能无法完成本书其余章节地理空间的Python之旅。

如你所见,SimpleGIS还有很多改进的空间。下面的内容是你能够根据本书开始提到的Tkinter和Python的参考材料进行扩展完善的内容:

- 在右上角创建包含美国国界和科罗拉多州位置的总览图;
- 为地图配色,对比鲜明,增强视觉吸引力;
- 为不同特征创建一个地图关键字;
- 创建一组州和城市的列表并添加更多州和城市;
- 为地图添加一个标题;
- 创建一个柱状图直观地比较人口数。

版权声明:本文首发在云栖社区,遵循云栖社区版权声明:本文内容由互联网用户自发贡献,版权归用户作者所有,云栖社区不为本文内容承担相关法律责任。云栖社区已升级为阿里云开发者社区。如果您发现本文中有涉嫌抄袭的内容,欢迎发送邮件至:developer2020@service.aliyun.com 进行举报,并提供相关证据,一经查实,阿里云开发者社区将协助删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章