QML简易地铁导乘屏

目录

[1 效果图](#1 效果图)

[2 简易功能](#2 简易功能)

[3 源码目录](#3 源码目录)

[4 源码](#4 源码)


1 效果图

2 简易功能

(1)QML UI构图

(2)语音播报

(3)路线规划

3 源码目录

工程使用QT 5.12 MINGW64编译

4 源码

untitled14.pro

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);
        // 这里可以添加更详细的路线展示界面
    }
}
相关推荐
Ulyanov5 天前
PySide6 + QML 混合编程全景解析:从底层原理到企业级实战
python·pyside6·qml·雷达电子对抗
_君莫笑9 天前
Qt+Qml前后端分离上位机软件技术方案
c++·qt·用户界面·qml
Ulyanov19 天前
《现代 Python 桌面应用架构实战:PySide6 + QML 从入门到工程化》:QML 声明式语法与霓虹按钮 —— 当 Python 遇见现代美学
开发语言·python·ui·qml·系统仿真·雷达电子对抗仿真
gdizcm25 天前
QT QML嵌入Widget窗体并通信
qt·qml·widget与qml
谁刺我心1 个月前
[QML]Functional功能型控件-虚拟键盘
开发语言·qml·虚拟键盘
Ulyanov1 个月前
《PySide6 GUI开发指南:QML核心与实践》 第十篇:综合实战——构建完整的跨平台个人管理应用
开发语言·python·qt·ui·交互·qml·雷达电子战系统仿真
Ulyanov1 个月前
《PySide6 GUI开发指南:QML核心与实践》 第八篇:性能优化大师——QML应用性能调优实战
python·qt·ui·性能优化·qml·系统仿真
Ulyanov1 个月前
《PySide6 GUI开发指南:QML核心与实践》 第一篇:GUI新纪元——QML与PySide6生态系统全景
开发语言·python·qt·qml·雷达电子对抗
大橘1 个月前
【qml-5.1】qml与c++交互(QML_ELEMENT/QML_SINGLETON)
开发语言·c++·qt·交互·qml