写点什么

全球地震系统的可视化

  • 2014-06-16
  • 本文字数:5593 字

    阅读完需:约 18 分钟

GIS(Geographic Information System) 在可视化方面,无疑是有很多优势的,相比于传统的表格或者图表,它更容易将读者快速的带入到具体场景中,并更好的理解图形背后的含义。

很多信息事实上都和地理位置有关,比如关键客户的分布图,货源 / 仓库在地理上的分布等,当这些信息以可视化的方式展现在地图上时,我们可以获得很多之前无法看到的信息,而这些信息可以帮助我们在未来做出更合理的决策。

本文将使用开源工具 OpenLayers 做一个实例,并在这个过程中详细讨论 GIS 背后的一些技术细节。在这个例子中,我们将会把地球上的地震统计信息用可视化的方式,直观的展现在地图上。

例子的最终运行结果如下:

图中的绿色点表示小于里氏 3 级的地震,红色的点表示大于里氏 3 级的地震。

Web GIS 简介

Web GIS 是将传统的 GIS 与 Web 集成起来,借助于 Web 的高可用性,Web GIS 可以使地图服务本身更容易被人们获得。成熟的 Web GIS 产品已经有很多,比如 Google Maps,Bing Maps 等,这些 GIS 产品已经很早就深入了我们的日常生活,并为我们的学习工作带来了众多的便利,比如去一个陌生的城市出差时找到预定的酒店,查找离你目前所处位置最近的银行等等。

那么我们看到的网页上很漂亮的地图是怎么产生的呢?它们又是如何被渲染到页面上的呢?

地图的渲染方式

几乎所有我们看到的基于 Web 的地图,都是通过不同层次叠加出来的结果。GIS 系统会将 河流,建筑物,街道,文本标签的信息分别存储与不同的层次上(物理上不同的文件 / 数据库中),这样做不但可以在编辑地图时更容易管理(比如表示街道的图层的更新频率肯定会高于表示河流的图层的更新频率 ),而且可以按需加载(只查看河流,街道,不要建筑物信息等),从而减少服务器上的负载。

地图服务器将不同的层次叠加,最终生成一张完整的地图。这个最终的地图包含了所有的信息,比如河流,建筑物等都会带上名称。而且每个层次都可以独立的配色,这样最终的地图就是我们见到的样子了:蓝色的水域,黄色的高速路,白色的建筑,绿色的公园等等。

图片来源( http://www.srh.noaa.gov/bmx/?n=gis)

地图在服务器端被渲染出来之后,尺寸一般会非常大。需要由专门的模块将这些大图切分成很多组的小图,这些小图被称之为瓦片(tile)。为了给不同缩放级别的客户端提供不同的图片,这些瓦片被精心的分成了多个组,每个组都有编号。如果地图支持 18 级的缩放,就会现有 18 个分组。当然分组好越靠后,分组中的瓦片越多。

服务器上的瓦片

而在客户端,当我们在 Google Maps 上拖动地图,或者放大某一个感兴趣的区域时,往往会看到一些灰色的块。这些灰色的块过几秒会被真实的地图替换掉。这是因为,我们不可能也不需要将 GIS 服务器上的地图完全加载到客户端呈现,而是分批获取。在获取地图瓦片的过程中,客户端会展现一些占位符,当真正的瓦片加载完成后,再完成替换。

比如当我们查看西安市地图时,前端的 JavaScript 脚本会根据缩放级别和目前浏览的区域发送少量的请求获取地图瓦片,然后再将这些瓦片拼成一副“完整”的地图。

WMS 请求

WMS(Web Map Service) 是一个基于 HTTP 的简单协议,客户端发送的请求中包含请求类型,地图的层次,边界等信息,服务器根据这个信息生成图片,并返回该图片:

Chrome 中的一次 WMS 请求详情

当然,WMS 本身支持多种类型的请求,最常见的就是 GetMap。具体的细节大家可以参考 OGC 规范及具体服务器的实现。而对于后端的服务器来说,从请求中获取这些信息之后,会首先从数据库 / 数据文件中得到数据,并使用渲染引擎绘制图片,并最后将图片返回客户端。

地图图层的分类

图层可以分为矢量图层和栅格图层,栅格图片一般的来源为航拍,遥感等技术,本质上来说就是照片,不过由于拍摄角度,器材本身产生的失真等,这些照片需要经过校正才能使用。由于栅格图片本身是拍摄的结果,那么放到到一定范围之后,必然会产生模糊。栅格图片通常会附带一些具有特别含义的属性,比如降水量,人口数等。

图片来源( http://earthobservatory.nasa.gov/Features/FalseColor/page3.php)

而矢量图层是数学上的抽象,矢量图层只需要记录在某个特定的坐标系中,各个点,线,面的关系即可,它可以任意缩放而不至于失真。同样,矢量图层中的元素也可以附加一些属性。

OpenLayers 简介

OpenLayers 是一个开源的 JavaScript 库,主要用于将不同来源的地图图层集成在一起,从而产生有价值的应用程序。一个最简单的场景是,底图使用 Google Maps 提供的卫星图层,而在这个图层之上加入与业务相关的矢量层,帮助读者快速的理解图层背后的含义。

OpenLayers 支持的数据格式

OpenLayers 支持很多种的数据格式,如 GML,GeoJSON,GPX 等等,大部分的 GIS 服务器都支持生成这样的数据格式以供展现。

其中,GML,GPX,Atom 等都是基于 XML 的文档,而 GeoJSON 则是基于 JSON 格式的。我们的这个例子主要关注 GeoJSON 格式,它相对而言更小巧,更加可读。

复制代码
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands"
}
}

一个 GeoJSON 中的节点包含三部分的信息:类型,几何要素和属性集。几何要素可以是点,线,面或者多点,多线,多面。而属性则可以包含任意复杂的信息,比如一个表示建筑物的多边形上的可以包含诸如绿化面积,降雨量,植物种类等信息。

数据来源

美国地理信息调查局是一个科学组织,他公开了很多地球上的灾难信息,比如对地震的统计,并提供编程接口。它公开的地震统计信息,包含全世界各地报告过的地震,以及全美所有检测到的地震,并以多种周期(小时,天,周,月等),多种格式(GeoJSON,KML,Atom 等)公开,以便应用程序的开发者只用这些数据。

我们的这个例子中,将使用 USGS 提供的 GeoJSON 格式的数据,并通过 OpenLayers 的 API 将这些数据展现在地图上。

逐步实现

设置基本环境

我们将借助 bower 来安装所有的代码依赖。首先,我们需要 bower 将所有的包都安装在 components 目录下,这个可以通过在当前目录的.bowerrc 文件中指定 directory:

复制代码
{
"directory": "components"
}

然后运行 bower 安装 jquery 以及 openlayers:

复制代码
$ bower install jquery
$ bower install openlayers

通过 bower 安装 OpenLayers 之后,可以通过 OpenLayers 自带的 build 工具将所有的源码合并压缩为一个文件:

复制代码
$ cd components/openlayers/build
$ ./build.py #将会在当前目录下生成一个 OpenLayers.js 的文件
$ mv OpenLayers.js ../

然后,创建一个简单的 HTML 文件,引用 jquery.js 和 OpenLayers.js,以及我们的入口脚本 app.js,本文所有的代码都只是修改这个文件。

复制代码
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Earthquake distribution</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="container">
<div id="map">
</div>
</div>
<script src="components/jquery/jquery.js" type="text/javascript"></script>
<script src="components/openlayers/OpenLayers.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
</body>
</html>

基本代码

一个最简单的 OpenLayers 应用,只需要 7 行代码:

复制代码
$(function() {
var map = new OpenLayers.Map("map");
var osm = new OpenLayers.Layer.OSM();
map.addLayers([osm]);
map.zoomToMaxExtent();
});

这段代码在 id 为 map 的 HTML 元素创建了一个地图,这个地图上有一个叫 OSM 的层(即 OpenStreetMap ,一个开源,类似于维基百科的地图平台),并将地图缩小到边界范围(以获得最大的视野):

生成矢量图层

使用 OpenLayers 来生成矢量图非常容易:

复制代码
var geo = new OpenLayers.Layer.Vector("EarthQuake", {
strategies: [new OpenLayers.Strategy.Fixed()],
protocol: new OpenLayers.Protocol.HTTP({
url: '/all_day.geojson',
format: new OpenLayers.Format.GeoJSON({ignoreExtraDims: true})
})
});

注意此处的 all_day.geojson 是从 USGS 网站上下载的,过去一天中世界各地的所有地震统计。

上边的代码创建了一个名称为 EarthQuake 的矢量层,strategies 中的 Fixed 策略表示仅请求一次资源,然后缓存在前端,不再请求。protocol 表明数据来源为 all_day.geojson,格式为 OpenLayers.Format.GeoJSON。由于 USGS 返回的地理信息除了经纬度还包含深度,而 OpenLayers 默认只处理经纬度的,因此需要此处的 ignoreExtraDims 来忽略那个额外的深度信息。

定制样式

虽然我们已经加上了新的层,也可以看到很多表示地震的点信息,但是并不能看出哪些地震是严重的,比如里氏 3 级以下的地震,几乎没有危害,可以标注成一种颜色;而更高震级的可以标记成另外一种颜色。

OpenLayers 可以很容易的做到这个定制化:

复制代码
var style = new OpenLayers.Style();
var ruleLow = new OpenLayers.Rule({
filter: new OpenLayers.Filter.Function({
evaluate: function(properties) {
return properties.mag < 3.0;
}
}),
symbolizer: {pointRadius: 3, fillColor: "green",
fillOpacity: 0.5, strokeColor: "black"}
});
var ruleHigh = new OpenLayers.Rule({
filter: new OpenLayers.Filter.Function({
evaluate: function(properties) {
return properties.mag >= 3.0;
}
}),
symbolizer: {pointRadius: 5, fillColor: "red",
fillOpacity: 0.7, strokeColor: "black"}
});
style.addRules([ruleLow, ruleHigh]);
geo.styleMap = new OpenLayers.StyleMap(style);

首先创建一个 Style 对象,为 Style 添加两条规则 Rule,然后将 Style 对象包装成 StyleMap 并赋值给表示地震的矢量层 earthquake。

对于规则 ruleLow,我们定义了,当一个 feature 的属性值 mag(震级) 小于三的时候后,使用绿色的,半径为 3px 的小圆圈来表示。而 ruleHigh 则定义了当震级大于等于三的时候,用红色,半径为 5px 的圆圈来表示。

更高级的样式规则

OpenLayers 有完善的样式规则模型,比如我们需要按等级生成不同的颜色标记:在里氏 1 到 3 级为绿色,3 级到 5 级为黄色,大于 5 级为红色:

复制代码
var rules = [
new OpenLayers.Rule({
filter: new OpenLayers.Filter.Function({
evaluate: function(properties) {
return properties.mag < 3.0;
}
}),
symbolizer: {
pointRadius: 3, fillColor: "green",
fillOpacity: 0.5, strokeColor: "black"
}
}),
new OpenLayers.Rule({
filter: new OpenLayers.Filter.Function({
evaluate: function(properties) {
return properties.mag >= 3.0 && properties.mag < 5.0;
}
}),
symbolizer: {
pointRadius: 5, fillColor: "orange",
fillOpacity: 0.5, strokeColor: "black"
}
}),
new OpenLayers.Rule({
filter: new OpenLayers.Filter.Function({
evaluate: function(properties) {
return properties.mag >= 5.0;
}
}),
symbolizer: {
pointRadius: 7, fillColor: "red",
fillOpacity: 0.5, strokeColor: "black"
}
}),
];

事件处理

虽然我们已经可以直观的根据震级不同而看到不同颜色的点,但是整个应用仍然没有多少意义:它不具备于用户的交互能力。我们需要添加上事件处理,当用户点击地图上的一个圆点的时候,应该看到一个更详细的窗口。

复制代码
var selectControl = new OpenLayers.Control.SelectFeature(geo, {
onSelect: onFeatureSelect,
onUnselect: onFeatureUnselect
});
map.addControl(selectControl);
selectControl.activate();
function onFeatureSelect(feature) {
var html = "<span>"+feature.attributes.title+"</span>";
var popup = new OpenLayers.Popup.FramedCloud("popup",
feature.geometry.getBounds().getCenterLonLat(),
null,
html,
null,
true
);
popup.panMapIfOutOfView = true;
popup.autoSize = true;
feature.popup = popup;
map.addPopup(popup);
}
function onFeatureUnselect(feature) {
map.removePopup(feature.popup);
feature.popup.destroy();
feature.popup = null;
}

我们在地图上添加了一个 SelectFeature 控件,并注册了回调函数:当矢量层中的矢量被选中之后,函数 onFeatureSelect 将被执行。我们可以在这个函数中添加对弹出窗口的控制。当 onFeatureSelect 执行时,OpenLayers 会将当前的 Feature 传递进来,我们可以动态的取得震级,标题,链接等信息,并展现给最终用户。

完整的代码示例可以看这里。通过这个例子,我们知道了如何使用OpenLayers 展现既有的数据源生成矢量图层,并了解了如何为这些图层应用不同的样式。最后,我们了解了如何为矢量图层中的特征注册事件处理器。

毫无疑问,我们最终的地图应用中对数据的展现,是其他传统展现方式无法比拟的。通过使用OpenLayers 和公开的数据源,我们可以很容易的搭建起一些很有用的可视化应用,这些应用也在很多方面使得我们的生活更加便利。


感谢张凯峰对本文的审校和策划。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-06-16 03:294897

评论

发布
暂无评论
发现更多内容

算法题每日一练---第5天:星系炸弹

知心宝贝

算法 前端 后端 7月月更

SENSORO 付刘伟:创新技术服务为基层政府筑造数字底座

SENSORO

人工智能 大数据 物联网 城市大脑 数字政府

多商户系统的直播功能用过吗?用过的朋友扣个 666!

CRMEB

关于数据产品经理的三个小的知识点

松子(李博源)

数据中台 数据产品经理 数据产品

NFT卡牌链游系统Dapp开发搭建

薇電13242772558

NFT 链游

C# 窗体应用DataGridView,使用数据库(Sql和MySQl)对DataGridView绑定数据源,获取数据

IC00

C# 7月月更

实时计算基本概念解析

五分钟学大数据

实时计算 7月月更

一文读懂Elephant Swap的LaaS方案的优势之处

西柚子

java零基础入门-java8新特性(上篇)

喵手

Java 7月月更

LeetCode-88. 合并两个有序数组(java)

bug菌

Leet Code 7月月更

开幕在即 | “万物互联,使能千行百业”2022开放原子全球开源峰会OpenAtom OpenHarmony分论坛

OpenHarmony开发者

OpenHarmony

JAVA编程规范之日志规约

源字节1号

后端技术

如何做好研发精益需求管理

思码逸研发效能

研发管理 研发效能 科技

妙啊!美团 OCTO 分布式服务治理系统,这描述也太清晰了

冉然学Java

Java 分布式 美团 #技术干货# Java 开发

阿里JAVA架构师面试136题含答案:JVM+spring+分布式+并发编程!

程序知音

Java 程序员 java面试 后端技术 八股文

在 Kyma 云原生平台上开发并部署 Node.js 应用

Jerry Wang

Kubernetes 云原生 SAP Kyma 7月月更

敏捷 ? DevOps ?

FunTester

NFT挖矿分红系统开发模式定制

开发微hkkf5566

面试突击67:说一下 TCP/IP 协议?以及每层的作用?

王磊

Java 面试

CSS 炫酷文本过渡动画

南城FE

CSS css3 前端 7月月更

SeekTiger的Okaleido有大动作,生态通证STI会借此爆发?

鳄鱼视界

strncpy() 复制字符串(受长度限制)

謓泽

7月月更

优博讯助力深圳打造全球“鸿蒙欧拉之城”

Geek_2d6073

面向高性能计算场景的存储系统解决方案

Baidu AICLOUD

异构计算 云原生存储 AI加速

Spring Cloud 与 K8s 的微服务设计

Damon

7月月更

干货分享-作为Lead 接手一个新的数据团队一 问题盘点 与Insights的发现

松子(李博源)

经验分享 数据分析师 成长笔记 带团队

NFT数字藏品系统搭建—app开发

开源直播系统源码

软件开发 数字藏品 NFT数字藏品系统

图文并茂,讲解TCP和UDP协议的原理以及区别

程序知音

程序员 网络 TCP/IP 后端技术 底层原理

转转监控系统的内部原理及实践 审核中

转转技术团队

监控 Prometheus

万物皆可柯里化的 Ramda.js

掘金安东尼

前端 函数式编程 7月月更

TIME的新封面:元宇宙将改变一切

智捷云

元宇宙 Metaverse 智捷云 智捷云科技

全球地震系统的可视化_语言 & 开发_邱俊涛_InfoQ精选文章