Creating reference map widget

This commit is contained in:
Dan Paulat 2021-06-23 21:48:08 -05:00
parent 760e8a52a1
commit b1e00cca83
8 changed files with 631 additions and 30 deletions

View file

@ -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)

View file

@ -1,35 +1,23 @@
#include "main_window.hpp"
#include "./ui_main_window.h"
#include <scwx/qt/map/map_widget.hpp>
namespace scwx
{
namespace qt
{
template<typename... Types>
class variant
{
public:
template<typename T>
inline variant(T&& value)
{
}
};
using ValueBase = variant<float, double>;
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()

View file

@ -14,7 +14,20 @@
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout"/>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">

View file

@ -0,0 +1,527 @@
#include "map_widget.hpp"
#include <QApplication>
#include <QColor>
#include <QDebug>
#include <QFile>
#include <QIcon>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QString>
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<QMapbox::SymbolAnnotation>(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<QMapbox::LineAnnotation>(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>(QColor(Qt::black));
fillAnnotationId_ = map_->addAnnotation(
QVariant::fromValue<QMapbox::FillAnnotation>(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<QMapbox::Feature>(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<QMapbox::Feature>
QVector<QMapbox::Feature> 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<QMapbox::Feature>
QList<QMapbox::Feature> 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

View file

@ -0,0 +1,56 @@
#pragma once
#include <QMapboxGL>
#include <QOpenGLWidget>
#include <QPropertyAnimation>
#include <QScopedPointer>
#include <QtGlobal>
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<QMapboxGL> map_;
uint64_t frameDraws_ = 0;
QVariant symbolAnnotationId_;
QVariant lineAnnotationId_;
QVariant fillAnnotationId_;
bool sourceAdded_ = false;
};
} // namespace qt
} // namespace scwx

View file

@ -4,7 +4,7 @@
<context>
<name>MainWindow</name>
<message>
<location filename="../source/scwx/qt/main_window.ui" line="14"/>
<location filename="../source/scwx/qt/main/main_window.ui" line="14"/>
<source>MainWindow</source>
<translation type="unfinished"></translation>
</message>