current revision
This commit is contained in:
120
ui/map/2D/ColorPoints2D.h
Normal file
120
ui/map/2D/ColorPoints2D.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef COLORPOINTS2D_H
|
||||
#define COLORPOINTS2D_H
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "Renderable2D.h"
|
||||
#include <Indoor/geo/Point3.h>
|
||||
|
||||
#include <Indoor/grid/Grid.h>
|
||||
|
||||
#include <KLib/math/filter/particles/Particle.h>
|
||||
#include "../nav/Node.h"
|
||||
#include "../nav/State.h"
|
||||
|
||||
/**
|
||||
* debug color points
|
||||
*/
|
||||
class ColorPoints2D : public Renderable2D {
|
||||
|
||||
private:
|
||||
|
||||
struct PT {
|
||||
Point3 pos;
|
||||
QColor color;
|
||||
PT(const Point3 pos, const QColor color) : pos(pos), color(color) {;}
|
||||
};
|
||||
|
||||
std::vector<PT> points;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
ColorPoints2D() {
|
||||
|
||||
}
|
||||
|
||||
void showGridImportance(Grid<MyGridNode>* grid) {
|
||||
|
||||
float min = +INFINITY;
|
||||
float max = -INFINITY;
|
||||
|
||||
for (const MyGridNode& n : *grid) {
|
||||
const float f = n.getWalkImportance();
|
||||
if (f < min) {min = f;}
|
||||
if (f > max) {max = f;}
|
||||
}
|
||||
|
||||
max = 1.2;
|
||||
|
||||
for (const MyGridNode& n : *grid) {
|
||||
const Point3 pt(n.x_cm/100.0f, n.y_cm/100.0f + 0.1f, n.z_cm/100.0f);
|
||||
const float f = n.getWalkImportance();
|
||||
float h = 0.66 - ((f-min)/(max-min)) * 0.66; // 0.66 is blue on the HSV-scale
|
||||
if (h < 0) {h = 0;}
|
||||
if (h > 1) {h = 1;}
|
||||
const QColor color = QColor::fromHsvF(h, 1, 1);
|
||||
points.push_back(PT(pt, color));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
template <typename T> void setFromParticles(const std::vector<K::Particle<T>>& particles) {
|
||||
|
||||
points.clear();
|
||||
|
||||
// group particles by grid-point
|
||||
std::unordered_map<GridPoint, float> weights;
|
||||
for (const K::Particle<T>& p : particles) {
|
||||
const GridPoint gp = p.state.position;
|
||||
if (weights.find(gp) != weights.end()) {continue;}
|
||||
weights[gp] += p.weight;
|
||||
}
|
||||
|
||||
// find min/max
|
||||
float min = +INFINITY;
|
||||
float max = -INFINITY;
|
||||
for (auto it : weights) {
|
||||
if (it.second > max) {max = it.second;}
|
||||
if (it.second < min) {min = it.second;}
|
||||
}
|
||||
|
||||
// draw colored
|
||||
for (auto it : weights) {
|
||||
const GridPoint gp = it.first;
|
||||
const float w = it.second;
|
||||
const float p = (w-min) / (max-min); // [0:1]
|
||||
const Point3 pt(gp.x_cm/100.0f, gp.y_cm/100.0f + 0.1f, gp.z_cm/100.0f);
|
||||
float h = 0.66 - (p*0.66); // 0.66 is blue on the HSV-scale
|
||||
const QColor color = QColor::fromHsvF(h, 1, 1);
|
||||
points.push_back(PT(pt, color));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void doRender(QPainter& qp, const Scaler2D& s, const RenderParams2D& r) override {
|
||||
|
||||
QPen pen;
|
||||
pen.setWidth(4);
|
||||
|
||||
for (const PT& pt : points) {
|
||||
|
||||
if (pt.pos.z < r.clip.belowHeight_m) {continue;}
|
||||
if (pt.pos.z > r.clip.aboveHeight_m) {continue;}
|
||||
|
||||
const Point2 p2 = s.mapToScreen(pt.pos.xy());
|
||||
|
||||
pen.setColor(pt.color);
|
||||
qp.setPen(pen);
|
||||
qp.drawPoint(p2.x, p2.y);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // COLORPOINTS2D_H
|
||||
62
ui/map/2D/Floor2D.h
Normal file
62
ui/map/2D/Floor2D.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef FLOOR2D_H
|
||||
#define FLOOR2D_H
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "Renderable2D.h"
|
||||
|
||||
/**
|
||||
* draw the floor itself (outline, obstacles)
|
||||
*/
|
||||
class Floor2D : public Renderable2D {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::Floor* floor;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Floor2D(Floorplan::Floor* floor) : floor(floor) {
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void doRender(QPainter& qp, const Scaler2D& s, const RenderParams2D& r) override {
|
||||
|
||||
if (floor->atHeight < r.clip.belowHeight_m) {return;}
|
||||
if (floor->atHeight > r.clip.aboveHeight_m) {return;}
|
||||
|
||||
qp.setPen(Qt::black);
|
||||
for (const Floorplan::FloorObstacle* obs : floor->obstacles) {
|
||||
const Floorplan::FloorObstacleLine* line = dynamic_cast<const Floorplan::FloorObstacleLine*>(obs);
|
||||
if (line) {drawLine(qp, s, line);}
|
||||
}
|
||||
|
||||
qp.setPen(Qt::gray);
|
||||
for (const Floorplan::FloorOutlinePolygon* poly : floor->outline) {
|
||||
drawOutline(qp, s, poly);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void drawLine(QPainter& qp, const Scaler2D& s, const Floorplan::FloorObstacleLine* line) {
|
||||
const Point2 pt1 = s.mapToScreen(line->from);
|
||||
const Point2 pt2 = s.mapToScreen(line->to);
|
||||
qp.drawLine(pt1.x, pt1.y, pt2.x, pt2.y);
|
||||
}
|
||||
|
||||
void drawOutline(QPainter& qp, const Scaler2D& s, const Floorplan::FloorOutlinePolygon* poly) {
|
||||
const int num = poly->poly.points.size();
|
||||
for (int i = 0; i < num; ++i) {
|
||||
const Point2 pt1 = s.mapToScreen(poly->poly.points[(i+0)]);
|
||||
const Point2 pt2 = s.mapToScreen(poly->poly.points[(i+1)%num]);
|
||||
qp.drawLine(pt1.x, pt1.y, pt2.x, pt2.y);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // FLOOR2D_H
|
||||
20
ui/map/2D/HasSelectableNodes.h
Normal file
20
ui/map/2D/HasSelectableNodes.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef HASSELECTABLENODES_H
|
||||
#define HASSELECTABLENODES_H
|
||||
|
||||
#include <vector>
|
||||
#include <KLib/geo/Point2.h>
|
||||
|
||||
class HasSelectableNodes {
|
||||
|
||||
|
||||
public:
|
||||
|
||||
virtual ~HasSelectableNodes() {;}
|
||||
|
||||
virtual std::vector<Point2> getNodes() const = 0;
|
||||
|
||||
virtual void selectNode(const int idx) = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // HASSELECTABLENODES_H
|
||||
184
ui/map/2D/MapView2D.cpp
Normal file
184
ui/map/2D/MapView2D.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
#include "MapView2D.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QResizeEvent>
|
||||
#include <QSlider>
|
||||
#include <QGridLayout>
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
|
||||
#include "Floor2D.h"
|
||||
#include "ColorPoints2D.h"
|
||||
#include "Path2D.h"
|
||||
#include "WiFiCalibTool.h"
|
||||
|
||||
#include "../../Icons.h"
|
||||
#include "../../UIHelper.h"
|
||||
|
||||
|
||||
MapView2D::MapView2D(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setRenderHeight(0);
|
||||
|
||||
colorPoints = new ColorPoints2D();
|
||||
elements.push_back(colorPoints);
|
||||
|
||||
pathToDest = new Path2D();
|
||||
pathToDest->setColor(Qt::blue);
|
||||
pathToDest->setWidth(10);
|
||||
elements.push_back(pathToDest);
|
||||
|
||||
pathWalked = new Path2D();
|
||||
pathToDest->setColor(Qt::black);
|
||||
pathToDest->setWidth(5);
|
||||
elements.push_back(pathWalked);
|
||||
|
||||
|
||||
// buttons
|
||||
//menu = new QWidget(this);
|
||||
QGridLayout* lay = new QGridLayout(this);
|
||||
int row = 0;
|
||||
|
||||
|
||||
lay->addItem(new QSpacerItem(0,0,QSizePolicy::Minimum,QSizePolicy::Expanding), row, 0, 1, 1);
|
||||
|
||||
++ row;
|
||||
|
||||
// // map-layer slider
|
||||
// sldLayer = new QSlider();
|
||||
// sldLayer->setOrientation(Qt::Horizontal);
|
||||
// connect(sldLayer, &QSlider::sliderMoved, this, &MapView2D::onLayerSelect);
|
||||
// connect(sldLayer, &QSlider::sliderReleased, this, &MapView2D::onLayerSelect);
|
||||
// lay->addWidget(sldLayer, row, 0, 1, 1);
|
||||
|
||||
// show/hide button
|
||||
const int bs = UIHelper::getButtonSize(this);
|
||||
btnColorPoints = new QPushButton(Icons::getIcon("dots", bs), "");
|
||||
btnColorPoints->connect(btnColorPoints, &QPushButton::clicked, [&] () {colorPoints->setVisible(!colorPoints->isVisible()); emit update();} );
|
||||
lay->addWidget(btnColorPoints, row, 0, 1, 1);
|
||||
|
||||
btnLayerMinus = new QPushButton("-");
|
||||
connect(btnLayerMinus, &QPushButton::clicked, this, &MapView2D::onLayerMinus);
|
||||
lay->addWidget(btnLayerMinus, row, 1, 1, 1);
|
||||
|
||||
btnLayerPlus = new QPushButton("+");
|
||||
connect(btnLayerPlus, &QPushButton::clicked, this, &MapView2D::onLayerPlus);
|
||||
lay->addWidget(btnLayerPlus, row, 2, 1, 1);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MapView2D::onLayerSelect() {
|
||||
setRenderHeight(sldLayer->value());
|
||||
}
|
||||
|
||||
void MapView2D::onLayerMinus() {
|
||||
if (layerHeight_m <= 0) {return;}
|
||||
layerHeight_m -= 1;
|
||||
setRenderHeight(layerHeight_m);
|
||||
}
|
||||
void MapView2D::onLayerPlus() {
|
||||
if (layerHeight_m >= 16) {return;}
|
||||
layerHeight_m += 1;
|
||||
setRenderHeight(layerHeight_m);
|
||||
}
|
||||
|
||||
void MapView2D::setMap(WiFiCalibrationDataModel* mdl, Floorplan::IndoorMap* map) {
|
||||
|
||||
for (Floorplan::Floor* floor : map->floors) {
|
||||
Floor2D* f = new Floor2D(floor);
|
||||
elements.push_back(f);
|
||||
}
|
||||
|
||||
wifiCalib = new WiFiCalibTool(mdl, map);
|
||||
elements.push_back(wifiCalib);
|
||||
|
||||
scaler.setCenterM(Point2(70, 35));
|
||||
|
||||
}
|
||||
|
||||
void MapView2D::showParticles(const std::vector<K::Particle<MyState>>* particles) {
|
||||
this->colorPoints->setFromParticles(*particles);
|
||||
}
|
||||
|
||||
void MapView2D::setCurrentEstimation(const Point3 pos_m, const Point3 dir) {
|
||||
(void) dir;
|
||||
setRenderHeight(pos_m.z);
|
||||
scaler.setCenterM(pos_m.xy());
|
||||
}
|
||||
|
||||
void MapView2D::setRenderHeight(const float height_m) {
|
||||
renderParams.clip.aboveHeight_m = height_m + 1.5;
|
||||
renderParams.clip.belowHeight_m = height_m - 1.5;
|
||||
emit update();
|
||||
}
|
||||
|
||||
void MapView2D::showGridImportance(Grid<MyGridNode>* grid) {
|
||||
colorPoints->showGridImportance(grid);
|
||||
}
|
||||
|
||||
void MapView2D::resizeEvent(QResizeEvent* evt) {
|
||||
(void) evt;
|
||||
int s = UIHelper::getButtonSize(this->parent()) * 1.5;
|
||||
//sldLayer->setMinimumHeight(s);
|
||||
//sldLayer->setMinimum(0);
|
||||
//sldLayer->setMaximum(16);
|
||||
btnColorPoints->setMinimumHeight(s);
|
||||
btnColorPoints->setMinimumWidth(s);
|
||||
btnLayerMinus->setMinimumHeight(s);
|
||||
btnLayerMinus->setMinimumWidth(s);
|
||||
btnLayerPlus->setMinimumHeight(s);
|
||||
btnLayerPlus->setMinimumWidth(s);
|
||||
|
||||
scaler.setScreenSize(width(), height());
|
||||
scaler.setScale( UIHelper::isLarge(this->parent()) ? 2 : 1 );
|
||||
|
||||
}
|
||||
|
||||
void MapView2D::mousePressEvent(QMouseEvent* evt) {
|
||||
move.startCenter_px = scaler.getCenterPX();
|
||||
move.startMouse_px = Point2(evt->x(), evt->y());
|
||||
}
|
||||
|
||||
void MapView2D::mouseMoveEvent(QMouseEvent* evt) {
|
||||
Point2 pt(evt->x(), evt->y());
|
||||
pt -= move.startMouse_px;
|
||||
pt.x = -pt.x;
|
||||
pt += move.startCenter_px;
|
||||
scaler.setCenterPX(pt);
|
||||
emit update();
|
||||
}
|
||||
|
||||
void MapView2D::mouseReleaseEvent(QMouseEvent* evt) {
|
||||
|
||||
if (!wifiCalib) {return;}
|
||||
|
||||
const Point2 p1(evt->x(), evt->y());
|
||||
|
||||
int idx = 0;
|
||||
for (const Point2 p2 : wifiCalib->getNodes()) {
|
||||
const float dist = p1.getDistance(p2);
|
||||
if (dist < 25) { wifiCalib->selectNode(idx); emit update(); break; }
|
||||
++idx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MapView2D::paintEvent(QPaintEvent*) {
|
||||
|
||||
QPainter qp(this);
|
||||
|
||||
// clear
|
||||
qp.fillRect(0, 0, width(), height(), Qt::white);
|
||||
|
||||
// render elements
|
||||
for (Renderable2D* r : elements) {
|
||||
r->render(qp, scaler, renderParams);
|
||||
}
|
||||
|
||||
qp.end();
|
||||
|
||||
}
|
||||
131
ui/map/2D/MapView2D.h
Normal file
131
ui/map/2D/MapView2D.h
Normal file
@@ -0,0 +1,131 @@
|
||||
#ifndef MAPVIEW2D_H
|
||||
#define MAPVIEW2D_H
|
||||
|
||||
#include "../misc/fixc11.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "Scaler2D.h"
|
||||
#include "Path2D.h"
|
||||
#include "Renderable2D.h"
|
||||
|
||||
#include <Indoor/nav/dijkstra/DijkstraPath.h>
|
||||
#include <Indoor/geo/Point3.h>
|
||||
|
||||
namespace Floorplan {
|
||||
class IndoorMap;
|
||||
}
|
||||
|
||||
template <typename T> class Grid;
|
||||
class MyGridNode;
|
||||
class Renderable2D;
|
||||
class QSlider;
|
||||
class QPushButton;
|
||||
class ColorPoints2D;
|
||||
class Path2D;
|
||||
|
||||
template <typename T> class DijkstraPath;
|
||||
namespace K {
|
||||
template <typename T> class Particle;
|
||||
}
|
||||
class MyState;
|
||||
|
||||
class WiFiCalibTool;
|
||||
class WiFiCalibrationDataModel;
|
||||
|
||||
class MapView2D : public QWidget {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
std::vector<Renderable2D*> elements;
|
||||
ColorPoints2D* colorPoints = nullptr;
|
||||
Path2D* pathToDest = nullptr;
|
||||
Path2D* pathWalked = nullptr;
|
||||
WiFiCalibTool* wifiCalib = nullptr;
|
||||
|
||||
Scaler2D scaler;
|
||||
RenderParams2D renderParams;
|
||||
float layerHeight_m = 0;
|
||||
|
||||
struct Move {
|
||||
Point2 startCenter_px;
|
||||
Point2 startMouse_px;
|
||||
} move;
|
||||
|
||||
QWidget* menu = nullptr;
|
||||
QSlider* sldLayer = nullptr;
|
||||
QPushButton* btnColorPoints = nullptr;
|
||||
QPushButton* btnLayerPlus = nullptr;
|
||||
QPushButton* btnLayerMinus = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
explicit MapView2D(QWidget *parent = 0);
|
||||
|
||||
/** set the to-be-shown map */
|
||||
void setMap(WiFiCalibrationDataModel* mdl, Floorplan::IndoorMap* map);
|
||||
|
||||
/** show importance factors for the grid */
|
||||
void showGridImportance(Grid<MyGridNode>* grid);
|
||||
|
||||
/** set the height-slice to be visible */
|
||||
void setRenderHeight(const float height_m);
|
||||
|
||||
|
||||
|
||||
/** set the path to the destination MUST BE CALLED FROM THE MAIN THREAD */
|
||||
void setPathToDestination(const std::vector<Point3>& path);
|
||||
|
||||
/** set the path to disply. MUST BE CALLED FROM THE MAIN THREAD */
|
||||
template <typename Node> void setPathToDestination(const DijkstraPath<Node>* path) {this->pathToDest->set(*path);}
|
||||
|
||||
/** set the path to disply. MUST BE CALLED FROM THE MAIN THREAD*/
|
||||
Q_INVOKABLE void setPathToDestination(const void* path) { setPathToDestination( (const DijkstraPath<MyGridNode>*) path); }
|
||||
|
||||
|
||||
|
||||
/** set the walked path. MUST BE CALLED FROM THE MAIN THREAD */
|
||||
void setPathWalked(const std::vector<Point3>& path) {this->pathWalked->set(path);}
|
||||
|
||||
/** set the walked path. MUST BE CALLED FROM THE MAIN THREAD */
|
||||
Q_INVOKABLE void setPathWalked(const void* path) { this->pathWalked->set( *((const std::vector<Point3>*) path)); }
|
||||
|
||||
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
Q_INVOKABLE void showParticles(const void* particles) {
|
||||
showParticles((const std::vector<K::Particle<MyState>>*) particles);
|
||||
}
|
||||
|
||||
/** NOTE: must be called from Qt's main thread! */
|
||||
void showParticles(const std::vector<K::Particle<MyState>>* particles);
|
||||
|
||||
|
||||
|
||||
/** set the currently estimated position */
|
||||
void setCurrentEstimation(const Point3 pos, const Point3 dir);
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
protected slots:
|
||||
|
||||
void onLayerSelect();
|
||||
void onLayerPlus();
|
||||
void onLayerMinus();
|
||||
|
||||
public slots:
|
||||
|
||||
void resizeEvent(QResizeEvent*);
|
||||
|
||||
void paintEvent(QPaintEvent*);
|
||||
|
||||
void mousePressEvent(QMouseEvent*);
|
||||
void mouseMoveEvent(QMouseEvent*);
|
||||
void mouseReleaseEvent(QMouseEvent*);
|
||||
|
||||
};
|
||||
|
||||
#endif // MAPVIEW2D_H
|
||||
123
ui/map/2D/Path2D.h
Normal file
123
ui/map/2D/Path2D.h
Normal file
@@ -0,0 +1,123 @@
|
||||
#ifndef PATH2D_H
|
||||
#define PATH2D_H
|
||||
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "Renderable2D.h"
|
||||
#include <Indoor/geo/Point3.h>
|
||||
#include <Indoor/nav/dijkstra/DijkstraPath.h>
|
||||
#include <Indoor/grid/Grid.h>
|
||||
|
||||
#include "../nav/Node.h"
|
||||
|
||||
|
||||
class Path2D : public Renderable2D {
|
||||
|
||||
private:
|
||||
|
||||
/** the path */
|
||||
std::vector<Point3> path;
|
||||
|
||||
float width = 8;
|
||||
QColor color = Qt::blue;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
Path2D() {
|
||||
|
||||
}
|
||||
|
||||
template <typename Node> void set(const DijkstraPath<Node>& path) {
|
||||
|
||||
std::vector<Point3> out;
|
||||
for (const DijkstraNode<Node>* node : path.getVector()) {
|
||||
if (!node) {break;}
|
||||
const Node* elem = node->element;
|
||||
out.push_back(Point3(elem->x_cm/100.0f, elem->y_cm/100.0f, elem->z_cm/100.0f));
|
||||
}
|
||||
|
||||
set(out);
|
||||
|
||||
}
|
||||
|
||||
/** MUST BE CALLED FROM THE MAIN THREAD */
|
||||
void set(const std::vector<Point3>& path) {
|
||||
this->path = simplify(path);
|
||||
}
|
||||
|
||||
|
||||
/** combine nodes while the direction stays the same (many small quads -> one large quad) */
|
||||
std::vector<Point3> simplify(const std::vector<Point3>& path) {
|
||||
|
||||
// copy
|
||||
std::vector<Point3> out = path;
|
||||
|
||||
// remove unneccesary nodes
|
||||
for (int i = 1; i < (int) out.size() - 1; ++i) {
|
||||
|
||||
const Point3 pa = out[i-1];
|
||||
const Point3 pb = out[i-0];
|
||||
const Point3 pc = out[i+1];
|
||||
|
||||
// same direction as last segment? combine segments!
|
||||
const float dir1 = std::atan2(pb.y-pa.y, pb.x-pa.x); // last edge
|
||||
const float dir2 = std::atan2(pc.y-pb.y, pc.x-pb.x); // next edge
|
||||
const bool isSameDir = std::abs(dir1-dir2) < 0.03; // last-edge and next-edge have (approx) the same direction?
|
||||
if (isSameDir) {out.erase(out.begin()+i); --i; continue;} // no additional information! remove the center node
|
||||
|
||||
// too many changes in a small space? -> remove some!
|
||||
const float d1 = pb.getDistance(pa); // distance to last node
|
||||
const float d2 = pb.getDistance(pc); // distance to next node
|
||||
const float min = 1.0;
|
||||
const bool isPackedChange = d1 < min && d2 < min; // both distances below a threshold?
|
||||
if (isPackedChange) {out.erase(out.begin()+i); --i; continue;} // -> many changes in a small area -> remove current node!
|
||||
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** the color to use */
|
||||
void setColor(const QColor color) {
|
||||
this->color = color;
|
||||
}
|
||||
|
||||
/** the width to use */
|
||||
void setWidth(const float w) {
|
||||
this->width = w;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
void doRender(QPainter& qp, const Scaler2D& s, const RenderParams2D& r) override {
|
||||
|
||||
QPen pen;
|
||||
pen.setWidth(this->width);
|
||||
pen.setColor(color);
|
||||
qp.setPen(pen);
|
||||
|
||||
for (int i = 0; i < (int)path.size() - 1; ++i) {
|
||||
|
||||
const Point3 p1 = path[i];
|
||||
const Point3 p2 = path[i+1];
|
||||
|
||||
if (p1.z < r.clip.belowHeight_m && p2.z < r.clip.belowHeight_m) {continue;}
|
||||
if (p1.z > r.clip.aboveHeight_m && p2.z > r.clip.aboveHeight_m) {continue;}
|
||||
|
||||
const Point2 pa1 = s.mapToScreen(p1.xy());
|
||||
const Point2 pa2 = s.mapToScreen(p2.xy());
|
||||
|
||||
qp.drawLine(pa1.x, pa1.y, pa2.x, pa2.y);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // PATH2D_H
|
||||
13
ui/map/2D/RenderParams2D.h
Normal file
13
ui/map/2D/RenderParams2D.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef RENDERPARAMS2D_H
|
||||
#define RENDERPARAMS2D_H
|
||||
|
||||
struct RenderParams2D {
|
||||
|
||||
struct Clipping {
|
||||
float belowHeight_m;
|
||||
float aboveHeight_m;
|
||||
} clip;
|
||||
|
||||
};
|
||||
|
||||
#endif // RENDERPARAMS2D_H
|
||||
37
ui/map/2D/Renderable2D.h
Normal file
37
ui/map/2D/Renderable2D.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef RENDERABLE2D_H
|
||||
#define RENDERABLE2D_H
|
||||
|
||||
#include <QPainter>
|
||||
#include "Scaler2D.h"
|
||||
#include "RenderParams2D.h"
|
||||
|
||||
class Renderable2D {
|
||||
|
||||
private:
|
||||
|
||||
bool visible = true;
|
||||
|
||||
public:
|
||||
|
||||
virtual ~Renderable2D() {;}
|
||||
|
||||
/** show/hide this element */
|
||||
void setVisible(const bool visible) {this->visible = visible;}
|
||||
|
||||
/** is this element currently visible? */
|
||||
bool isVisible() const {return this->visible;}
|
||||
|
||||
/** render this element */
|
||||
void render(QPainter& qp, const Scaler2D& s, const RenderParams2D& p) {
|
||||
if (!visible) {return;}
|
||||
doRender(qp, s, p);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/** subclasses render themselves here */
|
||||
virtual void doRender(QPainter& qp, const Scaler2D& s, const RenderParams2D& p) = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // RENDERABLE2D_H
|
||||
106
ui/map/2D/Scaler2D.h
Normal file
106
ui/map/2D/Scaler2D.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef SCALER2D_H
|
||||
#define SCALER2D_H
|
||||
|
||||
#include <QPoint>
|
||||
#include <Indoor/geo/Point2.h>
|
||||
|
||||
class Scaler2D {
|
||||
|
||||
private:
|
||||
|
||||
Point2 screenSize = Point2(400,400);
|
||||
Point2 center_m = Point2(0,0);
|
||||
float rotation_rad = 0.0f;
|
||||
float scaleFactor = 1;
|
||||
|
||||
public:
|
||||
|
||||
Scaler2D() {
|
||||
;
|
||||
}
|
||||
|
||||
/** set the screen's size (in pixel) */
|
||||
void setScreenSize(const float w_px, const float h_px) {
|
||||
this->screenSize = Point2(w_px, h_px);
|
||||
}
|
||||
|
||||
/** change the point displayed within the center of the screen */
|
||||
void setCenter(const float x_m, const float y_m) {
|
||||
this->center_m = Point2(x_m, y_m);
|
||||
}
|
||||
|
||||
/** change the point displayed within the center of the screen */
|
||||
void setCenterM(const Point2 center_m) {
|
||||
this->center_m = center_m;
|
||||
}
|
||||
|
||||
/** change the point displayed within the center of the screen */
|
||||
void setCenterPX(const Point2 center_px) {
|
||||
this->center_m = pxToM(center_px);
|
||||
}
|
||||
|
||||
Point2 getCenterPX() const {
|
||||
return mToPX(this->center_m);
|
||||
}
|
||||
|
||||
/** set the map's rotation in radians */
|
||||
void setRotation(const float rad) {
|
||||
this->rotation_rad = rad;
|
||||
}
|
||||
|
||||
void setScale(const float scale) {
|
||||
this->scaleFactor = scale;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
float mToPX(const float m) const {
|
||||
return m * scaleFactor * (screenSize.x * 0.01);
|
||||
}
|
||||
|
||||
float pxToM(const float px) const {
|
||||
return px / scaleFactor / (screenSize.x * 0.01);
|
||||
}
|
||||
|
||||
Point2 pxToM(const Point2 pt) const {
|
||||
return Point2(pxToM(pt.x), pxToM(pt.y));
|
||||
}
|
||||
|
||||
Point2 mToPX(const Point2 pt) const {
|
||||
return Point2(mToPX(pt.x), mToPX(pt.y));
|
||||
}
|
||||
|
||||
/** convert map to screen coordinates */
|
||||
Point2 mapToScreen(const Point2 pt_m) const {
|
||||
|
||||
Point2 pt = pt_m;
|
||||
|
||||
// move to (0,0)
|
||||
pt -= center_m;
|
||||
|
||||
// rotate
|
||||
pt = Point2(
|
||||
std::cos(rotation_rad) * pt.x - std::sin(rotation_rad) * pt.y,
|
||||
std::sin(rotation_rad) * pt.x + std::cos(rotation_rad) * pt.y
|
||||
);
|
||||
|
||||
// scale
|
||||
pt.x = mToPX(pt.x);
|
||||
pt.y = mToPX(pt.y);
|
||||
|
||||
|
||||
// add screen-center
|
||||
pt += screenSize/2;
|
||||
|
||||
// negate y
|
||||
pt.y = screenSize.y - pt.y;
|
||||
|
||||
// done
|
||||
return pt;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // SCALER2D_H
|
||||
129
ui/map/2D/WiFiCalibTool.h
Normal file
129
ui/map/2D/WiFiCalibTool.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef WIFICALIBTOOL_H
|
||||
#define WIFICALIBTOOL_H
|
||||
|
||||
|
||||
#include <Indoor/floorplan/v2/Floorplan.h>
|
||||
#include "Renderable2D.h"
|
||||
|
||||
#include "../../../Settings.h"
|
||||
#include "HasSelectableNodes.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
#include "../../../tools/calibration/WiFiCalibrationDataModel.h"
|
||||
#include "../../../tools/calibration/WiFiCalibrationScanDialog.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include "../ui/UIHelper.h"
|
||||
|
||||
#include "../../../sensors/SensorFactory.h"
|
||||
|
||||
struct WiFiCalibPoint {
|
||||
std::string name; // title
|
||||
Point3 pos_m; // map position + smartphone height
|
||||
Point2 pos_px; // screen position
|
||||
WiFiCalibPoint(const std::string& name, const Point3 pos_m, const Point2 pos_px) : name(name), pos_m(pos_m), pos_px(pos_px) {;}
|
||||
WiFiCalibPoint() {;}
|
||||
};
|
||||
|
||||
/**
|
||||
* helper for wifi calibration
|
||||
*/
|
||||
class WiFiCalibTool : public Renderable2D {
|
||||
|
||||
private:
|
||||
|
||||
WiFiCalibrationDataModel* mdl;
|
||||
Floorplan::IndoorMap* map;
|
||||
|
||||
std::vector<WiFiCalibPoint> currentlyVisible;
|
||||
WiFiCalibPoint currentlySelected;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
WiFiCalibTool(WiFiCalibrationDataModel* mdl, Floorplan::IndoorMap* map) : mdl(mdl), map(map) {
|
||||
|
||||
}
|
||||
|
||||
virtual void selectNode(const int idx) {
|
||||
currentlySelected = currentlyVisible[idx];
|
||||
showCalib(currentlySelected);
|
||||
}
|
||||
|
||||
/** get all selectable caliration nodes */
|
||||
virtual std::vector<Point2> getNodes() const {
|
||||
std::vector<Point2> pts;
|
||||
for (const WiFiCalibPoint& cp : currentlyVisible) {pts.push_back(cp.pos_px);}
|
||||
return pts;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void doRender(QPainter& qp, const Scaler2D& s, const RenderParams2D& r) override {
|
||||
|
||||
currentlyVisible.clear();
|
||||
const QFont font("Arial", 10);
|
||||
qp.setFont(font);
|
||||
|
||||
for (const Floorplan::Floor* floor : map->floors) {
|
||||
for (const Floorplan::Beacon* beacon : floor->beacons) {
|
||||
|
||||
const Point3 p = beacon->pos + Point3(0,0,floor->atHeight) + Point3(0,0,Settings::smartphoneAboveGround);
|
||||
const Point2 pt = s.mapToScreen(p.xy());
|
||||
|
||||
if (floor->atHeight < r.clip.belowHeight_m) {continue;}
|
||||
if (floor->atHeight > r.clip.aboveHeight_m) {continue;}
|
||||
|
||||
const WiFiCalibPoint cp(beacon->name, p, pt);
|
||||
currentlyVisible.push_back(cp);
|
||||
|
||||
const WiFiFingerprint& fp = mdl->getFingerprint(cp.pos_m);
|
||||
const QString txt1(beacon->name.c_str());
|
||||
const QString txt2 = QString::number(fp.measurements.entries.size());
|
||||
qp.setPen(Qt::black);
|
||||
|
||||
if (currentlySelected.name == cp.name) {
|
||||
qp.setBrush(Qt::blue);
|
||||
} else {
|
||||
qp.setBrush(Qt::NoBrush);
|
||||
}
|
||||
|
||||
//FONT SIZE??
|
||||
|
||||
|
||||
int s = 20;
|
||||
qp.drawEllipse(pt.x-s, pt.y-s, s*2, s*2);
|
||||
qp.drawText(pt.x+s*2, pt.y-s, txt1);
|
||||
qp.drawText(pt.x+s*2, pt.y+s, txt2);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
|
||||
|
||||
void showCalib(const WiFiCalibPoint& cp) {
|
||||
|
||||
// get (or create an empty one) the fingerprint for this location
|
||||
WiFiFingerprint& fp = mdl->getFingerprint(cp.pos_m);
|
||||
|
||||
// edit it (blocking!)
|
||||
WiFiCalibrationScanDialog::get(fp);
|
||||
|
||||
// save the model
|
||||
mdl->save();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFICALIBTOOL_H
|
||||
Reference in New Issue
Block a user