目录
[1 效果图](#1 效果图)
[2 简易功能](#2 简易功能)
[3 源码目录](#3 源码目录)
[4 源码](#4 源码)
1 效果图

2 简易功能
(1)QML UI构图
(2)语音播报
(3)路线规划
3 源码目录
工程使用QT 5.12 MINGW64编译

4 源码
cpp
QT += quick quickcontrols2 multimedia positioning network texttospeech
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Refer to the documentation for the
# deprecated API to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
MetroGuideCore.cpp \
main.cpp \
routeplanner.cpp \
voiceannouncer.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
MetroGuideCore.h \
routeplanner.h \
voiceannouncer.h
main.cpp
cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "MetroGuideCore.h"
#include "VoiceAnnouncer.h"
#include "RoutePlanner.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<MetroGuideCore>("MetroGuide", 1, 0, "MetroGuideCore");
qmlRegisterType<VoiceAnnouncer>("MetroGuide", 1, 0, "VoiceAnnouncer");
qmlRegisterType<RoutePlanner>("MetroGuide", 1, 0, "RoutePlanner");
QQmlApplicationEngine engine;
// 创建核心对象并暴露给QML
MetroGuideCore metroGuide;
VoiceAnnouncer voiceAnnouncer;
RoutePlanner routePlanner;
// 设置路径规划器的数据源
routePlanner.setMetroData(&metroGuide);
engine.rootContext()->setContextProperty("metroGuide", &metroGuide);
engine.rootContext()->setContextProperty("voiceAnnouncer", &voiceAnnouncer);
engine.rootContext()->setContextProperty("routePlanner", &routePlanner);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
MetroGuideCore.cpp
cpp
#include "MetroGuideCore.h"
#include <QDebug>
MetroGuideCore::MetroGuideCore(QObject *parent)
: QObject(parent)
, m_currentStationIndex(0)
{
initializeMetroData();
}
void MetroGuideCore::initializeMetroData()
{
// 定义线路颜色
lineColors["1号线"] = QColor(220, 20, 60); // 红色
lineColors["2号线"] = QColor(50, 205, 50); // 绿色
lineColors["5号线"] = QColor(255, 165, 0); // 橙色
lineColors["10号线"] = QColor(138, 43, 226); // 紫罗兰
// 添加站点数据 (根据实际地铁线路数据)
// 1号线站点
addStation("苹果园", "1号线", 100, 300);
addStation("古城", "1号线", 180, 300);
addStation("八角游乐园", "1号线", 260, 300);
addStation("八宝山", "1号线", 340, 300);
addStation("玉泉路", "1号线", 420, 300);
addStation("万寿路", "1号线", 500, 300);
addStation("公主坟", "1号线", 580, 300);
addStation("军事博物馆", "1号线", 660, 300);
addStation("木樨地", "1号线", 740, 300);
addStation("南礼士路", "1号线", 820, 300);
addStation("复兴门", "1号线", 900, 300);
// 2号线站点(环形)
addStation("西直门", "2号线", 900, 140);
addStation("车公庄", "2号线", 900, 200);
addStation("阜成门", "2号线", 900, 250);
addStation("复兴门", "2号线", 900, 300); // 换乘站
addStation("长椿街", "2号线", 900, 430);
addStation("前门", "2号线", 500, 550);
addStation("和平门", "2号线", 380, 550);
addStation("宣武门", "2号线", 260, 550);
addStation("长椿街", "2号线", 140, 550);
addStation("复兴门", "2号线", 20, 550);
// 标记换乘站
for (int i = 0; i < m_stations.size(); ++i) {
if (m_stations[i].name == "复兴门" && m_stations[i].line == "1号线") {
m_stations[i].isTransfer = true;
m_stations[i].transferLines << "2号线";
}
if (m_stations[i].name == "复兴门" && m_stations[i].line == "2号线") {
m_stations[i].isTransfer = true;
m_stations[i].transferLines << "1号线";
}
if (m_stations[i].name == "西直门") {
m_stations[i].isTransfer = true;
m_stations[i].transferLines << "13号线";
}
if (m_stations[i].name == "前门") {
m_stations[i].isTransfer = true;
m_stations[i].transferLines << "8号线";
}
}
}
void MetroGuideCore::addStation(const QString& name, const QString& line, double x, double y)
{
Station station(name, line, x, y);
int stationIndex = m_stations.size();
m_stations.append(station);
// 添加到线路
int lineIndex = -1;
for (int i = 0; i < m_lines.size(); ++i) {
if (m_lines[i].name == line) {
lineIndex = i;
break;
}
}
if (lineIndex == -1) {
Line newLine;
newLine.name = line;
newLine.color = lineColors[line];
m_lines.append(newLine);
lineIndex = m_lines.size() - 1;
}
m_lines[lineIndex].stationIndices.append(stationIndex);
}
QString MetroGuideCore::currentStationName() const
{
if (m_currentStationIndex >= 0 && m_currentStationIndex < m_stations.size()) {
return m_stations[m_currentStationIndex].name;
}
return "";
}
QString MetroGuideCore::directionText() const
{
if (m_currentStationIndex < m_stations.size() - 1) {
return "方向:开往 " + m_stations[m_currentStationIndex + 1].name;
}
return "终点站";
}
QString MetroGuideCore::nextStationText() const
{
if (m_currentStationIndex < m_stations.size() - 1) {
return "下一站:" + m_stations[m_currentStationIndex + 1].name;
} else if (m_currentStationIndex == m_stations.size() - 1) {
return "终点站:" + m_stations[m_currentStationIndex].name;
}
return "";
}
QString MetroGuideCore::getStationName(int index) const
{
if (index >= 0 && index < m_stations.size()) {
return m_stations[index].name;
}
return "";
}
double MetroGuideCore::getStationX(int index) const
{
if (index >= 0 && index < m_stations.size()) {
return m_stations[index].x;
}
return 0;
}
double MetroGuideCore::getStationY(int index) const
{
if (index >= 0 && index < m_stations.size()) {
return m_stations[index].y;
}
return 0;
}
bool MetroGuideCore::isTransferStation(int index) const
{
if (index >= 0 && index < m_stations.size()) {
return m_stations[index].isTransfer;
}
return false;
}
QStringList MetroGuideCore::getTransferLines(int index) const
{
if (index >= 0 && index < m_stations.size()) {
return m_stations[index].transferLines;
}
return QStringList();
}
int MetroGuideCore::getStationCount() const
{
return m_stations.size();
}
int MetroGuideCore::getLineCount() const
{
return m_lines.size();
}
QString MetroGuideCore::getLineName(int lineIndex) const
{
if (lineIndex >= 0 && lineIndex < m_lines.size()) {
return m_lines[lineIndex].name;
}
return "";
}
QString MetroGuideCore::getLineColor(int lineIndex) const
{
if (lineIndex >= 0 && lineIndex < m_lines.size()) {
return m_lines[lineIndex].color.name();
}
return "#000000";
}
int MetroGuideCore::getLineStationCount(int lineIndex) const
{
if (lineIndex >= 0 && lineIndex < m_lines.size()) {
return m_lines[lineIndex].stationIndices.size();
}
return 0;
}
int MetroGuideCore::getLineStationIndex(int lineIndex, int stationPos) const
{
if (lineIndex >= 0 && lineIndex < m_lines.size() &&
stationPos >= 0 && stationPos < m_lines[lineIndex].stationIndices.size()) {
return m_lines[lineIndex].stationIndices[stationPos];
}
return -1;
}
void MetroGuideCore::nextStation()
{
if (m_currentStationIndex < m_stations.size() - 1) {
setCurrentStationIndex(m_currentStationIndex + 1);
}
}
void MetroGuideCore::previousStation()
{
if (m_currentStationIndex > 0) {
setCurrentStationIndex(m_currentStationIndex - 1);
}
}
void MetroGuideCore::setCurrentStationIndex(int index)
{
if (index != m_currentStationIndex && index >= 0 && index < m_stations.size()) {
m_currentStationIndex = index;
emit currentStationChanged();
}
}
MetroGuideCore.h
cpp
#ifndef METROGUIDECORE_H
#define METROGUIDECORE_H
#include <QObject>
#include <QVector>
#include <QStringList>
#include <QMap>
#include <QColor>
struct Station {
QString name;
QString line;
double x; // 屏幕坐标X
double y; // 屏幕坐标Y
bool isTransfer; // 是否为换乘站
QStringList transferLines; // 换乘线路列表
Station() : x(0), y(0), isTransfer(false) {}
Station(const QString& n, const QString& l, double px, double py)
: name(n), line(l), x(px), y(py), isTransfer(false) {}
};
struct Line {
QString name;
QColor color;
QVector<int> stationIndices; // 站点索引列表
};
class MetroGuideCore : public QObject
{
Q_OBJECT
Q_PROPERTY(int currentStationIndex READ currentStationIndex WRITE setCurrentStationIndex NOTIFY currentStationChanged)
Q_PROPERTY(QString currentStationName READ currentStationName NOTIFY currentStationChanged)
Q_PROPERTY(QString directionText READ directionText NOTIFY currentStationChanged)
Q_PROPERTY(QString nextStationText READ nextStationText NOTIFY currentStationChanged)
public:
explicit MetroGuideCore(QObject *parent = nullptr);
void addStation(const QString& name, const QString& line, double x, double y);
int currentStationIndex() const { return m_currentStationIndex; }
QString currentStationName() const;
QString directionText() const;
QString nextStationText() const;
Q_INVOKABLE QString getStationName(int index) const;
Q_INVOKABLE double getStationX(int index) const;
Q_INVOKABLE double getStationY(int index) const;
Q_INVOKABLE bool isTransferStation(int index) const;
Q_INVOKABLE QStringList getTransferLines(int index) const;
Q_INVOKABLE int getStationCount() const;
Q_INVOKABLE int getLineCount() const;
Q_INVOKABLE QString getLineName(int lineIndex) const;
Q_INVOKABLE QString getLineColor(int lineIndex) const;
Q_INVOKABLE int getLineStationCount(int lineIndex) const;
Q_INVOKABLE int getLineStationIndex(int lineIndex, int stationPos) const;
Q_INVOKABLE void nextStation();
Q_INVOKABLE void previousStation();
signals:
void currentStationChanged();
public slots:
void setCurrentStationIndex(int index);
private:
void initializeMetroData();
int m_currentStationIndex;
QVector<Station> m_stations;
QVector<Line> m_lines;
// 线路颜色预设
QMap<QString, QColor> lineColors;
};
#endif // METROGUIDECORE_H
routeplanner.cpp
cpp
#include "RoutePlanner.h"
#include "MetroGuideCore.h"
#include <QQueue>
#include <QDebug>
#include <QMetaObject>
#include <QMetaProperty>
#include <QtMath>
RoutePlanner::RoutePlanner(QObject *parent) : QObject(parent)
{
}
void RoutePlanner::setMetroData(QObject *metroCore)
{
m_metroCore = metroCore;
buildGraph();
}
void RoutePlanner::buildGraph()
{
if (!m_metroCore) return;
MetroGuideCore *core = qobject_cast<MetroGuideCore*>(m_metroCore);
if (!core) return;
m_graph.clear();
m_lineStations.clear();
m_stationPositions.clear();
// 构建线路和站点关系图
for (int i = 0; i < core->getLineCount(); ++i) {
QString lineName = core->getLineName(i);
QStringList stations;
for (int j = 0; j < core->getLineStationCount(i); ++j) {
int stationIdx = core->getLineStationIndex(i, j);
QString stationName = core->getStationName(stationIdx);
stations << stationName;
// 记录站点在线路上的位置
m_stationPositions[stationName][lineName] = j;
// 添加到图中
if (!m_graph.contains(stationName)) {
m_graph[stationName].name = stationName;
}
m_graph[stationName].line = lineName;
// 添加相邻站点的连接
if (j > 0) {
QString prevStation = core->getStationName(core->getLineStationIndex(i, j-1));
// 相邻站点之间的时间设为2分钟
m_graph[stationName].neighbors[prevStation] = 2;
m_graph[prevStation].neighbors[stationName] = 2;
}
// 添加换乘信息
if (core->isTransferStation(stationIdx)) {
QStringList transferLines = core->getTransferLines(stationIdx);
for (const QString& transferLine : transferLines) {
m_graph[stationName].transferLines[transferLine] = transferLine;
}
}
}
m_lineStations[lineName] = stations;
}
}
// 将Route转换为QVariantMap供QML使用
QVariantMap RoutePlanner::routeToVariantMap(const Route& route)
{
QVariantMap result;
result["totalTime"] = route.totalTime;
result["totalStations"] = route.totalStations;
result["transferCount"] = route.transferCount;
result["description"] = route.description;
// 转换 stops 数组
QVariantList stopsList;
for (const RouteStop& stop : route.stops) {
QVariantMap stopMap;
stopMap["stationName"] = stop.stationName;
stopMap["line"] = stop.line;
stopMap["stationIndex"] = stop.stationIndex;
stopMap["timeFromStart"] = stop.timeFromStart;
stopMap["stationCount"] = stop.stationCount;
stopsList.append(stopMap);
}
result["stops"] = stopsList;
return result;
}
Route RoutePlanner::dijkstraSearch(const QString& from, const QString& to)
{
Route route;
QMap<QString, int> distance;
QMap<QString, QString> previous;
QMap<QString, QString> previousLine;
QSet<QString> visited;
// 初始化距离
for (auto it = m_graph.begin(); it != m_graph.end(); ++it) {
distance[it.key()] = INT_MAX;
}
if (!m_graph.contains(from) || !m_graph.contains(to)) {
route.description = "未找到有效路径";
return route;
}
distance[from] = 0;
for (int i = 0; i < m_graph.size(); ++i) {
// 找到未访问中距离最小的节点
QString current;
int minDist = INT_MAX;
for (auto it = distance.begin(); it != distance.end(); ++it) {
if (!visited.contains(it.key()) && it.value() < minDist) {
minDist = it.value();
current = it.key();
}
}
if (current.isEmpty() || current == to) break;
visited.insert(current);
// 更新邻接节点的距离
for (auto neighborIt = m_graph[current].neighbors.begin();
neighborIt != m_graph[current].neighbors.end(); ++neighborIt) {
QString neighbor = neighborIt.key();
int newDist = distance[current] + neighborIt.value();
if (newDist < distance[neighbor]) {
distance[neighbor] = newDist;
previous[neighbor] = current;
// 记录线路信息
if (m_graph[neighbor].line != m_graph[current].line) {
previousLine[neighbor] = m_graph[neighbor].line;
}
}
}
}
// 构建路径
if (distance[to] < INT_MAX) {
route.totalTime = distance[to];
QString current = to;
QVector<RouteStop> stops;
while (current != from) {
RouteStop stop;
stop.stationName = current;
stop.line = m_graph[current].line;
stop.timeFromStart = distance[current];
stops.prepend(stop);
if (!previous.contains(current)) break;
current = previous[current];
}
// 添加起点
RouteStop startStop;
startStop.stationName = from;
startStop.line = m_graph[from].line;
startStop.timeFromStart = 0;
stops.prepend(startStop);
route.stops = stops;
route.totalStations = stops.size();
// 计算换乘次数
route.transferCount = 0;
QString currentLine = stops[0].line;
for (int i = 1; i < stops.size(); ++i) {
if (stops[i].line != currentLine) {
route.transferCount++;
currentLine = stops[i].line;
}
}
route.description = QString("最快路径:共%1站,换乘%2次,约%3分钟")
.arg(route.totalStations)
.arg(route.transferCount)
.arg(route.totalTime);
} else {
route.description = "未找到可行路径";
}
return route;
}
Route RoutePlanner::bfsSearch(const QString& from, const QString& to)
{
Route route;
if (!m_graph.contains(from) || !m_graph.contains(to)) {
route.description = "未找到有效路径";
return route;
}
QQueue<QString> queue;
QMap<QString, QString> previous;
QMap<QString, int> transferCount;
QSet<QString> visited;
queue.enqueue(from);
visited.insert(from);
transferCount[from] = 0;
while (!queue.isEmpty()) {
QString current = queue.dequeue();
if (current == to) {
// 构建路径
QString node = to;
QVector<RouteStop> stops;
while (node != from) {
RouteStop stop;
stop.stationName = node;
stop.line = m_graph[node].line;
stops.prepend(stop);
if (!previous.contains(node)) break;
node = previous[node];
}
RouteStop startStop;
startStop.stationName = from;
startStop.line = m_graph[from].line;
stops.prepend(startStop);
route.stops = stops;
route.transferCount = transferCount[to];
route.totalStations = stops.size();
// 估算时间(每站2分钟,换乘3分钟)
route.totalTime = (route.totalStations - 1) * 2 + route.transferCount * 3;
route.description = QString("最少换乘路径:共%1站,换乘%2次,约%3分钟")
.arg(route.totalStations)
.arg(route.transferCount)
.arg(route.totalTime);
break;
}
for (auto neighborIt = m_graph[current].neighbors.begin();
neighborIt != m_graph[current].neighbors.end(); ++neighborIt) {
QString neighbor = neighborIt.key();
if (!visited.contains(neighbor)) {
visited.insert(neighbor);
previous[neighbor] = current;
// 计算换乘次数
int transfers = transferCount[current];
if (m_graph[neighbor].line != m_graph[current].line) {
transfers++;
}
transferCount[neighbor] = transfers;
queue.enqueue(neighbor);
}
}
}
return route;
}
QVariantMap RoutePlanner::findFastestRoute(const QString& fromStation, const QString& toStation)
{
Route route = dijkstraSearch(fromStation, toStation);
return routeToVariantMap(route);
}
QVariantMap RoutePlanner::findMinTransferRoute(const QString& fromStation, const QString& toStation)
{
Route route = bfsSearch(fromStation, toStation);
return routeToVariantMap(route);
}
QVariantList RoutePlanner::findAllRoutes(const QString& fromStation, const QString& toStation, int maxResults)
{
QVariantList routes;
// 添加最短时间路径
QVariantMap fastest = findFastestRoute(fromStation, toStation);
if (!fastest.isEmpty() && fastest["totalStations"].toInt() > 0) {
routes.append(fastest);
}
// 添加最少换乘路径
QVariantMap minTransfer = findMinTransferRoute(fromStation, toStation);
if (!minTransfer.isEmpty() && minTransfer["totalStations"].toInt() > 0 &&
minTransfer["description"].toString() != fastest["description"].toString()) {
routes.append(minTransfer);
}
return routes;
}
int RoutePlanner::calculateTravelTime(const QString& fromStation, const QString& toStation)
{
Route route = dijkstraSearch(fromStation, toStation);
return route.totalTime;
}
int RoutePlanner::calculateStationsCount(const QString& fromStation, const QString& toStation)
{
Route route = dijkstraSearch(fromStation, toStation);
return route.totalStations;
}
QStringList RoutePlanner::getStationsOnLine(const QString& lineName)
{
return m_lineStations[lineName];
}
bool RoutePlanner::isDirectRoute(const QString& fromStation, const QString& toStation)
{
// 检查是否在同一线路上
if (m_graph.contains(fromStation) && m_graph.contains(toStation)) {
if (m_graph[fromStation].line == m_graph[toStation].line) {
return true;
}
}
return false;
}
routeplanner.h
cpp
#ifndef ROUTEPLANNER_H
#define ROUTEPLANNER_H
#include <QObject>
#include <QVector>
#include <QMap>
#include <QVariantMap>
#include <QList>
struct RouteStop {
QString stationName;
QString line;
int stationIndex;
int timeFromStart;
int stationCount;
};
struct Route {
QVector<RouteStop> stops;
int totalTime;
int totalStations;
int transferCount;
QString description;
};
class RoutePlanner : public QObject
{
Q_OBJECT
public:
explicit RoutePlanner(QObject *parent = nullptr);
Q_INVOKABLE void setMetroData(QObject *metroCore);
// 修改返回类型为QVariantMap,这样QML可以识别
Q_INVOKABLE QVariantMap findFastestRoute(const QString& fromStation, const QString& toStation);
Q_INVOKABLE QVariantMap findMinTransferRoute(const QString& fromStation, const QString& toStation);
Q_INVOKABLE QVariantList findAllRoutes(const QString& fromStation, const QString& toStation, int maxResults = 5);
Q_INVOKABLE int calculateTravelTime(const QString& fromStation, const QString& toStation);
Q_INVOKABLE int calculateStationsCount(const QString& fromStation, const QString& toStation);
Q_INVOKABLE QStringList getStationsOnLine(const QString& lineName);
Q_INVOKABLE bool isDirectRoute(const QString& fromStation, const QString& toStation);
signals:
void routeFound(const QString& routeInfo);
void routeError(const QString& error);
private:
struct StationNode {
QString name;
QString line;
QMap<QString, int> neighbors;
QMap<QString, QString> transferLines;
};
void buildGraph();
Route bfsSearch(const QString& from, const QString& to);
Route dijkstraSearch(const QString& from, const QString& to);
QVariantMap routeToVariantMap(const Route& route);
QObject *m_metroCore;
QMap<QString, StationNode> m_graph;
QMap<QString, QMap<QString, int>> m_stationPositions;
QMap<QString, QStringList> m_lineStations;
};
#endif // ROUTEPLANNER_H
voiceannouncer.cpp
cpp
#include "VoiceAnnouncer.h"
#include <QDebug>
VoiceAnnouncer::VoiceAnnouncer(QObject *parent)
: QObject(parent)
, m_enabled(true)
, m_volume(50)
, m_rate(50)
, m_reminderCount(0)
{
// 初始化文本转语音引擎
m_tts = new QTextToSpeech(this);
// 设置默认语言为中文
m_tts->setLocale(QLocale(QLocale::Chinese, QLocale::China));
// 设置语音属性
m_tts->setVolume(0.5); // 50%音量
m_tts->setRate(0.2); // 正常语速
// 初始化提醒定时器
m_reminderTimer = new QTimer(this);
m_reminderTimer->setSingleShot(false);
}
void VoiceAnnouncer::setEnabled(bool enabled)
{
if (m_enabled != enabled) {
m_enabled = enabled;
emit enabledChanged();
if (!enabled) {
stopAnnouncement();
}
}
}
void VoiceAnnouncer::setVolume(int volume)
{
m_volume = volume;
m_tts->setVolume(volume / 100.0);
emit volumeChanged();
}
void VoiceAnnouncer::setRate(int rate)
{
m_rate = rate;
m_tts->setRate((rate - 50) / 50.0); // 转换到 -1.0 到 1.0
emit rateChanged();
}
void VoiceAnnouncer::speak(const QString& text)
{
if (m_enabled && !text.isEmpty()) {
qDebug() << "speak:" << text;
emit announcementStarted();
m_tts->say(text);
connect(m_tts, &QTextToSpeech::stateChanged, this, [this](QTextToSpeech::State state) {
if (state == QTextToSpeech::Ready) {
emit announcementFinished();
}
}, Qt::UniqueConnection);
}
}
void VoiceAnnouncer::announceArrival(const QString& stationName, int minutesToArrival)
{
QString announcement;
if (minutesToArrival <= 0) {
announcement = QString("请注意,%1站到了,请从左侧车门下车。").arg(stationName);
} else if (minutesToArrival <= 1) {
announcement = QString("即将到达%1站,请做好准备。").arg(stationName);
} else {
announcement = QString("下一站是%1站,大约%2分钟后到达。").arg(stationName).arg(minutesToArrival);
}
speak(announcement);
}
void VoiceAnnouncer::announceDeparture(const QString& stationName)
{
QString announcement = QString("列车已从%1站出发,下一站是").arg(stationName);
speak(announcement);
}
void VoiceAnnouncer::announceTransfer(const QString& stationName, const QStringList& lines)
{
if (lines.isEmpty()) return;
QString announcement = QString("请注意,%1站是换乘站,可换乘").arg(stationName);
for (int i = 0; i < lines.size(); ++i) {
if (i > 0) announcement += "、";
announcement += lines[i];
}
announcement += "。请换乘的乘客做好准备。";
speak(announcement);
}
void VoiceAnnouncer::announceNextStation(const QString& stationName, int minutesAway)
{
if (minutesAway <= 2) {
announceArrival(stationName, minutesAway);
} else {
scheduleNextStationReminder(stationName, minutesAway);
}
}
void VoiceAnnouncer::scheduleNextStationReminder(const QString& stationName, int minutesAway)
{
// 在到站前1分钟和30秒提醒
m_reminderCount = 0;
disconnect(m_reminderTimer, &QTimer::timeout, this, nullptr);
m_reminderTimer->start(1000); // 每秒检查一次
int initialDelay = (minutesAway - 1) * 60; // 提前1分钟提醒
connect(m_reminderTimer, &QTimer::timeout, this, [this, stationName, minutesAway]() {
static int elapsedSeconds = 0;
elapsedSeconds++;
int remainingSeconds = minutesAway * 60 - elapsedSeconds;
if (remainingSeconds == 60) {
announceArrival(stationName, 1);
} else if (remainingSeconds == 30) {
announceArrival(stationName, 0);
} else if (remainingSeconds <= 0) {
m_reminderTimer->stop();
elapsedSeconds = 0;
}
});
}
void VoiceAnnouncer::announceRoute(const QString& start, const QString& end, int duration, int stations)
{
QString announcement = QString("从%1到%2的行程规划完成。共%3站,预计需要%4分钟。")
.arg(start).arg(end).arg(stations).arg(duration);
speak(announcement);
}
void VoiceAnnouncer::testVoice()
{
speak("语音播报功能测试正常,欢迎乘坐地铁。");
//speak("hello world");
}
void VoiceAnnouncer::stopAnnouncement()
{
m_tts->stop();
m_reminderTimer->stop();
}
voiceannouncer.h
cpp
#ifndef VOICEANNOUNCER_H
#define VOICEANNOUNCER_H
#include <QObject>
#include <QTextToSpeech>
#include <QDateTime>
#include <QTimer>
class VoiceAnnouncer : public QObject
{
Q_OBJECT
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged)
Q_PROPERTY(int rate READ rate WRITE setRate NOTIFY rateChanged)
public:
explicit VoiceAnnouncer(QObject *parent = nullptr);
bool isEnabled() const { return m_enabled; }
int volume() const { return m_volume; }
int rate() const { return m_rate; }
Q_INVOKABLE void announceArrival(const QString& stationName, int minutesToArrival);
Q_INVOKABLE void announceDeparture(const QString& stationName);
Q_INVOKABLE void announceTransfer(const QString& stationName, const QStringList& lines);
Q_INVOKABLE void announceNextStation(const QString& stationName, int minutesAway);
Q_INVOKABLE void announceRoute(const QString& start, const QString& end, int duration, int stations);
Q_INVOKABLE void testVoice();
public slots:
void setEnabled(bool enabled);
void setVolume(int volume);
void setRate(int rate);
void stopAnnouncement();
signals:
void enabledChanged();
void volumeChanged();
void rateChanged();
void announcementStarted();
void announcementFinished();
private:
void speak(const QString& text);
void scheduleNextStationReminder(const QString& stationName, int minutesAway);
QTextToSpeech *m_tts;
bool m_enabled;
int m_volume;
int m_rate;
QTimer *m_reminderTimer;
int m_reminderCount;
};
#endif // VOICEANNOUNCER_H
main.qml
javascript
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Shapes 1.12
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.12
ApplicationWindow {
id: mainWindow
visible: true
width: 1200
height: 800
title: qsTr("地铁导乘屏")
color: "#1a1a2e"
// 控制按钮区域
Rectangle {
id: controlPanel
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 20
}
height: 100
color: "#16213e"
radius: 15
Row {
anchors.centerIn: parent
spacing: 20
Button {
text: "◀ 上一站"
font.pixelSize: 18
font.bold: true
width: 120
height: 50
onClicked: metroGuide.previousStation()
// 添加这一行来设置文本颜色为白色
contentItem: Text {
text: parent.text
font: parent.font
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
color: parent.pressed ? "#e94560" : "#0f3460"
radius: 10
}
}
Button {
text: "下一站 ▶"
font.pixelSize: 18
font.bold: true
width: 120
height: 50
onClicked: metroGuide.nextStation()
// 添加这一行来设置文本颜色为白色
contentItem: Text {
text: parent.text
font: parent.font
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
color: parent.pressed ? "#e94560" : "#0f3460"
radius: 10
}
}
}
// 在控制按钮区域旁边添加语音控制
RowLayout {
anchors.right: parent.right
anchors.rightMargin: 20
spacing: 10
Button {
text: voiceAnnouncer.enabled ? "🔊 语音开" : "🔇 语音关"
font.pixelSize: 14
onClicked: {
voiceAnnouncer.enabled = !voiceAnnouncer.enabled;
text = voiceAnnouncer.enabled ? "🔊 语音开" : "🔇 语音关";
}
}
Button {
text: "📢 测试"
font.pixelSize: 14
onClicked: voiceAnnouncer.testVoice()
}
}
}
// 添加搜索面板(替换或添加到底部信息面板旁边)
Rectangle {
id: searchPanelContainer
anchors {
right: parent.right
top: controlPanel.bottom
bottom: infoPanel.top
margins: 20
leftMargin: 20
}
width: 350
visible: true
SearchPanel {
anchors.fill: parent
}
}
// 地图显示区域
Rectangle {
id: mapArea
anchors {
top: controlPanel.bottom
bottom: infoPanel.top
left: parent.left
right: searchPanelContainer.left
margins: 20
topMargin: 10
bottomMargin: 10
rightMargin: 10
}
color: "#0f3460"
radius: 15
// 地铁线路图
Item {
anchors.fill: parent
anchors.margins: 20
// 绘制线路连接线
Canvas {
id: lineCanvas
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
// 绘制所有线路
for (var i = 0; i < metroGuide.getLineCount(); i++) {
ctx.beginPath();
var lineColor = metroGuide.getLineColor(i);
ctx.strokeStyle = lineColor;
ctx.lineWidth = 4;
var firstPoint = true;
for (var j = 0; j < metroGuide.getLineStationCount(i); j++) {
var stationIndex = metroGuide.getLineStationIndex(i, j);
var x = metroGuide.getStationX(stationIndex);
var y = metroGuide.getStationY(stationIndex);
// 坐标映射
var canvasX = (x / 1000) * parent.width;
var canvasY = (y / 600) * parent.height;
if (firstPoint) {
ctx.moveTo(canvasX, canvasY);
firstPoint = false;
} else {
ctx.lineTo(canvasX, canvasY);
}
}
ctx.stroke();
}
}
Component.onCompleted: {
// 当站点改变时重绘
metroGuide.currentStationChanged.connect(lineCanvas.requestPaint);
}
}
// 站点标记
Repeater {
model: metroGuide.getStationCount()
Item {
x: (metroGuide.getStationX(index) / 1000) * lineCanvas.width - 10
y: (metroGuide.getStationY(index) / 600) * lineCanvas.height - 10
Rectangle {
width: 20
height: 20
radius: 10
color: {
if (index == metroGuide.currentStationIndex) {
return "red"; // 当前站点为绿色
} /*else if (index == metroGuide.currentStationIndex - 1) {
//return "red"; // 上一站点为红色
} */else {
return "yellow"; // 其他站点为黄色
}
}
border.width: 2
border.color: "white"
// 当前站点高亮动画
SequentialAnimation on scale {
loops: Animation.Infinite
running: index === metroGuide.currentStationIndex
NumberAnimation { to: 1.3; duration: 500; easing.type: Easing.InOutQuad }
NumberAnimation { to: 1; duration: 500; easing.type: Easing.InOutQuad }
}
// 站点名称
Text {
anchors {
top: parent.bottom
topMargin: 5
horizontalCenter: parent.horizontalCenter
}
text: metroGuide.getStationName(index)
color: "white"
font.pixelSize: 10
font.bold: true
}
// 换乘标记
Row {
anchors {
top: parent.top
right: parent.right
rightMargin: -15
}
spacing: 2
visible: metroGuide.isTransferStation(index)
Repeater {
model: metroGuide.getTransferLines(index)
Rectangle {
width: 15
height: 15
radius: 7.5
color: {
switch(modelData) {
case "1号线": return "#dc143c";
case "2号线": return "#32cd32";
case "5号线": return "#ffa500";
case "10号线": return "#8a2be2";
default: return "#8a2be2";
}
}
Text {
anchors.centerIn: parent
text: modelData.substring(0, 1)
color: "white"
font.pixelSize: 8
font.bold: true
}
}
}
}
}
// 脉冲光环效果(当前站点)
Rectangle {
width: 40
height: 40
radius: 15
color: "#e94560"
opacity: 0.3
visible: index === metroGuide.currentStationIndex
// // ✅ 关键修复:让缩放从【中心】开始
// transformOrigin: Item.Center
SequentialAnimation on scale {
loops: Animation.Infinite
NumberAnimation { from: 0.5; to: 1.5; duration: 1000 }
NumberAnimation { from: 1.5; to: 0.5; duration: 0 }
}
SequentialAnimation on opacity {
loops: Animation.Infinite
NumberAnimation { from: 0.6; to: 0; duration: 1000 }
}
}
}
}
}
}
// 底部信息面板
Rectangle {
id: infoPanel
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
margins: 20
}
height: 150
color: "#16213e"
radius: 15
Row {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// 当前站点信息
Rectangle {
width: 250
height: parent.height - 40
color: "#0f3460"
radius: 10
Column {
anchors.centerIn: parent
spacing: 10
Text {
text: "当前站点"
color: "#aaaaaa"
font.pixelSize: 12
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: metroGuide.currentStationName
color: "#e94560"
font.pixelSize: 24
font.bold: true
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// 方向信息
Rectangle {
width: 300
height: parent.height - 40
color: "#0f3460"
radius: 10
Column {
anchors.centerIn: parent
spacing: 10
Text {
text: "运行方向"
color: "#aaaaaa"
font.pixelSize: 12
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: metroGuide.directionText
color: "#fbcb43"
font.pixelSize: 16
font.bold: true
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: metroGuide.nextStationText
color: "#ffffff"
font.pixelSize: 14
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// 线路图例
Rectangle {
width: parent.width - 570
height: parent.height - 40
color: "#0f3460"
radius: 10
Row {
anchors.centerIn: parent
spacing: 20
Repeater {
model: metroGuide.getLineCount()
Row {
spacing: 5
Rectangle {
width: 20
height: 20
radius: 3
color: metroGuide.getLineColor(index)
}
Text {
text: metroGuide.getLineName(index)
color: "white"
font.pixelSize: 12
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
// 窗口大小改变时重绘
onWidthChanged: lineCanvas.requestPaint()
onHeightChanged: lineCanvas.requestPaint()
}
SearchPanel.qml
javascript
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick 2.12
Rectangle {
id: searchPanel
color: "#16213e"
radius: 15
property string fromStation: ""
property string toStation: ""
ColumnLayout {
anchors.fill: parent
anchors.margins: 15
spacing: 15
Text {
text: "出行规划"
color: "#e94560"
font.pixelSize: 20
font.bold: true
Layout.alignment: Qt.AlignHCenter
}
// 起点选择
Rectangle {
Layout.fillWidth: true
height: 50
color: "#0f3460"
radius: 10
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Text {
text: "起点:"
color: "white"
font.pixelSize: 14
}
TextInput {
id: fromInput
Layout.fillWidth: true
color: "white"
font.pixelSize: 14
//placeholderText: "请输入起点站"
//placeholderTextColor: "#888888"
onTextChanged: searchPanel.fromStation = text
}
}
}
// 终点选择
Rectangle {
Layout.fillWidth: true
height: 50
color: "#0f3460"
radius: 10
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Text {
text: "终点:"
color: "white"
font.pixelSize: 14
}
TextInput {
id: toInput
Layout.fillWidth: true
color: "white"
font.pixelSize: 14
//placeholderText: "请输入终点站"
//placeholderTextColor: "#888888"
onTextChanged: searchPanel.toStation = text
}
}
}
// 搜索按钮
Button {
text: "规划路线"
Layout.fillWidth: true
height: 50
font.pixelSize: 16
font.bold: true
// 添加这一行来设置文本颜色为白色
contentItem: Text {
text: parent.text
font: parent.font
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
background: Rectangle {
color: parent.pressed ? "#e94560" : "#0f3460"
radius: 10
}
onClicked: {
if (fromStation && toStation) {
findRoutes();
}
}
}
// 结果显示区域
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "#0a1628"
radius: 10
visible: routeList.count > 0
ListView {
id: routeList
anchors.fill: parent
anchors.margins: 10
spacing: 10
clip: true
model: ListModel {
id: routeModel
}
delegate: Rectangle {
width: parent.width
height: 100
color: "#16213e"
radius: 8
Column {
anchors.fill: parent
anchors.margins: 10
spacing: 5
Text {
text: model.description
color: "#e94560"
font.pixelSize: 14
font.bold: true
}
Text {
text: "总站数: " + model.stations + " | 换乘: " + model.transfers + "次 | 时间: " + model.time + "分钟"
color: "#cccccc"
font.pixelSize: 12
}
Text {
text: model.path
color: "#aaaaaa"
font.pixelSize: 11
wrapMode: Text.WordWrap
width: parent.width
}
}
MouseArea {
anchors.fill: parent
onClicked: {
// 显示详细路线
showRouteDetails(model);
}
}
}
}
}
}
function findRoutes() {
routeModel.clear();
// 查找最短时间路线 - 现在返回的是QVariantMap
var fastestRoute = routePlanner.findFastestRoute(fromStation, toStation);
if (fastestRoute && fastestRoute.totalStations > 0) {
routeModel.append({
description: fastestRoute.description,
stations: fastestRoute.totalStations,
transfers: fastestRoute.transferCount,
time: fastestRoute.totalTime,
path: formatRoutePath(fastestRoute.stops)
});
}
// 查找最少换乘路线
var minTransferRoute = routePlanner.findMinTransferRoute(fromStation, toStation);
if (minTransferRoute && minTransferRoute.totalStations > 0 &&
minTransferRoute.description !== fastestRoute.description) {
routeModel.append({
description: minTransferRoute.description,
stations: minTransferRoute.totalStations,
transfers: minTransferRoute.transferCount,
time: minTransferRoute.totalTime,
path: formatRoutePath(minTransferRoute.stops)
});
}
// 语音播报路线信息
if (routeModel.count > 0 && voiceAnnouncer.enabled) {
voiceAnnouncer.announceRoute(fromStation, toStation,
routeModel.get(0).time,
routeModel.get(0).stations);
}
}
// 修改路径格式化函数,接收stops数组
function formatRoutePath(stops) {
if (!stops || stops.length === 0) return "";
var path = "";
var currentLine = "";
for (var i = 0; i < stops.length; i++) {
if (i > 0) path += " → ";
path += stops[i].stationName;
if (stops[i].line !== currentLine && i > 0) {
path += " [换乘" + stops[i].line + "]";
currentLine = stops[i].line;
} else if (currentLine === "") {
currentLine = stops[i].line;
}
}
return path;
}
function showRouteDetails(routeData) {
// 显示详细路线信息
console.log("详细路线:", routeData.path);
// 这里可以添加更详细的路线展示界面
}
}