前言:
我接触前端已经两年了,有一年的实习经历,对于前端方面的html、css、js、vue、react都比较熟悉,做过些后台管理系统的业务。但是对于GIS就不太了解了,最近在面试webgis的前端岗位,所以想去了解从事webgis开发需要掌握哪些技术。
这一块的知识很多且杂,我也不能逮着一个技术就深入去学,那样太耗费时间。所以我的目标是对webgis的概念、应用场景、技术栈有个了解,并解决学习过程中遇到的问题。
我努力想将本文写成“综述”一类的东西,希望能给和我有相同境遇的人提供些参考。
正文
一、我需要学些什么?
GIS即地理信息系统, webgis即将前端可视化技术与 GIS 技术结合,提供更好的信息展示和用户交互。所以对于前端开发人员就是做”获取数据、展示数据“两件事,这点和以往做得项目是共通的。
前端可视化技术涉及有canvas、webGL、计算机图形学,这一块我觉得是可以深入学习的,因为它较为底层,有益于自己在这一行深耕;而且就算不能从事webgis,还可以转到数据可视化。
GIS开发框架如下,这些框架都是用js语言开发的,所以比较容易上手的,而且都是调用API,经过一段时间的使用应该就能掌握了。
- 二维GIS(openlayers MapBox leaflet)
- 三维GIS(cesium)
当然gis相关的知识也是很重要的,毕竟是一个独立的专业嘛!
这里将web开发和webgis开发做一个比较,可以看到webgis开发时多了GIS数据库和GIS服务器。这也好理解,毕竟地理信息很丰富,而且一些数据资源应该是很机密的,所以被垄断在专门的GIS数据库和服务器商手里。但是也不乏有些开放的平台信息,如高德地图API。一般的小公司出于成本考虑,应该是用第三方的数据。
GIS数据库
GIS服务器:Web GIS架构中最重要的部分,决定了能提供的服务
二、Web GIS 的应用:
那么webgis开发的产品形式是什么样的呢?我知道后台管理系统是怎么回事,对于这个,还不太懂呢?就像你要去做一辆车,最好得先知道车是什么样的呀?
- Web GIS + 应急
- Web GIS + 环保
- Web GIS + 智慧城市
从上图可以看到,就是开发一些应用,当然载体有web浏览器,移动端,或是桌面软件。
但是正如第一部分提到的,webgis的生态位上还涉及到各种框架、提供第三方服务的机构,所以可做的事情不局限于调用API去开发系统。但是就我来说,我还是喜欢贴近底层用户,做些高性能、炫酷的页面,开发出可用的系统来,这也是我选择前端开发的原因。当然,随着学习的深入,我也会慢慢向生态位的上游靠拢。
三、Web GIS 的发展趋势以及对应的难点
- 实时数据吞吐能力、大数据分析、实时分析
- 从二维地图转向三维地图,而且三维展示更形象。这一点要得益于webGL、缓存技术的发展,使得浏览器可以平滑地展示三维数据
- VR与AR展示更加逼真地展示地物
数据多了问题就来了,由此而来的GIS服务器访问压力、网络传输带宽压力、客户端渲染能力的问题,其实哪怕简单的增删改查,只要数据量一多都要考虑到这些问题,从而要求做出优化,如web缓存、算法优化、合理分配服务器和客户端的工作量。
四、基于高德地图的webgis案例——《智慧校园》 体验链接
这个案例很简单,仅涉及到js+高德地图提供的API+GeoJSON。
简单介绍下GeoJSON:其实质就是我们熟悉的json,使用 json 数据格式来保存地理信息,因为要配合相应的 api 使用——如高德地图中的 AMap.GeoJSON ,所以有些字段是限定的。将数据存为 GeoJSON 格式,再保存到数据库或文件中,实现数据的持久化。
功能:可以用鼠标在地图上标记点,自动规划出路程最短的校园游览路线;点击标记点,可以打卡并记录次数
案例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智慧校园</title>
<!-- 引入css资源 -->
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
<style>
body,
html,
#container {
width: 100%;
height: 100%;
}
</style>
<!-- 引入js资源,这里需要预先去高德平台申请key -->
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: '014b3a524fc1fdb97c989671801bae00',
}
</script>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=688698070791bd5d966f8dc8656803f0"></script>
</head>
<body>
<div id="container"></div>
<div class="info">标记校园景点,规划游玩路线</div>
<div class="input-card" style="width:10rem; height:10rem">
<h4>推荐浏览路线</h4>
<div class="input-item">
<button class="btn" onclick="startAnimation()" style="margin-bottom: 15px">显示路线</button>
<button class="btn" onclick="deleteData()">清除标记</button>
</div>
</div>
<script>
// 创建地图对象
let map = new AMap.Map('container', {
center: [118.715422, 32.203426],
zoom: 16,
viewMode: '3D',
pitch: 45
})
// 使用控件
// 同时引入工具条插件,比例尺插件和鹰眼插件
AMap.plugin([
'AMap.ToolBar',
'AMap.Scale',
'AMap.ControlBar',
'AMap.GeoJSON',
'AMap.MoveAnimation'
], function () {
map.addControl(new AMap.ToolBar({
position: {
top: '80px',
right: '40px'
}
}))
map.addControl(new AMap.Scale())
map.addControl(new AMap.ControlBar())
})
// 从localStorage中读取数据
function getData () {
if (!localStorage.getItem('geojson')) {
saveData([])
}
return localStorage.getItem('geojson')
}
// 保存数据到localStorage中
function saveData (data) {
localStorage.setItem('geojson', JSON.stringify(data))
}
let driving
function deleteData () {
localStorage.removeItem('geojson')
map.clearMap()
driving.clear()
}
// 定义一个全局变量,保存geojson
let geojson = new AMap.GeoJSON({
geojson: null
})
if (getData() !== '[]') {
// 导入数据
geojson.importData(JSON.parse(getData()))
// 恢复旧数据的点击事件
geojson.eachOverlay((item) => {
item.on('click', (e) => {
let ext = item.getExtData()
//click用于统计点击次数
let click = ++ext._geoJsonProperties.click
// 使用信息提示框显示
let infowindow = new AMap.InfoWindow({
anchor: 'top-center',
content: `<div>打卡了${click}次</div>`
})
infowindow.open(map, item.getPosition())
// 保存数据
saveData(geojson.toGeoJSON())
})
})
}
map.add(geojson)
// 监听点击事件
map.on('click', (e) => {
let marker = new AMap.Marker({
position: e.lnglat,
extData: {
_geoJsonProperties: {
gid: geojson.getOverlays().length + 1,
click: 0
}
}
})
// 使用覆盖物的点击事件
marker.on('click', function (e) {
let ext = marker.getExtData()
//click用于统计点击次数
let click = ++ext._geoJsonProperties.click
// 使用信息提示框显示
let infowindow = new AMap.InfoWindow({
anchor: 'top-center',
content: `打卡了${click}次`
})
infowindow.open(map, marker.getPosition())
// 保存数据
saveData(geojson.toGeoJSON())
})
// 通过geojson对象来管理覆盖物
geojson.addOverlay(marker)
// 保存数据
saveData(geojson.toGeoJSON())
})
function startAnimation () {
// 实现路径规划
AMap.plugin('AMap.Driving', function () {
driving = new AMap.Driving({
// 驾车策略,用时最少
policy: AMap.DrivingPolicy.REAL_TRAFFIC,
map: map
})
// 设置起止点
let start = new AMap.LngLat(118.724521, 32.202509)
let end = new AMap.LngLat(118.708021, 32.205772)
// 得到途经点的经纬度
let opts = {
waypoints: [],
}
geojson.eachOverlay(function (item) {
opts.waypoints.push(item.getPosition())
})
driving.search(start, end, opts, function (status, result) {
if (status === 'complete') {
// 实现轨迹模拟
console.log(result)
let marker = new AMap.Marker({
map: map,
position: start,
icon: "https://webapi.amap.com/images/car.png",
offset: new AMap.Pixel(-26, -13),
autoRotation: true,
angle: 180,
})
let passedPolyline = new AMap.Polyline({
map: map,
strokeColor: '#af5',
strokeWeight: 6,
})
marker.on('moving', function (e) {
passedPolyline.setPath(e.passedPath)
})
map.setFitView()
// 获取经过的路径
let lineArr = []
result.routes[0].steps.forEach((item) => {
lineArr.push(...item.path)
})
marker.moveAlong(lineArr, {
duration: 100,
autoRotation: true
})
} else {
console.log('error')
}
})
})
}
</script>
</body>
</html>