diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 4bcf048d..41242d13 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -43,20 +43,37 @@ find_package(Qt${QT_VERSION_MAJOR} include(qt6-linguist.cmake) +set(HDR_MAIN source/scwx/qt/main/main_window.hpp) +set(SRC_MAIN source/scwx/qt/main/main.cpp + source/scwx/qt/main/main_window.cpp) +set(UI_MAIN source/scwx/qt/main/main_window.ui) +set(HDR_MAP source/scwx/qt/map/map_widget.hpp) +set(SRC_MAP source/scwx/qt/map/map_widget.cpp) + set(TS_FILES ts/scwx_en_US.ts) -set(PROJECT_SOURCES - source/scwx/qt/main.cpp - source/scwx/qt/main_window.cpp - source/scwx/qt/main_window.hpp - source/scwx/qt/main_window.ui - ${TS_FILES} -) +set(PROJECT_SOURCES ${HDR_MAIN} + ${SRC_MAIN} + ${UI_MAIN} + ${HDR_MAP} + ${SRC_MAP} + ${TS_FILES}) + +source_group("Header Files\\main" FILES ${HDR_MAIN}) +source_group("Source Files\\main" FILES ${SRC_MAIN}) +source_group("UI Files\\main" FILES ${UI_MAIN}) +source_group("Header Files\\map" FILES ${HDR_MAP}) +source_group("Source Files\\map" FILES ${SRC_MAP}) +source_group("I18N Files" FILES ${TS_FILES}) qt_add_executable(scwx-qt ${PROJECT_SOURCES} ) -qt6_create_translation_scwx(QM_FILES ${PROJECT_SOURCE_DIR} ${TS_FILES}) +qt6_create_translation_scwx(QM_FILES ${scwx-qt_SOURCE_DIR} ${TS_FILES}) -target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) +target_include_directories(scwx-qt PRIVATE ${scwx-qt_SOURCE_DIR}/source) + +target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::OpenGLWidgets + qmapboxgl) diff --git a/scwx-qt/source/scwx/qt/main.cpp b/scwx-qt/source/scwx/qt/main/main.cpp similarity index 100% rename from scwx-qt/source/scwx/qt/main.cpp rename to scwx-qt/source/scwx/qt/main/main.cpp diff --git a/scwx-qt/source/scwx/qt/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp similarity index 53% rename from scwx-qt/source/scwx/qt/main_window.cpp rename to scwx-qt/source/scwx/qt/main/main_window.cpp index 8bc166c8..5dab28f0 100644 --- a/scwx-qt/source/scwx/qt/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -1,35 +1,23 @@ #include "main_window.hpp" #include "./ui_main_window.h" +#include + namespace scwx { namespace qt { -template -class variant -{ -public: - template - inline variant(T&& value) - { - } -}; - -using ValueBase = variant; - -struct Value : ValueBase -{ - using ValueBase::ValueBase; -}; - MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); - Value(0.0f); - Value(0.0); + QMapboxGLSettings settings; + settings.setCacheDatabasePath("/tmp/mbgl-cache.db"); + settings.setCacheDatabaseMaximumSize(20 * 1024 * 1024); + + ui->centralwidget->layout()->addWidget(new MapWidget(settings)); } MainWindow::~MainWindow() diff --git a/scwx-qt/source/scwx/qt/main_window.hpp b/scwx-qt/source/scwx/qt/main/main_window.hpp similarity index 100% rename from scwx-qt/source/scwx/qt/main_window.hpp rename to scwx-qt/source/scwx/qt/main/main_window.hpp diff --git a/scwx-qt/source/scwx/qt/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui similarity index 65% rename from scwx-qt/source/scwx/qt/main_window.ui rename to scwx-qt/source/scwx/qt/main/main_window.ui index f28d9ec4..c52bfa20 100644 --- a/scwx-qt/source/scwx/qt/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -14,7 +14,20 @@ MainWindow - + + + 0 + + + 0 + + + 0 + + + 0 + + diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp new file mode 100644 index 00000000..a5478a70 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -0,0 +1,527 @@ +#include "map_widget.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ + +MapWidget::MapWidget(const QMapboxGLSettings& settings) : settings_(settings) +{ + setFocusPolicy(Qt::StrongFocus); +} + +MapWidget::~MapWidget() +{ + // Make sure we have a valid context so we + // can delete the QMapboxGL. + makeCurrent(); +} + +qreal MapWidget::pixelRatio() +{ + return devicePixelRatioF(); +} + +void MapWidget::changeStyle() +{ + static uint8_t currentStyleIndex; + + auto& styles = QMapbox::defaultStyles(); + + map_->setStyleUrl(styles[currentStyleIndex].first); + setWindowTitle(QString("Mapbox GL: ") + styles[currentStyleIndex].second); + + if (++currentStyleIndex == styles.size()) + { + currentStyleIndex = 0; + } + + sourceAdded_ = false; +} + +void MapWidget::keyPressEvent(QKeyEvent* ev) +{ + switch (ev->key()) + { + case Qt::Key_S: changeStyle(); break; + case Qt::Key_L: + { + if (sourceAdded_) + { + return; + } + + sourceAdded_ = true; + + // Not in all styles, but will work on streets + QString before = "waterway-label"; + + QFile geojson(":source1.geojson"); + geojson.open(QIODevice::ReadOnly); + + // The data source for the route line and markers + QVariantMap routeSource; + routeSource["type"] = "geojson"; + routeSource["data"] = geojson.readAll(); + map_->addSource("routeSource", routeSource); + + // The route case, painted before the route + QVariantMap routeCase; + routeCase["id"] = "routeCase"; + routeCase["type"] = "line"; + routeCase["source"] = "routeSource"; + map_->addLayer(routeCase, before); + + map_->setPaintProperty("routeCase", "line-color", QColor("white")); + map_->setPaintProperty("routeCase", "line-width", 20.0); + map_->setLayoutProperty("routeCase", "line-join", "round"); + map_->setLayoutProperty("routeCase", "line-cap", "round"); + + // The route, painted on top of the route case + QVariantMap route; + route["id"] = "route"; + route["type"] = "line"; + route["source"] = "routeSource"; + map_->addLayer(route, before); + + map_->setPaintProperty("route", "line-color", QColor("blue")); + map_->setPaintProperty("route", "line-width", 8.0); + map_->setLayoutProperty("route", "line-join", "round"); + map_->setLayoutProperty("route", "line-cap", "round"); + + QVariantList lineDashArray; + lineDashArray.append(1); + lineDashArray.append(2); + + map_->setPaintProperty("route", "line-dasharray", lineDashArray); + + // Markers at the beginning and end of the route + map_->addImage("label-arrow", QImage(":label-arrow.svg")); + map_->addImage("label-background", QImage(":label-background.svg")); + + QVariantMap markerArrow; + markerArrow["id"] = "markerArrow"; + markerArrow["type"] = "symbol"; + markerArrow["source"] = "routeSource"; + map_->addLayer(markerArrow); + + map_->setLayoutProperty("markerArrow", "icon-image", "label-arrow"); + map_->setLayoutProperty("markerArrow", "icon-size", 0.5); + map_->setLayoutProperty("markerArrow", "icon-ignore-placement", true); + + QVariantList arrowOffset; + arrowOffset.append(0.0); + arrowOffset.append(-15.0); + map_->setLayoutProperty("markerArrow", "icon-offset", arrowOffset); + + QVariantMap markerBackground; + markerBackground["id"] = "markerBackground"; + markerBackground["type"] = "symbol"; + markerBackground["source"] = "routeSource"; + map_->addLayer(markerBackground); + + map_->setLayoutProperty( + "markerBackground", "icon-image", "label-background"); + map_->setLayoutProperty("markerBackground", "text-field", "{name}"); + map_->setLayoutProperty("markerBackground", "icon-text-fit", "both"); + map_->setLayoutProperty( + "markerBackground", "icon-ignore-placement", true); + map_->setLayoutProperty( + "markerBackground", "text-ignore-placement", true); + map_->setLayoutProperty("markerBackground", "text-anchor", "left"); + map_->setLayoutProperty("markerBackground", "text-size", 16.0); + map_->setLayoutProperty("markerBackground", "text-padding", 0.0); + map_->setLayoutProperty("markerBackground", "text-line-height", 1.0); + map_->setLayoutProperty("markerBackground", "text-max-width", 8.0); + + QVariantList iconTextFitPadding; + iconTextFitPadding.append(15.0); + iconTextFitPadding.append(10.0); + iconTextFitPadding.append(15.0); + iconTextFitPadding.append(10.0); + map_->setLayoutProperty( + "markerBackground", "icon-text-fit-padding", iconTextFitPadding); + + QVariantList backgroundOffset; + backgroundOffset.append(-0.5); + backgroundOffset.append(-1.5); + map_->setLayoutProperty( + "markerBackground", "text-offset", backgroundOffset); + + map_->setPaintProperty("markerBackground", "text-color", QColor("white")); + + QVariantList filterExpression; + filterExpression.append("=="); + filterExpression.append("$type"); + filterExpression.append("Point"); + + QVariantList filter; + filter.append(filterExpression); + + map_->setFilter("markerArrow", filter); + map_->setFilter("markerBackground", filter); + + // Tilt the labels when tilting the map and make them larger + map_->setLayoutProperty("road-label-large", "text-size", 30.0); + map_->setLayoutProperty( + "road-label-large", "text-pitch-alignment", "viewport"); + + map_->setLayoutProperty("road-label-medium", "text-size", 30.0); + map_->setLayoutProperty( + "road-label-medium", "text-pitch-alignment", "viewport"); + + map_->setLayoutProperty( + "road-label-small", "text-pitch-alignment", "viewport"); + map_->setLayoutProperty("road-label-small", "text-size", 30.0); + + // Buildings extrusion + QVariantMap buildings; + buildings["id"] = "3d-buildings"; + buildings["source"] = "composite"; + buildings["source-layer"] = "building"; + buildings["type"] = "fill-extrusion"; + buildings["minzoom"] = 15.0; + map_->addLayer(buildings); + + QVariantList buildingsFilterExpression; + buildingsFilterExpression.append("=="); + buildingsFilterExpression.append("extrude"); + buildingsFilterExpression.append("true"); + + QVariantList buildingsFilter; + buildingsFilter.append(buildingsFilterExpression); + + map_->setFilter("3d-buildings", buildingsFilterExpression); + + QString fillExtrusionColorJSON = R"JSON( + [ + "interpolate", + ["linear"], + ["get", "height"], + 0.0, "blue", + 20.0, "royalblue", + 40.0, "cyan", + 60.0, "lime", + 80.0, "yellow", + 100.0, "red" + ] + )JSON"; + + map_->setPaintProperty( + "3d-buildings", "fill-extrusion-color", fillExtrusionColorJSON); + map_->setPaintProperty("3d-buildings", "fill-extrusion-opacity", .6); + + QVariantMap extrusionHeight; + extrusionHeight["type"] = "identity"; + extrusionHeight["property"] = "height"; + + map_->setPaintProperty( + "3d-buildings", "fill-extrusion-height", extrusionHeight); + + QVariantMap extrusionBase; + extrusionBase["type"] = "identity"; + extrusionBase["property"] = "min_height"; + + map_->setPaintProperty( + "3d-buildings", "fill-extrusion-base", extrusionBase); + } + break; + case Qt::Key_1: + { + if (symbolAnnotationId_.isNull()) + { + QMapbox::Coordinate coordinate = map_->coordinate(); + QMapbox::SymbolAnnotation symbol {coordinate, "default_marker"}; + map_->addAnnotationIcon("default_marker", + QImage(":default_marker.svg")); + symbolAnnotationId_ = map_->addAnnotation( + QVariant::fromValue(symbol)); + } + else + { + map_->removeAnnotation(symbolAnnotationId_.toUInt()); + symbolAnnotationId_.clear(); + } + } + break; + case Qt::Key_2: + { + if (lineAnnotationId_.isNull()) + { + QMapbox::Coordinates coordinates; + coordinates.push_back(map_->coordinateForPixel({0, 0})); + coordinates.push_back(map_->coordinateForPixel( + {qreal(size().width()), qreal(size().height())})); + + QMapbox::CoordinatesCollection collection; + collection.push_back(coordinates); + + QMapbox::CoordinatesCollections lineGeometry; + lineGeometry.push_back(collection); + + QMapbox::ShapeAnnotationGeometry annotationGeometry( + QMapbox::ShapeAnnotationGeometry::LineStringType, lineGeometry); + + QMapbox::LineAnnotation line; + line.geometry = annotationGeometry; + line.opacity = 0.5f; + line.width = 1.0f; + line.color = Qt::red; + lineAnnotationId_ = map_->addAnnotation( + QVariant::fromValue(line)); + } + else + { + map_->removeAnnotation(lineAnnotationId_.toUInt()); + lineAnnotationId_.clear(); + } + } + break; + case Qt::Key_3: + { + if (fillAnnotationId_.isNull()) + { + QMapbox::Coordinates coordinates; + coordinates.push_back( + map_->coordinateForPixel({qreal(size().width()), 0})); + coordinates.push_back(map_->coordinateForPixel( + {qreal(size().width()), qreal(size().height())})); + coordinates.push_back( + map_->coordinateForPixel({0, qreal(size().height())})); + coordinates.push_back(map_->coordinateForPixel({0, 0})); + + QMapbox::CoordinatesCollection collection; + collection.push_back(coordinates); + + QMapbox::CoordinatesCollections fillGeometry; + fillGeometry.push_back(collection); + + QMapbox::ShapeAnnotationGeometry annotationGeometry( + QMapbox::ShapeAnnotationGeometry::PolygonType, fillGeometry); + + QMapbox::FillAnnotation fill; + fill.geometry = annotationGeometry; + fill.opacity = 0.5f; + fill.color = Qt::green; + fill.outlineColor = QVariant::fromValue(QColor(Qt::black)); + fillAnnotationId_ = map_->addAnnotation( + QVariant::fromValue(fill)); + } + else + { + map_->removeAnnotation(fillAnnotationId_.toUInt()); + fillAnnotationId_.clear(); + } + } + break; + case Qt::Key_5: + { + if (map_->layerExists("circleLayer")) + { + map_->removeLayer("circleLayer"); + map_->removeSource("circleSource"); + } + else + { + QMapbox::Coordinates coordinates; + coordinates.push_back(map_->coordinate()); + + QMapbox::CoordinatesCollection collection; + collection.push_back(coordinates); + + QMapbox::CoordinatesCollections point; + point.push_back(collection); + + QMapbox::Feature feature(QMapbox::Feature::PointType, point, {}, {}); + + QVariantMap circleSource; + circleSource["type"] = "geojson"; + circleSource["data"] = QVariant::fromValue(feature); + map_->addSource("circleSource", circleSource); + + QVariantMap circle; + circle["id"] = "circleLayer"; + circle["type"] = "circle"; + circle["source"] = "circleSource"; + map_->addLayer(circle); + + map_->setPaintProperty("circleLayer", "circle-radius", 10.0); + map_->setPaintProperty("circleLayer", "circle-color", QColor("black")); + } + } + break; + case Qt::Key_6: + { + if (map_->layerExists("innerCirclesLayer") || + map_->layerExists("outerCirclesLayer")) + { + map_->removeLayer("innerCirclesLayer"); + map_->removeLayer("outerCirclesLayer"); + map_->removeSource("innerCirclesSource"); + map_->removeSource("outerCirclesSource"); + } + else + { + auto makePoint = [&](double dx, double dy, const QString& color) { + auto coordinate = map_->coordinate(); + coordinate.first += dx; + coordinate.second += dy; + return QMapbox::Feature {QMapbox::Feature::PointType, + {{{coordinate}}}, + {{"color", color}}, + {}}; + }; + + // multiple features by QVector + QVector inner {makePoint(0.001, 0, "red"), + makePoint(0, 0.001, "green"), + makePoint(0, -0.001, "blue")}; + + map_->addSource( + "innerCirclesSource", + {{"type", "geojson"}, {"data", QVariant::fromValue(inner)}}); + map_->addLayer({{"id", "innerCirclesLayer"}, + {"type", "circle"}, + {"source", "innerCirclesSource"}}); + + // multiple features by QList + QList outer {makePoint(0.002, 0.002, "cyan"), + makePoint(-0.002, 0.002, "magenta"), + makePoint(0.002, -0.002, "yellow"), + makePoint(-0.002, -0.002, "black")}; + + map_->addSource( + "outerCirclesSource", + {{"type", "geojson"}, {"data", QVariant::fromValue(outer)}}); + map_->addLayer({{"id", "outerCirclesLayer"}, + {"type", "circle"}, + {"source", "outerCirclesSource"}}); + + QVariantList getColor {"get", "color"}; + map_->setPaintProperty("innerCirclesLayer", "circle-radius", 10.0); + map_->setPaintProperty("innerCirclesLayer", "circle-color", getColor); + map_->setPaintProperty("outerCirclesLayer", "circle-radius", 15.0); + map_->setPaintProperty("outerCirclesLayer", "circle-color", getColor); + } + } + break; + default: break; + } + + ev->accept(); +} + +void MapWidget::mousePressEvent(QMouseEvent* ev) +{ + lastPos_ = ev->position(); + + if (ev->type() == QEvent::MouseButtonPress) + { + if (ev->buttons() == (Qt::LeftButton | Qt::RightButton)) + { + changeStyle(); + } + } + + if (ev->type() == QEvent::MouseButtonDblClick) + { + if (ev->buttons() == Qt::LeftButton) + { + map_->scaleBy(2.0, lastPos_); + } + else if (ev->buttons() == Qt::RightButton) + { + map_->scaleBy(0.5, lastPos_); + } + } + + ev->accept(); +} + +void MapWidget::mouseMoveEvent(QMouseEvent* ev) +{ + QPointF delta = ev->position() - lastPos_; + + if (!delta.isNull()) + { + if (ev->buttons() == Qt::LeftButton && + ev->modifiers() & Qt::ShiftModifier) + { + map_->pitchBy(delta.y()); + } + else if (ev->buttons() == Qt::LeftButton) + { + map_->moveBy(delta); + } + else if (ev->buttons() == Qt::RightButton) + { + map_->rotateBy(lastPos_, ev->position()); + } + } + + lastPos_ = ev->position(); + ev->accept(); +} + +void MapWidget::wheelEvent(QWheelEvent* ev) +{ + if (ev->angleDelta().y() == 0) + { + return; + } + + float factor = ev->angleDelta().y() / 1200.; + if (ev->angleDelta().y() < 0) + { + factor = factor > -1 ? factor : 1 / factor; + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + map_->scaleBy(1 + factor, ev->position()); +#else + map_->scaleBy(1 + factor, ev->pos()); +#endif + + ev->accept(); +} + +void MapWidget::initializeGL() +{ + map_.reset(new QMapboxGL(nullptr, settings_, size(), pixelRatio())); + connect(map_.data(), SIGNAL(needsRendering()), this, SLOT(update())); + + // Set default location to KLSX. + map_->setCoordinateZoom(QMapbox::Coordinate(38.6986, -90.6828), 14); + + QString styleUrl = qgetenv("MAPBOX_STYLE_URL"); + if (styleUrl.isEmpty()) + { + changeStyle(); + } + else + { + map_->setStyleUrl(styleUrl); + setWindowTitle(QString("Mapbox GL: ") + styleUrl); + } +} + +void MapWidget::paintGL() +{ + frameDraws_++; + map_->resize(size()); + map_->setFramebufferObject(defaultFramebufferObject(), + size() * pixelRatio()); + map_->render(); +} + +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp new file mode 100644 index 00000000..95deb747 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include +#include +#include +#include + +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +namespace scwx +{ +namespace qt +{ + +class MapWidget : public QOpenGLWidget +{ + Q_OBJECT + +public: + MapWidget(const QMapboxGLSettings&); + ~MapWidget(); + +private: + void changeStyle(); + qreal pixelRatio(); + + // QWidget implementation. + void keyPressEvent(QKeyEvent* ev) override final; + void mousePressEvent(QMouseEvent* ev) override final; + void mouseMoveEvent(QMouseEvent* ev) override final; + void wheelEvent(QWheelEvent* ev) override final; + + // QOpenGLWidget implementation. + void initializeGL() override final; + void paintGL() override final; + + QPointF lastPos_; + + QMapboxGLSettings settings_; + QScopedPointer map_; + + uint64_t frameDraws_ = 0; + + QVariant symbolAnnotationId_; + QVariant lineAnnotationId_; + QVariant fillAnnotationId_; + + bool sourceAdded_ = false; +}; + +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/ts/scwx_en_US.ts b/scwx-qt/ts/scwx_en_US.ts index 62989f17..c9c86b53 100644 --- a/scwx-qt/ts/scwx_en_US.ts +++ b/scwx-qt/ts/scwx_en_US.ts @@ -4,7 +4,7 @@ MainWindow - + MainWindow