掘金地图方面的文章似乎比较少,偶尔有两篇大佬的文章,写得特别深入,但是都是诸如WEBGL,三维three.js, 等技术门槛特别高,第二个原因应该是使用场景比较少,而一般地理数据交由GIS专职处理
此文章几乎不涉及 GIS 地理信息系统基础和原理(其实是我也啥也不了解 ╮(╯﹏╰)╭),用最简单的方式给大家介绍一套高性能的地图可视化开源方案全家桶
技术栈(官网文档地址):
演示:30W管点管线数据量,忘记打开fps显示了,基本上可以稳定在60帧
双重水印,最为致命 ε=(´ο`*)))
原理简述
地图的大数据量的渲染实质上是geoserver从postgis数据库请求数据,封装为pbf二进制格式返回给前端,然后前端mapbox-gl引擎解析文件,将几何数据渲染到GPU上面达到最终效果
而大数据渲染不造成卡顿的方法,一是通过瓦片形式返回当前屏幕所需要的数据,二是当数据量过大时通过道格拉斯-普克算法,抽稀几何保留其基本形状返回
PostGIS
简介和安装
简介
PostGIS 是一个空间数据库,能把二维、三维乃至四维几何空间数据储存在数据库,依赖于 PostgreSQL 对象关系数据库
想要深入了解和学习请看这篇大佬的文章,详细讲解了 PostGIS 的各方面知识点,包括空间几何创建及其关系、空间索引、搜索连接等,配合官方文档食用更佳。
安装
安装 PostGIS 的前置条件是 PostgreSQL 数据库环境的支持,所以首先安装 PostgreSQL,直接从 官网下载 就好了,安装步骤不再赘述,配置好安装目录和用户密码就 ok,不同的系统环境若有安装不上的情况请百度查询,一般来说问题不大。
接下来安装 PostGIS,有两种方式:
一是从安装好的 PostgreSQL,打开 Stack Builder 选择 PostGIS 进行安装(需要科学上网)
二是从 官网下载 与 PostgreSQL 对应版本的 PostGIS 进行安装
注意,在安装的时候勾选 Create spatial database 会创建空间数据库模板,方便后续其它空间库的创建。
启动停止,window 可在管理面板服务里面找到,安装好默认开机启动,linux 还没研究过= =
数据操作
数据操作界面
pgAdmin 4
PostgreSQL 自带一个 web 端的操作界面 ,现在是版本 4,如图所示,比起普通数据库,多了一些和空间几何相关的扩展、转换器和函数等。
navicat 15
PostgreSQL 12 版本之后 navicat 需要版本 15 才能进行连接,不然无法看到数据表
Shapefile 导入
PostGIS 自带 Shapefile 文件导入,如图所示,支持覆盖/更新/追加等插入
说明和常规操作
数据库增删查改和普通数据库差别不大,多了一个几何字段,以及许多几何操作函数,
数据格式简介
简单类型一般来说只有三种基本几何类型,其他都是这三个的变种,细节请看 PostGis 文档,推荐查看 GeoJson 格式有助理解,一个对各种地理数据结构进行编码的格式,官方规范,嫌麻烦的建议看这个大佬整理的简单说明 地址在这
常用格式:
- 点 POINT(0 0)
- 多点 MULTIPOINT(0 0,1 2)
- 线 LINESTRING(0 0,1 1,1 2)
- 多线 MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))
- 面 POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))
- 多面 MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))
- 合集 GEOMETRYCOLLECTION(POINT(2 3),LINESTRING((2 3,3 4)))
三维数据在经纬度后面加一个 Z 值就可以了,例如 POINT(0 0 1),另外还有曲线、三角面等有兴趣可以翻阅相关文档
数据插入
postgis 现版本几何字段默认为 geom,里面储存空间几何信息,本质上就是一系列坐标值,如以下插入一个经纬度在 110,30 位置的点位几何数据:
-- EWKT格式
insert into pip_point (id,geom) values ('xxx', 'SRID=4326;POINT(110 30)')
-- WKT格式
insert into pip_point (id,geom) values ('xxx', st_geomfromtext('POINT(110 30)',4326))
-- GML格式
insert into pip_point (id,geom) values ('xxxX', st_geomfromgml('<gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:coordinates>30,110</gml:coordinates></gml:Point>'))
以上 sql 插入表示同一个点位,有许多几何格式根据不同的标准和规范来表示同样的结果
具体语法和几何操作(包含、相交、连接、距离...)请查阅以上链接文档和专题
geoserver
交互操作设计,用地理开放标准来发布主流的空间地理数据源数据
百度百科
兼容 WMS 和 WFS 特性;支持 PostgreSQL、 Shapefile 、 ArcSDE 、 Oracle 、 VPF 、 MySQL 、 MapInfo ;支持上百种投影;能够将网络地图输出为 jpeg 、 gif 、 png 、 SVG 、 KML 、pbf 等格式;能够运行在任何基于 J2EE/Servlet 容器之上;嵌入 MapBuilder 支持 AJAX 的地图客户端 OpenLayers;除此之外还包括许多其他的特性。
安装和配置
官方下载通道 点这里 为了配合我们的 mapbox-gl 使用,需要下载页面 Extensions 下方 Output Formats 里面的 Vector Tiles 插件,
- 插件:扩展插件下载之后放在 geoserver 安装目录\webapps\geoserver\WEB-INF\lib 里面
- 账号:缺省用户名/密码:admin/geoserver
- 端口:缺省 8080,修改在安装目录 start.ini 里面,jetty.port 可修改
- 启停: 开始菜单,start geoserver,或者安装目录/bin/startup.bat,
跨域:安装目录:./webapps/geoserver/WEB-INF/web.xml 放开两个跨域注释,如图所示:
服务发布
步骤
geoserver 一般操作步骤:
- 新建工作区,如果需要 wfs 服务,最好在工作区命名空间 URI 添写 www.openplans.org/ + 工作区名称,这不是一个真实地址,只是为了指向这个工作区
- 新建数据源,支持栅格、矢量等,矢量数据支持 PostGIS , Shapefile 文件以及文件夹,这里我们链接了一个上面所说的 PostGIS 数据库,取一个表作为一个图层
- 图层,根据数据源中的要素集发布为一个图层,几何类型为要素集的类型,图层会有属性表和数据源一致
- 图层边框设置为全幅或者当前数据边界即可,其它配置根据需求设置
- 保存即可
配置和查看
输出格式:在图层 Tile Caching 选项卡里面,默认为 png 和 jpg,勾选 application/vnd.mapbox-vector-tile,即 mapbox-gl 需要用到的矢量切片,如图所示:
查看:在数据里面 Layer Preview 找到相应的图层查看,或者在 Tile Caching, Tile Layers 里面查看即可
在 mapbox-gl 里面使用的地址:打开主页,右侧边打开 tms,找到相关图层在地址后面添加 zxy 即可
"http://localhost:6070/geoserver/gwc/service/tms/1.0.0/test:pip_point@EPSG:900913@pbf/{z}/{x}/{y}.pbf";
QGis
QGis 是一个开源的桌面软件,可以进行基本的地理信息显示、编辑和分析功能,现在我们在里面看一下 PostGis 里面的数据:
这里面有大概 15W 个管点,以及 15W 条管线,总共 30W 条数据。
mapbox-gl
简介
Mapbox GL JS 是一个 JavaScript 库,它使用 WebGL,以 vector tiles 和 Mapbox styles 为来源,将它们渲染成互动式地图。
安装和使用
API.
API 引入一般也是通过 CDN 和 npm 使用,下面搬运了部分常用的官方文档。(详情还请细读官方文档)
cdn
在线引用如下:
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link
href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet"
/>
npm
安装:
npm install --save mapbox-gl
使用: 需要额外引入 css 样式 mapbox-gl/dist/mapbox-gl.css,可在项目主要入口文件里面引入
import mapboxgl from "mapbox-gl";
基本使用
const map = new Mapboxgl.Map({
container: this.$refs.map,
center:[114.43001465651969, 23.039352283664215],
zoom: 15.5,
maxZoom: 24,
/* 允许导出绘制数据(截图) */
preserveDrawingBuffer: true,
/* 中日韩 CJK表意文字字体,去掉默认值(sans-serif) */
localIdeographFontFamily: false,
/* style里面声明了地图的数据源、图层和样式、以及底图资源等,是整个api的核心,贯穿整个mapbox, */
style: {
/* 现版本固定值 */
version: 8,
/* 雪碧图(地图图标) */
sprite: `${origin}${pathname}/mapbox/sprite/sprite`,
/* pbf格式字体 */
glyphs: `${origin}${pathname}/mapbox/fonts/{fontstack}/{range}.pbf`,
/* 数据源 */
sources: {...},
/* 图层样式 */
layers: [...]
}
})
sources
sources 声明了底图里面的数据源,常用的类型有 raster,vector 以及 geojson:
- raster 表示栅格数据即底图切片资源,例如我们的天地图,谷歌地图,天地图或者自己生成的规范切片
- vector 表示矢量切片,即由 geoserver 生成的 pbf 格式矢量切片,把我们上面介绍的地址拿过来用就行了
- geojson 和 vector 类似,只不过我们需要自己提供geojson格式的矢量数据,格式简单学习
layers
layers 声明了地图里面渲染的图层和样式:
- raster,只能由 raster 类型的数据源渲染
- fill/line/circle,对应着面/线/点数据,并进行基本的样式设定,样式类似 css 具体可参考官网
- symbol, 符号,一般用于点位,也可用于沿线连续图案以及面状数据中心位置,其中包括图标和文字,皆由外层 api 的 sprite 雪碧图和 glyphs 字体提供资源,图标和文字可同时存在,可以调整其位置关系、碰撞检测等
示例
... ...
sources: {
/* 栅格数据源(谷歌在线街道图) */
googleVec: {
type: 'raster',
tiles: [
'https://mt1.google.cn/vt/lyrs=m&x={x}&y={y}&z={z}',
'https://mt2.google.cn/vt/lyrs=m&x={x}&y={y}&z={z}',
'https://mt3.google.cn/vt/lyrs=m&x={x}&y={y}&z={z}',
]
},
/* 矢量切片(geoserver从postgis读取生成) */
pipLayer: {
type: "vector",
scheme: "tms",
tiles: [
"http://localhost:6070/geoserver/gwc/service/tms/1.0.0/test:pip_test@EPSG:900913@pbf/{z}/{x}/{y}.pbf"
]
},
/* geosjon 数据源(自己提供geojson格式数据)*/
testLine: {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "LineString",
coordinates: [
[104.0537, 30.65],
[104.0437, 30.56],
[104.0637, 30.66]
]
},
properties: {}
}
}
},
layers: [
/* 栅格图层 */
{
id: 'googleVec',
source: 'googleVec',
type: 'raster'
},
/* 矢量切片 */
{
id: "pointLayer",
type: "symbol",
source: "pointLineLayer",
minzoom: 16,
"source-layer": 'pip_point',
layout: {
"text-field": "{expNo}",
"text-size": 12,
"text-offset": [0, 1.5]
"icon-image": "default_icon",
"icon-size": 0.8,
},
/* 矢量切片/geojson -> 线状数据 */
{
id: "testLine",
source: "testLine",
type: "line",
paint: {
"line-width": 4,
"line-color": "#f00"
}
}
]
... ...
常用事件和交互操作
/* 鼠标事件,click/dbclick/mousemove...... */
map.on("click", ({ lngLat, point, ...rest }) => {
/* 当前鼠标位置经纬度 */
lngLat;
/* 当前鼠标位置相对坐标 */
point;
});
/* 获取当前中心位置 */
map.getCenter();
/* 动态设置样式 */
//map.setPaintProperty('layer', 'line-color', '#f00')
setPaintProperty(layerid, name, value);
//map.setLayoutProperty('layer', 'visibility', 'none')
setLayoutProperty(layerid, name, value);
/* 获取/设置数据源 */
//geojson: map.getSource(id).setData(newData)
map.getSource(id);
/* 动画过渡到某个位置 */
map.flyTo({
center: [x, y],
zoom: 18,
});
/**
* 获取已渲染(遮挡、隐藏等看不见的要素都不会获取)的要素
* @param {geometry} geom - 相对坐标几何,一个点或者相对坐标构成的box区域
* @param {Object} option - 可选项
* @param {string[]} option.layers - 查询包含的图层
* @param {expression} option.filter - 查询过滤表达式
* @return {feature[]} - 要素集
*/
queryRenderedFeatures(geom, { layers, filter });
/**
* 获取数据源数据(geojson,vector tile),当前屏幕范围内包括隐藏,遮挡等要素,(vector由于切片,可能返回多个片段)
* @param {string} id - 数据源名称
* @param {Object} option - 可选项
* @param {string} option.sourcelayer - 数据源里面某个图层(适用于vector)
* @param {expression} option.filter - 查询过滤表达式
* @return {feature[]} - 要素集
*/
querySourceFeatures(id, { sourcelayer, filter });
marker & popup
标记和弹出气泡,用于突出标点或者承载数据展示,可交由前端控制
expression
expression 较为复杂,用于 filter 过滤、paint 和 layout 里面参数条件
格式为一个数组,第一个值为命名表达式关键字,后续为参数,搭配嵌套使用,示例:
/* 当前要素类型等于点的要素 */
['==' , '$type', 'Point'],
/* 要素的name属性为caelan的要素 */
['==',
['get', 'name'],
'caelan'
]
/* 满足属性age大于18,并且属性name是['caelan', 'helen','caeser']里面的要素 */
['all',
['in',
['get', 'name'],
['literal', ['caelan', 'helen','caeser']]
],
['>', ['get', 'age'], 18]
]
/* 根据plCode属性来匹配现有配置颜色 */
const colors = {
JS: "rgb(0,255,255)",
JY: "rgb(0,255,255)",
SS: "rgb(0,255,255)",
ZS: "rgb(0,255,255)",
JP: "rgb(0,255,255)",
JZ: "rgb(0,255,255)",
XS: "rgb(0,255,255)",
LS: "rgb(0,255,255)",
JH: "rgb(0,255,255)",
PS: "rgb(76,57,38)",
YS: "rgb(76,57,38)",
......
}
const color = Object.entries(colors).flat();
... ...
paint: {
"line-width": 3,
"line-color": ["match", ["get", "plCode"], ...color, "#000"],
},
... ...
building-3d
实际是type类型为fill-extrusion的一个layer图层,数据源需要为面状数据,可设置高度扩展填充:
layers: [
... ...
{
"id": "building-3d",
"type": "fill-extrusion",
"source": "openmaptiles",
"source-layer": "building",
"filter": [
"all",
[
"!has",
"hide_3d"
]
],
"paint": {
"fill-extrusion-base": {
"property": "render_min_height",
"type": "identity"
},
"fill-extrusion-color": [
"case",
[
"has",
"colour"
],
[
"get",
"colour"
],
"hsl(39, 41%, 86%)"
],
"fill-extrusion-height": {
"property": "render_height",
"type": "identity"
},
"fill-extrusion-opacity": 0.6
}
}
... ...
]
底图
作为底图一般来说,在线各大厂商提供的栅格切片使用比较多,但是 OpenStreetMap 提供部分矢量切片作为底图,以下可供参考
OpenStreetMap
OpenStreetMap 支持矢量/栅格数据
矢量切片如果作为商用,好像 14 级以上需要收费,栅格地址, 矢量地址, mapbox 与 openmaptiles 结合的底图样式构造器 maptiler
以上需要登录,并生成个人 key 提供使用地址
各大厂商的资源
包括天地图、谷歌地图、高德等栅格瓦片,也可以用我们自己拍摄的地理正射影像转换的高精度谷歌标准或者 tms 规范的切片
谷歌切片标准算是 tms 规范的一个变种,谷歌切片从左上角开始,tms 从左下角开始,通过 Math.pow(2, z) - y - 1 可相互转换 Y 值,具体可以查询相关规范信息
以下整理一份栅格数据地址供参考
- 谷歌切片
http://mt2.google.cn/vt/lyrs=h&x={x}&y={y}&z={z}
, 其中参数:lyrs
标记 | 说明 |
---|---|
h | 注记图 |
m | 街道图 |
r | 街道简图(无交通路网) |
t | 地形图 |
p | 注记&地形图 |
s | 影像 |
y | 影像&标注 |
- 天地图 key 个人申请,
http://t0.tianditu.gov.cn/DataServer?T=vec_c&x={x}&y={y}&l={z}&tk=key
,其中参数: T
标记 | 说明 |
---|---|
vec_? | 矢量图 |
cva_? | 矢量图注记 |
img_? | 影像图 |
cia_? | 影像图注记 |
ter_? | 地形图(只有 16 级) |
cta_? | 地形图注记 |
? 为 c 时是经纬度,? 为 w 时是墨卡托
- 高德切片
http://wprd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x=53597&y=28455&z=16&scl=1<ype=2
,其中参数
style | ltype | lang | 说明 |
---|---|---|---|
6 | --- | --- | 影像图 |
7 | --- | zh_cn | 矢量+交通+注记 |
7 | 1 | zh_cn | 矢量图 |
7 | 2 | zh_cn | 交通图 |
7 | 3 | zh_cn | 矢量+交通 |
7 | 4 | zh_cn | 注记 |
7 | 5 | zh_cn | 矢量+注记 |
7 | 6 | zh_cn | 交通+注记 |
8 | 2 | --- | 交通(透明) |
8 | 4 | --- | 注记(透明) |
8 | 6 | --- | 矢量+注记(透明) |
资源配置
雪碧图
每个项目使用的图标不一致,这个一定要生产,或其他满足规范的生成也可
- 工具:mapbox 工具:spritezero-cli
- 环境:node v6/v8 (建议用 node 包管理工具 nvm)
- 使用:全局安装
npm install -g @mapbox/spritezero-cli
,然后spritezero [output filename][input directory]
- 输出是文件名字,输入是装有 svg 的文件夹
- 引用:url 指向生成的文件名字(json 和 png 是同名的)
- 注意:svg 的 viewbox 和高宽需要一致
pbf 字体
收集到了两种生产方式,满足 pbf 规范即可
genfontgl
- 工具:genfontgl
- 环境:linux,node v6
- 使用:全局安装 npm install -g genfontgl,然后 genfontgl xxx.ttf [output location]
- 输入是 ttf 格式字体,输出是 pbf 字体文件夹
- 引用:根据 mapbox ./{fontstack}/{range}.pbf 引用
node-fontnik
node-fontnik 是集成 genfontgl 的主要库
缺点:需要自己实现转换业务代码
优点:环境支持 node v8 - v14,64 bit OS X or 64 bit Linux
转换代码如下:(代码引用地址)
var fontnik = require("./node-fontnik");
var fs = require("fs");
var path = require("path");
var convert = function(fileName, outputDir) {
var font = fs.readFileSync(path.resolve(__dirname + "/" + fileName));
output2pbf(font, 0, 255, outputDir);
};
function output2pbf(font, start, end, outputDir) {
if (start > 65535) {
console.log("done!");
return;
}
fontnik.range({ font: font, start: start, end: end }, function(err, res) {
var outputFilePath = path.resolve(
__dirname + "/" + outputDir + start + "-" + end + ".pbf"
);
fs.writeFile(outputFilePath, res, function(err) {
if (err) {
console.error(err);
} else {
output2pbf(font, end + 1, end + 1 + 255, outputDir);
}
});
});
}
convert("./xx.ttf", "./xx/");
以上
我只是搬运工
希望可以和大家一起学习和讨论
相关扩展:
- AntV L7 , 大规模地理数据可视化引擎,完美结合mapbox-gl,几行代码就可以做出炫酷的效果
- pbf (protocol buffer) 谷歌开源的结构化数据交互格式
- wfs 规范 (web feature service) 可通过 rest 直接与数据库交换数据
- QGis, ArcMap 等,地理数据处理
- webgl,three.js 等,可在 mapbox-gl 继承构建高性能图层和三维场景