0、先上效果图
1、Mapserver基础与配置
【注意:以下内容默认已经安装了MapServer4.0及以上版本,Web GIS使用Openlayers5作为请求和地图渲染库,ol-ext作为弹窗UI】
1.1、MapServer基础
MapServer是一个流行的开源项目,其目的是能够通过网络服务发布用户自定义的地图[1]。
在其最基本的形式中,MapServer是一个在Web服务器上非活动的CGI程序。当一个请求被发送到MapServer时,它使用在请求URL和Mapfile中传递的信息来创建被请求映射的图像。请求也可能返回图例、比例条、参考地图和以CGI变量传递的值的图像。(前端请求常用的方式)
当然,MapServer也可以通过MapScript脚本语言或templating进行扩展和定制,可以构建它来支持许多不同的矢量和栅格输入数据格式,并且可以生成多种输出格式。大多数预编译的MapServer发行版都包含了大部分MapServer的特性。(有兴趣请到官网查看)
1.2、MapServer前端请求准备
Mapfile:Mapfile文件包含了对应地理数据在MapServer的配置,包括渲染配置和查询配置等,用于告诉MapServer能对里面涉及的地理数据进行什么样的操作[1]。一份Mapfile中能够定义多个地理数据图层,类似于ArcGIS的*.mxd文件。
地理数据:MapServer会通过Mapfile中定义的图层(Layer)的路径读取实际的地理数据,因此,在Mapfile中定义的图层都是需要有实际数据存在,否则会导致MapServer读取数据失败。
HTML页面:MapServer内部提供了默认的HTML页面,当进行查询时,MapServer会将查询到的结果放在HTML页面中返回。当然,用户也可以自己定义符合自己要求的HTML页面。对于查询数据属性而言,HTML页面是必须存在,否则无法触发MapServer的查询机制。
MapServer CGI:有了前面三类材料,前端进行查询还需要一个通用接口,这个接口称为公共网关接口(Common Gateway Interface,CGI),是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序(MapServer CGI程序名为mapserv)能与浏览器进行交互,还可通过数据API与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据[2]。格式化为HTML文档后,发送给浏览器,也可以将从浏览器获得的数据放到数据库中。MapServer CGI一般通过表单提交,参数较多,目前常用方法是通过OpenLayers封装的方法获取对应的URL[10]。
Web服务器:要使得MapServer查询能够通过网络访问,计算机中还需要有一个公有IP地址和配置Web服务访问。
1.3、单个栅格图层Mapfile构建
其基本结构为[3,4,5] (每次缩进都是2个英文的空格,每个标签后值的填写都需要空一格。注意:以下用#标注注释每个标签的含义,在实际文件中不用出现。加粗的字体都是为实现栅格图层查询所必须加的配置,矢量查询也类似)
MAP #Mapfile的根标注
NAME #为该文件命名,可以与其他文件重复,用于标注属于哪个服务
STATUS #文件激活状态,默认为ON,表示运行读取该文件
SIZE #每次返回地图的大小(一般请求地图是使用WMSTile方式)
EXTENT #配置该文件中图层的显示范围
UNITS #图层的显示单位
SHAPEPATH #重要,用于指示地理数据存放的绝对路径
IMAGECOLOR #返回地图的背景颜色
FONTSET #能够使用的字体,如标注等
WEB #配置Web服务的元数据,以下给出常用的的6个字段
METADATA
"wms_title" "WMS Demo Server"
"wms_onlineresource" "http://localhost:8091/cgi-bin/mapserv.exe?"
"wms_srs" "EPSG:4326 EPSG:3857"
"wms_enable_request" "*"
"queryable" "true" #查询栅格数据必须要有的一项,指出可查询
"wms_feature_info_mime_type" "text/html" #查询栅格数据必须要有的一项,指出查询结果使用html方式返回,同时需要在LAYER中指定模板html
END
END
PROJECTION #返回地图的投影
"init=epsg:3857" #在Openlayers显示中,默认投影是3857
END
LEGEND #图例配置
KEYSIZE #大小
LABEL #图例内部字体的标签配置
TYPE BITMAP #暂时不懂,默认使用BITMAP
SIZE #字体排版方式,一般为MEDIUM
COLOR #字体的颜色
END
STATUS #图例的激活状态,一般默认填写ON
END
LAYER
NAME #图层的名字
DATA #图层的文件名,结合SHAPEPATH得到文件的绝对路径
PROJECTION #可以定义具体图层的投影
"init=epsg:4326"
END
TYPE #指出数据是什么类型的,栅格为RASTER,点数据为POINT
STATUS #图层的激活状态,一般为DEFAULT
SIZEUNITS #单元大小,栅格图层一般设置为PIXELS
TOLERANCEUNITS #栅格容限单位大小,一般设置为PIXELS
DUMP #是否允许保存查询,一般设置为ON
TEMPLATE #指出查询结果返回的模板,必须有,否则无法查询数据
METADATA #图层的元数据,查询数据时必须添加
"gml_include_items" "all"
"wms_include_items" "all"
END
CLASS
NAME #类别1的名称
EXPRESSION #表达式,用于判断哪个值区间内属于该类别,图层值使用[pixel]进行获取,可以作简单的运行
STYLE #显示的样式
COLOR #显示的颜色
END
LABEL #可选项,可以配置在像素上显示标注,一般用于矢量数据
END
END
CLASS
NAME #类别2的名称
EXPRESSION #表达式,用于判断哪个值区间内属于该类别,图层值使用[pixel]进行获取,可以作简单的运行
STYLE #显示的样式
COLOR #显示的颜色
END
END
END
END
1.4、Template HTML页面构建
1.4.1、栅格查询返回的结果[6]
x: 查询栅格像素的经度,该坐标的地理参考系与LAYER中的设置的相同
y: 查询栅格像素的纬度,该坐标的地理参考系与LAYER中的设置的相同
value_list:如果栅格为多波段数据,则返回每个图层在相同位置的查询结果
value_n: 在所选列表中该像素处的第n个频带的值(基于0)。每个选择的波段都有一个value_n条目。
class: 此像素所属的类的名称(仅为分类层)。
red、green、blue:该像素的颜色
1.4.2、用HTML页面封装的结构[7]
整体上为通用的HTML结构,但第一行必须添加MapServer的模板识别字符串。
#该行是为了能让MapServer识别这是模板
通过可以将查询的信息构建为一份表格,返回HTML后可以直接在前端显示。获取查询的数据使用中括号[字段],如果该HTML不直接作为显示,而是仅作为封装,则建议在td标签添加name字段,以便用于前端HTML数据的解析。例子:
MapServer Template Sample
name
latitude
longitude
value
[Layer]
[x]
[y]
[value_0]
2、基于Openlayers5的栅格数据查询
//请求与显示思路:
//1、绑定map的单击事件;
//2、监听map的单击事件,并获取单击的像素投影坐标;
//3、将投影坐标转为地理坐标;
//4、获取需要查询的栅格图层,并基于该图层构建CGI查询的URL;
//5、发送请求,得到数据并进行解析;
//6、重复步骤4,直至所有栅格图层查询完毕;
//7、在地图上显示查询信息【第3节】
//8、解绑map的单击事件【第3节,当不需要再查询的时候触发】
//代码例子:
import {toLonLat} from 'ol/proj';
data() {
return {
clickEvent: null,
}
}
queryLayerInfo(){
//捆绑监听事件
this.clickEvent = this.basemap.on('singleclick', this.mapSingleClick);
}
mapSingleClick(evt) {
let layers = this.basemap.getLayers().getArray();
//将投影坐标转为地理坐标
let coordinate = toLonLat(evt.coordinate);
let infoModel = {'longitude': coordinate[0].toFixed(5), 'latitude': coordinate[1].toFixed(5)};//用于存储多个栅格图层的像素值对象
let viewResolution = 0.001;
for (let i = 0; i < layers.length; i++) {
//挑选出可以查询的图层
if (layers[i].get('name') !== undefined && layers[i].get('name') !== 'baseVectorLyr') {
let url = layers[i].getSource().getGetFeatureInfoUrl(
coordinate,
viewResolution,
'EPSG:4326',
{
'INFO_FORMAT': 'text/html',
'QUERY_LAYERS': layers[i].get('name')
}
);
$.ajax({
type: 'GET',
url: url,
async: false,
success: function (res) {
let el = document.createElement('html');
el.innerHTML = res;
let tr = el.getElementsByTagName('td');
if (tr.length > 0){
infoModel[tr[0].innerText] = tr[1].innerText;
}
}
});
}
}
},
注意事项【重要】
3、Openlayers-Extend显示
//显示思路:
//7、在地图上显示查询信息【第3节】
//8、解绑map的单击事件【第3节,当不需要再查询的时候触发
import Popup from 'ol-ext/overlay/Popup'
import {unByKey} from 'ol/Observable'
//向map中添加popup图层
let popup = new Popup({
popupClass: 'default anim',
closeBox: true,
});
map.addOverlay(popup);
//根据结构化的查询数据构建HTML内容
getShowContent(infoModel){
if (!infoModel) return '';
let html = ol_ext_element.create('DIV', { className: 'ol-popupfeature' });
// 设置标题
let title = 'Raster Layer Information: ';
ol_ext_element.create('H3', { html:title, parent: html });
// 构建表格
if (infoModel) {
let tr, table = ol_ext_element.create('TABLE', { parent: html });
for (let att in infoModel) {
let value = infoModel[att];
tr = ol_ext_element.create('TR', { parent: table });
//添加字段
ol_ext_element.create('TD', {
html: att.substring(att.lastIndexOf('/')+1), parent: tr
});
// 添加值
ol_ext_element.create('TD', {
html: value,
parent: tr
});
}
}
return html;
}
//获取html内容
let htm = this.getShowContent(infoModel);
//在指定投影坐标上弹出内容
this.popup.show(coordinate, htm);
//如果不需要再点击查询了,可以解除监听,避免每次点击都被认为是在查询
unByKey(this.clickEvent);//取消监听事件
注意事项【重要】
以上代码为多个文件中截取出,不宜直接复制粘贴。理解思路才是最重要的,代码书写形式次要。Openlayers-Extend的关于Popup的API如下[9]。
参考资料
[1]https://mapserver.org/introduction.html#mapserver-overview
[2]https://baike.baidu.com/item/CGI/607810?fr=aladdin
[3]https://mapserver.org/mapfile/index.html#mapfile
[4]https://mapserver.gis.umn.edu/de/tutorial/section1.html
[5]http://augusttown.blogspot.com/2010/01/customize-wms-getfeatureinfo-response.html
[6]https://mapserver.org/input/raster.html
[7]https://mapserver.org/mapfile/template.html#template
[8]https://stackoverflow.com/questions/46095654/strange-error-returned-while-trying-to-access-raster-pixel-value
[9]http://viglino.github.io/ol-ext/doc/doc-pages/ol.Overlay.Popup.html#show
[10]https://mapserver.org/cgi/controls.html