NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

全球地震系统的可视化

  • 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:294907

评论

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

《我想进大厂》之Java基础夺命连环16问

艾小仙

Java 面试 编程语言

11.11 程序员的 1111 种死法

京东科技开发者

程序员 程序人生

Java动态修改LOGGER日志级别

Zhendong

Java Arthas

一次完整的JVM堆外内存泄漏故障排查记录

Zhendong

【概念篇】你真正了解越来越火的“数据驱动” 吗?

Java架构师迁哥

目标检测-框架之darknet-数据读取

Dreamer

践行新基建,共建城市智能体,为数字经济发展提供新动能

CECBC

云计算 大数据

阿里首发MySQL“完美日记”,基础+优化+事务+集群+锁+主从复制+安全备份

Java架构追梦

Java MySQL 数据库 架构 面试

LeetCode题解:剑指 Offer 22. 链表中倒数第k个节点,使用栈,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

双“11”搞促销?本文教你用贪心算法来盘他!

Java架构师迁哥

第七周作业

Geek_4c1353

极客大学架构师训练营

iptables 端口转发

田振宇

「架构师训练营」第 4 周作业

小黄鱼

极客大学架构师训练营

腾讯WeMap,一颗“孢子”的数智化之旅

脑极体

甲方日常 50

句子

工作 随笔杂谈 日常

年末十家手机银行数字化升级大盘点:谁家开发更全面?谁家建设更到位?

CECBC

疫情 银行 手机银行

重拳出击!平台经济反垄断,互联网巨头市值蒸发千亿

CECBC

小额贷款 反垄断

架构训练营-week8-数据结构与算法,网络,IO

于成龙

极客大学架构师训练营 架构训练营

面试蚂蚁金服,首战被MySQL惨虐,熬夜啃透这份阿里面经复盘一个月再战拿下P7offer

比伯

Java 程序员 架构 面试 阿里

阿里突遭断网断电!双11最惊险一幕刚刚曝光

Java架构师迁哥

当Tomcat遇上Netty,我这一系列神操作,同事看了拍手叫绝

小Q

Java 学习 程序员 架构 面试

要求自愿降薪,员工内心普遍满意:“服从”是如何发生的?

脑极体

【Mycat】Mycat核心开发者带你看尽Mycat三大核心配置文件!!

冰河

分布式数据库 中间件 mycat

技术干货:Apache Pulsar 在移动云上的应用

Apache Pulsar

大数据 开源 云原生 Apache Pulsar

Pulsar Summit Asia 2020 | 场景案例论坛(下):多行业,多场景

Apache Pulsar

大数据 开源 Apache Pulsar

浅谈程序员的“内卷化”

数据社

搞微服务用阿里开源的 Nacos 真香啊!

阿里巴巴 开源 编程

SpringBoot启动原理

编程门槛 框架设计 spring Boot Starter】

面试官问我redis数据类型,我回答了8种

数据库 学习 面试

Java批量导入去除重复数据并返回结果,我差点就被放倒了

小Q

Java 学习 程序员 架构

MySQL 的 join 功能弱爆了?

程序员历小冰

MySQL postgres 多表join

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