使用高德API和MapboxGL实现路径规划并语音播报

分类: mobile365体育投注下载 时间: 2026-10-01 12:34:03 作者: admin

概述本文使用高德API实现位置查询和路径规划,使用MapboxGL完成地图交互与界面展示,并使用Web Speech API实现行驶中路线的实时语音播报。

效果image.pngimage.pngWeb Speech API简介Web Speech API 使你能够将语音数据合并到 Web 应用程序中。Web Speech API 有两个部分:SpeechSynthesis 语音合成(文本到语音 TTS)和 SpeechRecognition 语音识别(异步语音识别)。

语音识别通过 SpeechRecognition接口进行访问,它提供了识别从音频输入(通常是设备默认的语音识别服务)中识别语音情景的能力。一般来说,你将使用该接口的构造函数来构造一个新的 SpeechRecognition对象,该对象包含了一系列有效的对象处理函数来检测识别设备麦克风中的语音输入。SpeechGrammar 接口则表示了你应用中想要识别的特定文法。文法则通过 JSpeech Grammar Format (JSGF.) 来定义。语音合成通过SpeechSynthesis接口进行访问,它提供了文字到语音(TTS)的能力,这使得程序能够读出它们的文字内容(通常使用设备默认的语音合成器)。不同的声音类类型通过SpeechSynthesisVoice对象进行表示,不同部分的文字则由 SpeechSynthesisUtterance 对象来表示。你可以将它们传递给 SpeechSynthesis.speak()方法来产生语音。SpeechSynthesisUtterance是HTML5中新增的API,用于将指定文字合成为对应的语音。它包含一些配置项,可以指定如何去阅读(如语言、音量、音调等)。

简单使用示例如下代码:

代码语言:javascript复制// 创建 SpeechSynthesisUtterance 对象

var utterance = new SpeechSynthesisUtterance();

// 可选:设置语言(例如,中文)

utterance.lang = "zh-CN";

// 可选:设置语音

utterance.voice = window.speechSynthesis.getVoices()[0];

// 可选:设置音量(0.0到1.0之间)

utterance.volume = 1.0;

// 可选:设置语速(正常速度为1.0)

utterance.rate = 1.0;

// 可选:设置语调(正常语调为1.0)

utterance.pitch = 1.0;

// 设置要朗读的文本

utterance.text = '设置要朗读的文本';

window.speechSynthesis.speak(utterance);实现实现思路地图初始化的时候通过H5的geolocation接口获取当前位置;调用rgeo接口,根据获取到的位置获取位置所在市;调用inputtips接口完成关键词联想查询;调用/direction/driving接口完成路径的规划;用MapboxGL实现地图交互与路径展示;根据当前位置判断是否进入对应的步骤,提示对应的语音。实现代码示例使用Vue作为演示,界面与地图初始化代码如下:

代码语言:javascript复制

示例中使用轨迹播放的方式演示了位置的变化,前文mapboxGL轨迹展示与播放已经有过分享,示例在前文的基础上做了一点改动,改动完代码如下:

代码语言:javascript复制const icon = "/imgs/car.png";

const arrow = "/imgs/arrow.png";

import * as turf from "@turf/turf";

class AnimationRoute {

constructor(map, route, play = true, fit = true, speed = 60) {

this._map = map;

this._json = route;

this._play = play;

this._speed = speed;

this._path = route.properties.path;

this.init();

if (fit) this._map.fitBounds(turf.bbox(route), { padding: 50 });

}

init() {

const that = this;

// 创建 SpeechSynthesisUtterance 对象

var utterance = new SpeechSynthesisUtterance();

// 可选:设置语言(例如,中文)

utterance.lang = "zh-CN";

// 可选:设置语音

utterance.voice = window.speechSynthesis.getVoices()[0];

// 可选:设置音量(0.0到1.0之间)

utterance.volume = 1.0;

// 可选:设置语速(正常速度为1.0)

utterance.rate = 1.0;

// 可选:设置语调(正常语调为1.0)

utterance.pitch = 1.0;

that.utterance = utterance;

that._index = 0;

const length = turf.length(that._json);

const scale = 60;

that._count = Math.round((length / that._speed) * 60 * 60) * scale;

that._step = length / that._count;

that._stepPlay = -1;

that._flag = 0;

that._playId = "play-" + Date.now();

// 添加路径图层

that._map.addSource(that._playId, {

type: "geojson",

data: that._json,

});

that._map.addLayer({

id: that._playId,

type: "line",

source: that._playId,

layout: {

"line-cap": "round",

"line-join": "round",

},

paint: {

"line-color": "#aaaaaa",

"line-width": 10,

},

});

// 添加已播放路径

that._map.addSource(that._playId + "-played", {

type: "geojson",

data: that._json,

});

that._map.addLayer({

id: that._playId + "-played",

type: "line",

source: that._playId + "-played",

layout: {

"line-cap": "round",

"line-join": "round",

},

paint: {

"line-color": "#09801a",

"line-width": 10,

},

});

// 添加路径上的箭头

that._map.loadImage(arrow, function (error, image) {

if (error) throw error;

that._map.addImage(that._playId + "-arrow", image);

that._map.addLayer({

id: that._playId + "-arrow",

source: that._playId,

type: "symbol",

layout: {

"symbol-placement": "line",

"symbol-spacing": 50,

"icon-image": that._playId + "-arrow",

"icon-size": 0.6,

"icon-allow-overlap": true,

},

});

});

// 添加动态图标

that._map.loadImage(icon, function (error, image) {

if (error) throw error;

that._map.addImage(that._playId + "-icon", image);

that._map.addSource(that._playId + "-point", {

type: "geojson",

data: that._getDataByCoords(),

});

that._map.addLayer({

id: that._playId + "-point",

source: that._playId + "-point",

type: "symbol",

layout: {

"icon-image": that._playId + "-icon",

"icon-size": 0.75,

"icon-allow-overlap": true,

"icon-rotation-alignment": "map",

"icon-pitch-alignment": "map",

"icon-rotate": 50,

},

});

that._animatePath();

});

}

pause() {

this._play = false;

window.cancelAnimationFrame(this._flag);

}

start() {

this._index = 0;

this.play();

}

play() {

this._play = true;

this._animatePath();

}

_animatePath() {

if (this._index > this._count) {

window.cancelAnimationFrame(this._flag);

} else {

const coords = turf.along(this._json, this._step * this._index).geometry

.coordinates;

// 已播放的线

const start = turf.along(this._json, 0).geometry.coordinates;

this._map

.getSource(this._playId + "-played")

.setData(turf.lineSlice(start, coords, this._json));

// 车的图标位置

this._map

.getSource(this._playId + "-point")

.setData(this._getDataByCoords(coords));

// 计算旋转角度

const nextIndex =

this._index === this._count ? this._count - 1 : this._index + 1;

const coordsNext = turf.along(this._json, this._step * nextIndex).geometry

.coordinates;

let angle = turf.bearing(turf.point(coords), turf.point(coordsNext)) - 90;

if (this._index === this._count) angle += 180;

this._map.setLayoutProperty(

this._playId + "-point",

"icon-rotate",

angle

);

const camera = this._map.getFreeCameraOptions();

camera.position = mapboxgl.MercatorCoordinate.fromLngLat(coords, 100);

camera.lookAtPoint(coordsNext);

this._map.setFreeCameraOptions(camera);

this._map.setPitch(80);

this._index++;

if (this._play) {

this._playInstruction(coords);

this._flag = requestAnimationFrame(() => {

this._animatePath();

});

}

}

}

_playInstruction(coords) {

const { steps } = this._path;

const stepPlay = this._stepPlay;

const start = this._stepPlay !== -1 ? this._stepPlay : 0

for (let i = start; i < steps.length; i++) {

const step = steps[i];

const polyline = step.polyline

.split(";")

.map((v) => v.split(",").map(Number));

const pt = turf.point(coords);

const line = turf.lineString(polyline);

const dis = turf.pointToLineDistance(pt, line) * 1000;

if (i > this._stepPlay && dis < 10) {

this._stepPlay = i;

break;

}

}

if (stepPlay !== this._stepPlay) {

this.utterance.text = steps[this._stepPlay].instruction;

window.speechSynthesis.speak(this.utterance);

}

}

_getDataByCoords(coords) {

if (!coords || coords.length !== 2) return null;

return turf.point(coords, {

label: this._formatDistance(this._step * this._index),

});

}

_formatDistance(dis) {

if (dis < 1) {

dis = dis * 1000;

return dis.toFixed(0) + "米";

} else {

return dis.toFixed(2) + "千米";

}

}

destory() {

window.cancelAnimationFrame(this._flag);

if (this._map.getSource(this._playId + "-point")) {

this._map.removeLayer(this._playId + "-point");

this._map.removeSource(this._playId + "-point");

}

if (this._map.getSource(this._playId + "-played")) {

this._map.removeLayer(this._playId + "-played");

this._map.removeSource(this._playId + "-played");

}

if (this._map.getSource(this._playId)) {

this._map.removeLayer(this._playId);

this._map.removeLayer(this._playId + "-arrow");

this._map.removeSource(this._playId);

}

}

}

export default AnimationRoute;