# 示例:最快路线计算
通过指定起点和终点(也可以添加一些途经点),计算一条或多条行进最快的路线。
# 1. 搭建页面框架
首先我们创建一个名为 network-paths.html
的页面,这里我们引入 OpenLayers,在页面上显示一个地图组件,并加载一个开放底图:
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css">
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body>
<div id="app">
<div id="map" style="width: 600px; height: 600px"></div>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
map: null,
markerLayer: null,
markerStyle: null,
resultLayer: null,
},
mounted: function() {
this.markerLayer = new ol.layer.Vector({
source: new ol.source.Vector(),
});
this.markerStyle = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1.0],
src: '../../../../assets/guide/icon-pin.png',
}),
});
this.resultLayer = new ol.layer.Vector({
source: new ol.source.Vector(),
});
this.map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM(),
}),
this.resultLayer,
this.markerLayer,
],
view: new ol.View({
center: ol.proj.fromLonLat([7.4126, 43.7407]),
zoom: 14,
}),
});
}
});
</script>
</body>
</html>
# 2. 响应鼠标点击事件
然后,我们在鼠标点击的时候,在地图上添加代表位置的图标。在这个例子里,我们只设置起点和终点,如果超过两个点就清空地图重新开始:
this.map.on('click', function(evt) {
const markerSource = this.markerLayer.getSource();
const markerCount = markerSource.getFeatures().length;
if (markerCount > 1) {
markerSource.clear();
}
const { coordinate } = evt;
const feature = new ol.Feature({
geometry: new ol.geom.Point(coordinate),
});
feature.setStyle(this.markerStyle);
markerSource.addFeature(feature);
}.bind(this));
这时,页面会随着鼠标点击,会显示点击的位置:
# 3. 计算两点间的最快路径
当地图上存在两个点的时候,调用 API 计算这两个点之间的最快路径:
const features = markerSource.getFeatures();
if (features.length == 2) {
const from = features[0].getGeometry();
const to = features[1].getGeometry();
fetch('http://localhost:9000/heycloud/api/routing/network/sample/paths', {
method: 'POST',
mode: 'cors',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
'points': [
from.getFirstCoordinate(),
to.getFirstCoordinate(),
],
'sr': 'web-mercator',
'alternatives': 2,
}),
})
.then(resp => resp.json())
.then(resp => {
const { waypoints, routes } = resp.result;
});
}
在这里,我们需要将起点和终点的坐标传入points
参数,同时指定了最多返回两条备选路线。在 API 调用结果中,我们能够得到设定点对应的道路节点waypoints
以及计算后的路线routes
。然后,我们可以将这些对象显示到地图上:
const { waypoints, routes } = resp.result;
const image = new ol.style.Circle({
radius: 4,
stroke: new ol.style.Stroke({
color: '#f00',
width: 4,
}),
fill: new ol.style.Fill({
color: '#fff',
}),
});
const waypointStyle = new ol.style.Style({
image,
});
const routeStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#f00',
width: 4,
}),
});
const routeAlterStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#449',
width: 4,
lineDash: [6, 12],
}),
});
for (let i = routes.length - 1; i >= 0; --i) {
const route = routes[i];
for (let leg of route.properties.legs) {
for (let step of leg.steps) {
const stepFeature = new ol.format.GeoJSON().readFeature(step);
if (i == 0) {
stepFeature.setStyle(routeStyle);
} else {
stepFeature.setStyle(routeAlterStyle);
}
resultSource.addFeature(stepFeature);
}
}
}
for (let waypoint of waypoints) {
const feature = new ol.format.GeoJSON().readFeature(waypoint);
feature.setStyle(waypointStyle);
resultSource.addFeature(feature);
}
这样的页面,地图显示的效果如下:
# 4. 显示导航建议
路线计算的结果中,还包含导航建议信息,下面我们在图上显示这些建议。导航建议信息存储在 step 对象内部,在这个例子中,我们只挑选转弯、环岛环路信息进行显示:
const stepManeuverFeature = new ol.format.GeoJSON().readFeature(step.properties.maneuver);
const type = stepManeuverFeature.get('type');
const modifier = stepManeuverFeature.get('modifier');
if (['turn', 'roundabout', 'exit roundabout', 'rotary', 'exit rotary'].includes(type)) {
resultSource.addFeature(stepManeuverFeature);
}
接下来我们给这些对象添加鼠标响应事件,我们希望在鼠标移动到这些对象上的时候,它们会改变样式:
this.map.on('pointermove', function(evt) {
let hit = false;
this.map.forEachFeatureAtPixel(evt.pixel, function(f) {
hit = true;
const type = f.get('type');
if (['turn', 'roundabout', 'exit roundabout', 'rotary', 'exit rotary'].includes(type)) {
if (this.selected) {
this.selected.setStyle(undefined);
this.selected = null;
}
this.selected = f;
const image = new ol.style.Circle({
radius: 4,
stroke: new ol.style.Stroke({
color: '#000',
width: 4,
}),
fill: new ol.style.Fill({
color: '#fff',
}),
});
const selectStyle = new ol.style.Style({
image,
});
f.setStyle(selectStyle);
return true;
}
}.bind(this));
this.map.getViewport().style.cursor = hit ? 'pointer' : '';
}.bind(this));
同时,我们还可以在图上显示文本提示信息:
<div id="app" style="position: relative;">
<div id="map" style="width: 600px; height: 600px"></div>
<div v-if="hint" style="position: absolute; left: 4px; bottom: 4px; background: #fff; border-radius: 4px; padding: 4px; font-size: 0.8em; color: #666">{{ hint }}</div>
</div>
var app = new Vue({
computed: {
hint: function() {
const f = this.selected;
if (!f) {
return;
}
const type = f.get('type');
const modifier = f.get('modifier');
if (type == 'turn') {
switch (modifier) {
case 'uturn':
return '掉头';
case 'sharp right':
return '大右转';
case 'right':
return '右转';
case 'slight right':
return '小右转';
case 'straight':
return '直行';
case 'sharp left':
return '大左转';
case 'left':
return '左转';
case 'slight left':
return '小左转';
}
} else if (type == 'roundabout') {
return '进入环岛';
} else if (type == 'exit roundabout') {
return '离开环岛';
} else if (type == 'rotary') {
return '进入环路';
} else if (type == 'exit rotary') {
return '离开环路';
}
},
},
});
这时,当鼠标移动到导航提示点上时,你可以看到提示点切换了显示样式;同时,在左下方还显示了当前的提示信息: