From 958c81fb1d8ede9d859e040e37981e79892e0940 Mon Sep 17 00:00:00 2001 From: peri4 Date: Fri, 20 Jan 2023 09:16:42 +0300 Subject: [PATCH] version 2.13.0 add Map library (MapView with OSM maps and items) and mapviewer util --- CMakeLists.txt | 2 +- icons/maps.png | Bin 0 -> 20841 bytes libs/map/CMakeLists.txt | 1 + libs/map/lang/qad_application_en.ts | 144 ++++++++++ libs/map/lang/qad_application_ru.ts | 144 ++++++++++ libs/map/lang/update.bat | 2 + libs/map/mapitembase.cpp | 95 ++++++ libs/map/mapitembase.h | 94 ++++++ libs/map/mapitemellipse.cpp | 77 +++++ libs/map/mapitemellipse.h | 53 ++++ libs/map/mapitemgeometrybase.cpp | 16 ++ libs/map/mapitemgeometrybase.h | 53 ++++ libs/map/mapitemgeopolygon.cpp | 35 +++ libs/map/mapitemgeopolygon.h | 44 +++ libs/map/mapitemgeopolyline.cpp | 34 +++ libs/map/mapitemgeopolyline.h | 44 +++ libs/map/mapitemimage.cpp | 38 +++ libs/map/mapitemimage.h | 46 +++ libs/map/mapitemnongeogeometrybase.cpp | 21 ++ libs/map/mapitemnongeogeometrybase.h | 49 ++++ libs/map/mapitempolygon.cpp | 24 ++ libs/map/mapitempolygon.h | 43 +++ libs/map/mapitempolyline.cpp | 23 ++ libs/map/mapitempolyline.h | 43 +++ libs/map/mapitemtext.cpp | 50 ++++ libs/map/mapitemtext.h | 51 ++++ libs/map/mapview.cpp | 317 +++++++++++++++++++++ libs/map/mapview.h | 104 +++++++ libs/map/osm_downloader.cpp | 73 +++++ libs/map/osm_downloader_p.h | 66 +++++ libs/map/osm_math_p.h | 66 +++++ libs/map/osm_tile_cache.cpp | 100 +++++++ libs/map/osm_tile_cache_p.h | 77 +++++ libs/map/osm_types.cpp | 62 ++++ libs/map/osm_types_p.h | 56 ++++ libs/map/plugin/CMakeLists.txt | 1 + libs/map/plugin/mapplugin.cpp | 70 +++++ libs/map/plugin/mapplugin.h | 33 +++ libs/map/plugin/mapview_designerplugin.cpp | 13 + libs/map/plugin/mapview_designerplugin.h | 23 ++ libs/map/qad_map.qrc | 5 + utils/mapviewer/CMakeLists.txt | 15 + utils/mapviewer/icons/maps.ico | Bin 0 -> 19531 bytes utils/mapviewer/icons/maps.png | Bin 0 -> 20841 bytes utils/mapviewer/main.cpp | 72 +++++ utils/mapviewer/mapviewer.qrc | 5 + 46 files changed, 2383 insertions(+), 1 deletion(-) create mode 100644 icons/maps.png create mode 100644 libs/map/CMakeLists.txt create mode 100644 libs/map/lang/qad_application_en.ts create mode 100644 libs/map/lang/qad_application_ru.ts create mode 100644 libs/map/lang/update.bat create mode 100644 libs/map/mapitembase.cpp create mode 100644 libs/map/mapitembase.h create mode 100644 libs/map/mapitemellipse.cpp create mode 100644 libs/map/mapitemellipse.h create mode 100644 libs/map/mapitemgeometrybase.cpp create mode 100644 libs/map/mapitemgeometrybase.h create mode 100644 libs/map/mapitemgeopolygon.cpp create mode 100644 libs/map/mapitemgeopolygon.h create mode 100644 libs/map/mapitemgeopolyline.cpp create mode 100644 libs/map/mapitemgeopolyline.h create mode 100644 libs/map/mapitemimage.cpp create mode 100644 libs/map/mapitemimage.h create mode 100644 libs/map/mapitemnongeogeometrybase.cpp create mode 100644 libs/map/mapitemnongeogeometrybase.h create mode 100644 libs/map/mapitempolygon.cpp create mode 100644 libs/map/mapitempolygon.h create mode 100644 libs/map/mapitempolyline.cpp create mode 100644 libs/map/mapitempolyline.h create mode 100644 libs/map/mapitemtext.cpp create mode 100644 libs/map/mapitemtext.h create mode 100644 libs/map/mapview.cpp create mode 100644 libs/map/mapview.h create mode 100644 libs/map/osm_downloader.cpp create mode 100644 libs/map/osm_downloader_p.h create mode 100644 libs/map/osm_math_p.h create mode 100644 libs/map/osm_tile_cache.cpp create mode 100644 libs/map/osm_tile_cache_p.h create mode 100644 libs/map/osm_types.cpp create mode 100644 libs/map/osm_types_p.h create mode 100644 libs/map/plugin/CMakeLists.txt create mode 100644 libs/map/plugin/mapplugin.cpp create mode 100644 libs/map/plugin/mapplugin.h create mode 100644 libs/map/plugin/mapview_designerplugin.cpp create mode 100644 libs/map/plugin/mapview_designerplugin.h create mode 100644 libs/map/qad_map.qrc create mode 100644 utils/mapviewer/CMakeLists.txt create mode 100644 utils/mapviewer/icons/maps.ico create mode 100644 utils/mapviewer/icons/maps.png create mode 100644 utils/mapviewer/main.cpp create mode 100644 utils/mapviewer/mapviewer.qrc diff --git a/CMakeLists.txt b/CMakeLists.txt index b5571ac..563b6df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_policy(SET CMP0017 NEW) # need include() with .cmake cmake_policy(SET CMP0072 NEW) # FindOpenGL prefers GLVND by default project(QAD) set(QAD_MAJOR 2) -set(QAD_MINOR 12) +set(QAD_MINOR 13) set(QAD_REVISION 0) set(QAD_SUFFIX ) set(QAD_COMPANY SHS) diff --git a/icons/maps.png b/icons/maps.png new file mode 100644 index 0000000000000000000000000000000000000000..ae68d5483413487ef2d91a9e4098c2d797c33b9e GIT binary patch literal 20841 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 Xt{Q>V*RlD800000NkvXXu0mjftL8%x literal 0 HcmV?d00001 diff --git a/libs/map/CMakeLists.txt b/libs/map/CMakeLists.txt new file mode 100644 index 0000000..ff89440 --- /dev/null +++ b/libs/map/CMakeLists.txt @@ -0,0 +1 @@ +qad_library(map "Gui;Widgets;Network" "qad_widgets") diff --git a/libs/map/lang/qad_application_en.ts b/libs/map/lang/qad_application_en.ts new file mode 100644 index 0000000..cc85e91 --- /dev/null +++ b/libs/map/lang/qad_application_en.ts @@ -0,0 +1,144 @@ + + + + + AboutWindow + + + - About + + + + + Versions + + + + + Build + + + + + Authors + + + + + About Qt... + + + + + OK + + + + + + About + + + + + EMainWindow + + + + Clear recent list + + + + + + + + Show all + + + + + + + + Hide all + + + + + Toolbars + + + + + Docks + + + + + Select file to open + + + + + Select files to open + + + + + Save changes%1? + + + + + in + + + + + Select file to save + + + + + HistoryView + + + + History cleared + + + + + LogView + + + Category: + + + + + + + Clear + + + + + + Select All + + + + + + Copy + + + + + All + + + + diff --git a/libs/map/lang/qad_application_ru.ts b/libs/map/lang/qad_application_ru.ts new file mode 100644 index 0000000..bb4ba21 --- /dev/null +++ b/libs/map/lang/qad_application_ru.ts @@ -0,0 +1,144 @@ + + + + + AboutWindow + + + - About + - О программе + + + + Versions + Версии + + + + Build + Сборка + + + + Authors + Авторы + + + + About Qt... + О Qt ... + + + + OK + + + + + + About + О программе + + + + EMainWindow + + + + Clear recent list + Очистить список недавних + + + + + + + Show all + Показать все + + + + + + + Hide all + Скрыть все + + + + Toolbars + Панели инструментов + + + + Docks + Окна + + + + Select file to open + Выбрать файл для открытия + + + + Select files to open + Выберите файлы для открытия + + + + Save changes%1? + Сохранить изменения%1? + + + + in + в + + + + Select file to save + Выберите файл для сохранения + + + + HistoryView + + + + History cleared + История очищена + + + + LogView + + + Category: + Категория: + + + + + + Clear + Очистить + + + + + Select All + Выделить всё + + + + + Copy + Копировать + + + + All + Все + + + diff --git a/libs/map/lang/update.bat b/libs/map/lang/update.bat new file mode 100644 index 0000000..34f3eee --- /dev/null +++ b/libs/map/lang/update.bat @@ -0,0 +1,2 @@ +lupdate ../ -ts qad_application_ru.ts +lupdate ../ -ts qad_application_en.ts diff --git a/libs/map/mapitembase.cpp b/libs/map/mapitembase.cpp new file mode 100644 index 0000000..b9089d0 --- /dev/null +++ b/libs/map/mapitembase.cpp @@ -0,0 +1,95 @@ +#include "mapitembase.h" + +#include "mapview.h" +#include "osm_math_p.h" + + +MapItemBase::MapItemBase() {} + + +MapItemBase::~MapItemBase() { + if (!parent) return; + parent->removeItem(this); +} + + +void MapItemBase::setInteracive(bool newInteracive) { + m_interacive = newInteracive; + updateParent(); +} + + +void MapItemBase::setVisible(bool newVisible) { + m_visible = newVisible; + updateParent(); +} + + +void MapItemBase::setRotation(double deg) { + m_rotation = deg; + updateParent(); +} + + +void MapItemBase::rotate(double deg) { + m_rotation += deg; + while (deg < 0) + deg += 360.; + while (deg > 360.) + deg -= 360.; + updateParent(); +} + + +void MapItemBase::scale(QPointF s) { + m_scale = {m_scale.x() * s.x(), m_scale.y() * s.y()}; + updateParent(); +} + + +void MapItemBase::setScale(QPointF s) { + m_scale = s; + updateParent(); +} + + +void MapItemBase::setPosition(QPointF geo) { + m_position = geo; + updatePosition(); + updateParent(); +} + + +void MapItemBase::setCursor(const QCursor & newCursor) { + m_cursor = newCursor; + updateParent(); +} + + +QPointF MapItemBase::mapToView(QPointF norm) const { + if (!parent) return norm; + return parent->mapFromNorm(norm); +} + + +double MapItemBase::scalePx2M(QPointF norm) const { + if (!parent) return 1.; + return parent->scalePx2M(norm); +} + + +void MapItemBase::updatePosition() { + norm_pos = OSM::geo2xy(m_position); + potitionChanged(); + zoomChanged(); +} + + +void MapItemBase::updateParent() { + if (parent) parent->update(); +} + + +void MapItemBase::setBoundingRect(QRectF b) { + m_bounding = b; +} diff --git a/libs/map/mapitembase.h b/libs/map/mapitembase.h new file mode 100644 index 0000000..55ba0ba --- /dev/null +++ b/libs/map/mapitembase.h @@ -0,0 +1,94 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitembase_h +#define mapitembase_h + +#include "qad_map_export.h" + +#include +#include +#include +#include +#include + +class MapView; + +class QAD_MAP_EXPORT MapItemBase { + friend class MapView; + +public: + explicit MapItemBase(); + virtual ~MapItemBase(); + + + bool isInteracive() const { return m_interacive; } + void setInteracive(bool newInteracive); + + bool isVisible() const { return m_visible; } + void setVisible(bool newVisible); + + double getRotation() const { return m_rotation; } + void setRotation(double deg); + void rotate(double deg); + + QPointF getScale() const { return m_scale; } + void scale(double s) { scale({s, s}); } + void scale(double sx, double sy) { scale({sx, sy}); } + void scale(QPointF s); + void setScale(double s) { setScale({s, s}); } + void setScale(double sx, double sy) { scale({sx, sy}); } + void setScale(QPointF s); + + QPointF position() const { return m_position; } + void setPosition(QPointF geo); + void setPosition(double lat, double lng) { setPosition({lat, lng}); } + + const QCursor & cursor() const { return m_cursor; } + void setCursor(const QCursor & newCursor); + + QVariant data(int id) const { return data_.value(id); } + void setData(int id, const QVariant & d) { data_[id] = d; } + +protected: + QPointF mapToView(QPointF norm) const; + double scalePx2M(QPointF norm) const; + virtual void draw(QPainter * p) = 0; + void updateParent(); + void setBoundingRect(QRectF b); + + QPointF norm_pos; + bool ignore_scale = false; + +private: + void updatePosition(); + virtual void potitionChanged() {} + virtual void zoomChanged() {} + + MapView * parent = nullptr; + bool m_visible = true, m_interacive = false; + double m_rotation = 0.; + QPointF m_position, m_scale = {1., 1.}; + QRectF m_bounding; + QCursor m_cursor; + QMap data_; +}; + + +#endif diff --git a/libs/map/mapitemellipse.cpp b/libs/map/mapitemellipse.cpp new file mode 100644 index 0000000..be7ad8b --- /dev/null +++ b/libs/map/mapitemellipse.cpp @@ -0,0 +1,77 @@ +#include "mapitemellipse.h" + +#include + + +MapItemEllipse::MapItemEllipse(const QRectF & r) { + setEllipse(r); +} + + +MapItemEllipse::MapItemEllipse(const QPointF & p, double rx, double ry) { + setEllipse(p, rx, ry); +} + + +void MapItemEllipse::setStartAngle(double a) { + sa = a; + rebuild(); + updateParent(); +} + + +void MapItemEllipse::setEndAngle(double a) { + ea = a; + rebuild(); + updateParent(); +} + + +void MapItemEllipse::setEllipse(const QRectF & r) { + rct = r; + rebuild(); + updateParent(); +} + + +void MapItemEllipse::setEllipse(const QPointF & p, double rx, double ry) { + rct.setWidth(rx * 2.); + rct.setHeight(ry * 2.); + rct.moveCenter(p); + rebuild(); + updateParent(); +} + + +void MapItemEllipse::draw(QPainter * p) { + p->setPen(pen()); + p->setBrush(brush()); + QPolygonF dp(pol); + double us = unitScale(); + for (auto & i: dp) + i = QPointF(i.x(), -i.y()) / us; + p->drawPolygon(dp); + setBoundingRect(dp.boundingRect()); +} + + +void MapItemEllipse::rebuild() { + pol.clear(); + QPointF c = rct.center(); + bool full = sa == ea; + if (!full) pol << c; + double w = rct.width() / 2., h = rct.height() / 2.; + static double deg2rad = M_PI / 180.; + if (full) { + sa = 0.; + ea = 360.; + } else { + if (ea < sa) qSwap(sa, ea); + } + for (double a = sa; a <= ea; a += 3.) { + if (a > ea) a = ea; + QPointF p(sin(a * deg2rad) * w, cos(a * deg2rad) * h); + pol << (c + p); + } + if (!full) pol << c; +} diff --git a/libs/map/mapitemellipse.h b/libs/map/mapitemellipse.h new file mode 100644 index 0000000..6dbd132 --- /dev/null +++ b/libs/map/mapitemellipse.h @@ -0,0 +1,53 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitemellipse_h +#define mapitemellipse_h + +#include "mapitemnongeogeometrybase.h" + + +/// ellipse in meters, based on map scale at "position", or pixels +/// north = 0 deg, positive is clockwise +class QAD_MAP_EXPORT MapItemEllipse: public MapItemNonGeoGeometryBase { +public: + MapItemEllipse(const QRectF & r = QRectF()); + MapItemEllipse(const QPointF & p, double rx, double ry); + + + QRectF rect() const { return rct; } + double startAngle() const { return sa; } + double endAngle() const { return ea; } + void setStartAngle(double a); + void setEndAngle(double a); + void setEllipse(const QRectF & r); + void setEllipse(const QPointF & p, double rx, double ry); + +protected: + void draw(QPainter * p) override; + void rebuild(); + +private: + QRectF rct; + QPolygonF pol; + double sa = 0., ea = 0.; +}; + + +#endif diff --git a/libs/map/mapitemgeometrybase.cpp b/libs/map/mapitemgeometrybase.cpp new file mode 100644 index 0000000..53dcfba --- /dev/null +++ b/libs/map/mapitemgeometrybase.cpp @@ -0,0 +1,16 @@ +#include "mapitemgeometrybase.h" + + +MapItemGeometryBase::MapItemGeometryBase() {} + + +void MapItemGeometryBase::setPen(QPen newPen) { + m_pen = newPen; + updateParent(); +} + + +void MapItemGeometryBase::setBrush(QBrush newBrush) { + m_brush = newBrush; + updateParent(); +} diff --git a/libs/map/mapitemgeometrybase.h b/libs/map/mapitemgeometrybase.h new file mode 100644 index 0000000..d87b92c --- /dev/null +++ b/libs/map/mapitemgeometrybase.h @@ -0,0 +1,53 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitemgeometrybase_h +#define mapitemgeometrybase_h + +#include "mapitembase.h" + +#include +#include +#include + +class QAD_MAP_EXPORT MapItemGeometryBase: public MapItemBase { + friend class MapView; + +public: + explicit MapItemGeometryBase(); + + + QPen pen() const { return m_pen; } + void setPen(QPen newPen); + + QBrush brush() const { return m_brush; } + void setBrush(QBrush newBrush); + +protected: + virtual void updatePolygon() {} + + QPolygonF norm_polygon; + +private: + QPen m_pen = QPen(Qt::black, 1); + QBrush m_brush = QBrush(Qt::white); +}; + + +#endif diff --git a/libs/map/mapitemgeopolygon.cpp b/libs/map/mapitemgeopolygon.cpp new file mode 100644 index 0000000..0d886ed --- /dev/null +++ b/libs/map/mapitemgeopolygon.cpp @@ -0,0 +1,35 @@ +#include "mapitemgeopolygon.h" + +#include "osm_math_p.h" + + +MapItemGeoPolygon::MapItemGeoPolygon(const QPolygonF & p) { + ignore_scale = true; + setPolygon(p); +} + + +void MapItemGeoPolygon::setPolygon(const QPolygonF & p) { + pol = p; + updatePolygon(); +} + + +void MapItemGeoPolygon::draw(QPainter * p) { + p->setPen(pen()); + p->setBrush(brush()); + QPolygonF dp(norm_polygon); + QPointF pos_offset = mapToView(norm_pos); + for (auto & i: dp) + i = mapToView(i) - pos_offset; + p->drawPolygon(dp); + setBoundingRect(dp.boundingRect()); +} + + +void MapItemGeoPolygon::updatePolygon() { + norm_polygon.resize(pol.size()); + for (int i = 0; i < pol.size(); ++i) + norm_polygon[i] = OSM::geo2xy(pol[i]); + updateParent(); +} diff --git a/libs/map/mapitemgeopolygon.h b/libs/map/mapitemgeopolygon.h new file mode 100644 index 0000000..5af0487 --- /dev/null +++ b/libs/map/mapitemgeopolygon.h @@ -0,0 +1,44 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitemgeopolygon_h +#define mapitemgeopolygon_h + +#include "mapitemgeometrybase.h" + + +/// polygon in geo-coordinates +class QAD_MAP_EXPORT MapItemGeoPolygon: public MapItemGeometryBase { +public: + explicit MapItemGeoPolygon(const QPolygonF & p = QPolygonF()); + + + QPolygonF polygon() const { return pol; } + void setPolygon(const QPolygonF & p); + +protected: + void draw(QPainter * p) override; + void updatePolygon() override; + +private: + QPolygonF pol; +}; + + +#endif diff --git a/libs/map/mapitemgeopolyline.cpp b/libs/map/mapitemgeopolyline.cpp new file mode 100644 index 0000000..19b6bc7 --- /dev/null +++ b/libs/map/mapitemgeopolyline.cpp @@ -0,0 +1,34 @@ +#include "mapitemgeopolyline.h" + +#include "osm_math_p.h" + + +MapItemGeoPolyline::MapItemGeoPolyline(const QPolygonF & p) { + ignore_scale = true; + setPolyline(p); +} + + +void MapItemGeoPolyline::setPolyline(const QPolygonF & p) { + pol = p; + updatePolygon(); +} + + +void MapItemGeoPolyline::draw(QPainter * p) { + p->setPen(pen()); + QPolygonF dp(norm_polygon); + QPointF pos_offset = mapToView(norm_pos); + for (auto & i: dp) + i = mapToView(i) - pos_offset; + p->drawPolyline(dp); + setBoundingRect(dp.boundingRect()); +} + + +void MapItemGeoPolyline::updatePolygon() { + norm_polygon.resize(pol.size()); + for (int i = 0; i < pol.size(); ++i) + norm_polygon[i] = OSM::geo2xy(pol[i]); + updateParent(); +} diff --git a/libs/map/mapitemgeopolyline.h b/libs/map/mapitemgeopolyline.h new file mode 100644 index 0000000..6b87e87 --- /dev/null +++ b/libs/map/mapitemgeopolyline.h @@ -0,0 +1,44 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitemgeopolyline_h +#define mapitemgeopolyline_h + +#include "mapitemgeometrybase.h" + + +/// polyline in geo-coordinates +class QAD_MAP_EXPORT MapItemGeoPolyline: public MapItemGeometryBase { +public: + explicit MapItemGeoPolyline(const QPolygonF & p = QPolygonF()); + + + QPolygonF polyline() const { return pol; } + void setPolyline(const QPolygonF & p); + +protected: + void draw(QPainter * p) override; + void updatePolygon() override; + +private: + QPolygonF pol; +}; + + +#endif diff --git a/libs/map/mapitemimage.cpp b/libs/map/mapitemimage.cpp new file mode 100644 index 0000000..8e2f3e1 --- /dev/null +++ b/libs/map/mapitemimage.cpp @@ -0,0 +1,38 @@ +#include "mapitemimage.h" + + +MapItemImage::MapItemImage(const QPixmap & p) { + setPixmap(p); +} + + +void MapItemImage::setPixmap(const QPixmap & p) { + m_pixmap = p; + updateParent(); +} + + +void MapItemImage::setAlignment(Qt::Alignment a) { + m_alignment = a; + updateParent(); +} + + +void MapItemImage::draw(QPainter * p) { + if (m_pixmap.isNull()) return; + QRectF pr(QPointF(0, 0), QSizeF(m_pixmap.size())); + if (m_alignment.testFlag(Qt::AlignHCenter)) { + pr.translate(-pr.center().x(), 0.); + } + if (m_alignment.testFlag(Qt::AlignRight)) { + pr.translate(-pr.width(), 0.); + } + if (m_alignment.testFlag(Qt::AlignVCenter)) { + pr.translate(0., -pr.center().y()); + } + if (m_alignment.testFlag(Qt::AlignBottom)) { + pr.translate(0., -pr.height()); + } + p->drawPixmap(pr, m_pixmap, m_pixmap.rect()); + setBoundingRect(pr); +} diff --git a/libs/map/mapitemimage.h b/libs/map/mapitemimage.h new file mode 100644 index 0000000..9c3a36b --- /dev/null +++ b/libs/map/mapitemimage.h @@ -0,0 +1,46 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitemimage_h +#define mapitemimage_h + +#include "mapitembase.h" + +#include + +class QAD_MAP_EXPORT MapItemImage: public MapItemBase { +public: + explicit MapItemImage(const QPixmap & p = QPixmap()); + + QPixmap pixmap() const { return m_pixmap; } + void setPixmap(const QPixmap & p); + + Qt::Alignment alignment() const { return m_alignment; } + void setAlignment(Qt::Alignment a); + +protected: + void draw(QPainter * p) override; + +private: + QPixmap m_pixmap; + Qt::Alignment m_alignment = Qt::AlignCenter; +}; + + +#endif diff --git a/libs/map/mapitemnongeogeometrybase.cpp b/libs/map/mapitemnongeogeometrybase.cpp new file mode 100644 index 0000000..59d58c2 --- /dev/null +++ b/libs/map/mapitemnongeogeometrybase.cpp @@ -0,0 +1,21 @@ +#include "mapitemnongeogeometrybase.h" + + +void MapItemNonGeoGeometryBase::setUnits(Units u) { + m_units = u; + updateParent(); +} + + +void MapItemNonGeoGeometryBase::zoomChanged() { + px2m = scalePx2M(norm_pos); +} + + +double MapItemNonGeoGeometryBase::unitScale() const { + switch (m_units) { + case Meters: return px2m; + default: break; + } + return 1.; +} diff --git a/libs/map/mapitemnongeogeometrybase.h b/libs/map/mapitemnongeogeometrybase.h new file mode 100644 index 0000000..ee34e92 --- /dev/null +++ b/libs/map/mapitemnongeogeometrybase.h @@ -0,0 +1,49 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitemnongeogeometrybase_h +#define mapitemnongeogeometrybase_h + +#include "mapitemgeometrybase.h" + + +/// ellipse in meters, based on map scale at "position" +/// north = 0 deg, positive is clockwise +class QAD_MAP_EXPORT MapItemNonGeoGeometryBase: public MapItemGeometryBase { +public: + enum Units { + Meters, + Pixels + }; + + Units units() const { return m_units; } + void setUnits(Units u); + +protected: + void zoomChanged() override; + double unitScale() const; + + +private: + Units m_units = Meters; + double px2m = 1.; +}; + + +#endif diff --git a/libs/map/mapitempolygon.cpp b/libs/map/mapitempolygon.cpp new file mode 100644 index 0000000..1a97ada --- /dev/null +++ b/libs/map/mapitempolygon.cpp @@ -0,0 +1,24 @@ +#include "mapitempolygon.h" + + +MapItemPolygon::MapItemPolygon(const QPolygonF & p) { + setPolygon(p); +} + + +void MapItemPolygon::setPolygon(const QPolygonF & p) { + pol = p; + updateParent(); +} + + +void MapItemPolygon::draw(QPainter * p) { + p->setPen(pen()); + p->setBrush(brush()); + QPolygonF dp(pol); + double us = unitScale(); + for (auto & i: dp) + i = QPointF(i.x(), -i.y()) / us; + p->drawPolygon(dp); + setBoundingRect(dp.boundingRect()); +} diff --git a/libs/map/mapitempolygon.h b/libs/map/mapitempolygon.h new file mode 100644 index 0000000..77e140d --- /dev/null +++ b/libs/map/mapitempolygon.h @@ -0,0 +1,43 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitempolygon_h +#define mapitempolygon_h + +#include "mapitemnongeogeometrybase.h" + + +/// polygon in meters, based on map scale at "position", or pixels +class QAD_MAP_EXPORT MapItemPolygon: public MapItemNonGeoGeometryBase { +public: + explicit MapItemPolygon(const QPolygonF & p = QPolygonF()); + + + QPolygonF polygon() const { return pol; } + void setPolygon(const QPolygonF & p); + +protected: + void draw(QPainter * p) override; + +private: + QPolygonF pol; +}; + + +#endif diff --git a/libs/map/mapitempolyline.cpp b/libs/map/mapitempolyline.cpp new file mode 100644 index 0000000..de9611a --- /dev/null +++ b/libs/map/mapitempolyline.cpp @@ -0,0 +1,23 @@ +#include "mapitempolyline.h" + + +MapItemPolyline::MapItemPolyline(const QPolygonF & p) { + setPolyline(p); +} + + +void MapItemPolyline::setPolyline(const QPolygonF & p) { + pol = p; + updateParent(); +} + + +void MapItemPolyline::draw(QPainter * p) { + p->setPen(pen()); + QPolygonF dp(pol); + double us = unitScale(); + for (auto & i: dp) + i = QPointF(i.x(), -i.y()) / us; + p->drawPolyline(dp); + setBoundingRect(dp.boundingRect()); +} diff --git a/libs/map/mapitempolyline.h b/libs/map/mapitempolyline.h new file mode 100644 index 0000000..1f4dcac --- /dev/null +++ b/libs/map/mapitempolyline.h @@ -0,0 +1,43 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitempolyline_h +#define mapitempolyline_h + +#include "mapitemnongeogeometrybase.h" + + +/// polyline in meters, based on map scale at "position", or pixels +class QAD_MAP_EXPORT MapItemPolyline: public MapItemNonGeoGeometryBase { +public: + explicit MapItemPolyline(const QPolygonF & p = QPolygonF()); + + + QPolygonF polyline() const { return pol; } + void setPolyline(const QPolygonF & p); + +protected: + void draw(QPainter * p) override; + +private: + QPolygonF pol; +}; + + +#endif diff --git a/libs/map/mapitemtext.cpp b/libs/map/mapitemtext.cpp new file mode 100644 index 0000000..401a2a8 --- /dev/null +++ b/libs/map/mapitemtext.cpp @@ -0,0 +1,50 @@ +#include "mapitemtext.h" + + +MapItemText::MapItemText(const QString & t) { + setBrush(Qt::NoBrush); + setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + setText(t); +} + + +void MapItemText::setAlignment(Qt::Alignment a) { + m_alignment = a; + updateParent(); +} + + +void MapItemText::setText(const QString & s) { + str = s; + updateParent(); +} + + +void MapItemText::setFont(const QFont & f) { + m_font = f; + updateParent(); +} + + +void MapItemText::draw(QPainter * p) { + if (str.isEmpty()) return; + p->setPen(pen()); + p->setFont(font()); + auto br = p->fontMetrics().boundingRect(str); + br.translate(-br.topLeft()); + if (m_alignment.testFlag(Qt::AlignHCenter)) { + br.translate(-br.center().x(), 0.); + } + if (m_alignment.testFlag(Qt::AlignRight)) { + br.translate(-br.width(), 0.); + } + if (m_alignment.testFlag(Qt::AlignVCenter)) { + br.translate(0., -br.center().y()); + } + if (m_alignment.testFlag(Qt::AlignBottom)) { + br.translate(0., -br.height()); + } + p->fillRect(br.adjusted(-2, 0, 2, 0), brush()); + p->drawText(br, Qt::TextSingleLine, str); + setBoundingRect(br); +} diff --git a/libs/map/mapitemtext.h b/libs/map/mapitemtext.h new file mode 100644 index 0000000..4763379 --- /dev/null +++ b/libs/map/mapitemtext.h @@ -0,0 +1,51 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapitemtext_h +#define mapitemtext_h + +#include "mapitemgeometrybase.h" + +#include + + +class QAD_MAP_EXPORT MapItemText: public MapItemGeometryBase { +public: + explicit MapItemText(const QString & t = QString()); + + Qt::Alignment alignment() const { return m_alignment; } + void setAlignment(Qt::Alignment a); + + QString text() const { return str; } + void setText(const QString & s); + + QFont font() const { return m_font; } + void setFont(const QFont & f); + +protected: + void draw(QPainter * p) override; + +private: + Qt::Alignment m_alignment = Qt::AlignCenter; + QString str; + QFont m_font; +}; + + +#endif diff --git a/libs/map/mapview.cpp b/libs/map/mapview.cpp new file mode 100644 index 0000000..dc2d9e2 --- /dev/null +++ b/libs/map/mapview.cpp @@ -0,0 +1,317 @@ +#include "mapview.h" + +#include "osm_downloader_p.h" +#include "osm_math_p.h" +#include "osm_tile_cache_p.h" +#include "qad_types.h" + +#include +#include +#include +#include +#include +#include + +const int tileSize = 256; + + +MapView::MapView(QWidget * parent): QWidget(parent) { + downloader = new OSMDownloader(this); + cache = new OSMTileCache(this); + setMouseTracking(true); + connect(cache, &OSMTileCache::tileReady, this, [this]() { drawBackground(); }); + int size = 16; + QPixmap px(QSize(size, size) * 2); + QPainter p(&px); + for (int i = 0; i < 2; ++i) { + for (int j = 0; j < 2; ++j) { + p.fillRect(i * size, j * size, size, size, (((i + j) % 2) == 0) ? Qt::white : Qt::lightGray); + } + } + p.end(); + brush_tr.setTexture(px); + zoomTo(0); +} + + +MapView::~MapView() { + for (auto * i: items_) + i->parent = nullptr; + qDeleteAll(items_); + delete cache; + delete downloader; +} + + +void MapView::addItem(MapItemBase * item) { + if (items_.contains(item)) return; + items_ << item; + item->parent = this; + item->updatePosition(); + update(); +} + + +void MapView::removeItem(MapItemBase * item) { + if (!items_.contains(item)) return; + items_.removeOne(item); + if (hover == item) hover = nullptr; +} + + +QPointF MapView::center() const { + return OSM::xy2geo(center_); +} + + +void MapView::setCenter(QPointF c) { + center_ = OSM::geo2xy(c); + updateViewRect(); + drawBackground(); +} + + +void MapView::setZoom(double z) { + zoomTo(z); +} + + +void MapView::mousePressEvent(QMouseEvent * e) { + is_pan = false; + press_point = e->pos(); +} + + +void MapView::mouseReleaseEvent(QMouseEvent * e) { + if (!is_pan) { + if (hover) emit itemClicked(hover); + } + is_pan = false; +} + + +void MapView::mouseDoubleClickEvent(QMouseEvent * e) {} + + +void MapView::mouseMoveEvent(QMouseEvent * e) { + // QPointF geo = OSM::xy2geo(mapToNorm(e->pos())); + // qDebug() << QString("%1 , %2").arg(geo.x(), 0, 'f', 5).arg(geo.y(), 0, 'f', 5); + if (e->buttons() == Qt::LeftButton) { + if (is_pan) { + double mins = qMin(width(), height()); + center_ -= QPointF(e->pos() - press_point) / scale_ / mins; + press_point = e->pos(); + updateViewRect(); + drawBackground(); + } else { + if ((e->pos() - press_point).manhattanLength() >= QApplication::startDragDistance()) is_pan = true; + } + } + updateMouse(e->pos()); +} + + +void MapView::wheelEvent(QWheelEvent * e) { + double scl = e->angleDelta().x() == 0 ? e->angleDelta().y() : e->angleDelta().x(); + scl = 1. + (scl / 400.); + QPoint mp; +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + mp = e->pos(); +#else + mp = e->position().toPoint(); +#endif + zoom(scl, mp); +} + + +void MapView::paintEvent(QPaintEvent *) { + QPainter p(this); + p.drawPixmap(0, 0, background); + drawItems(p); + updateMouse(mapFromGlobal(QCursor::pos())); +} + + +void MapView::resizeEvent(QResizeEvent *) { + cache->setMinimumCacheSize((width() + tileSize) * (height() + tileSize) * 4); + checkZoom(); + updateZoomLevel(); + updateViewRect(); + drawBackground(); +} + + +void MapView::drawBackground() { + if (size() != background.size()) background = QPixmap(size()); + background.fill(palette().window().color()); + QPainter p(&background); + double mins = qMin(width(), height()); + int sx = qMax(0, (int)floor(view_rect.left() * tiles_side)); + int sy = qMax(0, (int)floor(view_rect.top() * tiles_side)); + int ex = qMin(tiles_side, (int)ceil(view_rect.right() * tiles_side)); + int ey = qMin(tiles_side, (int)ceil(view_rect.bottom() * tiles_side)); + double ts = 1. / tiles_side / qMax(view_rect.width(), view_rect.height()) * qMax(width(), height()); + QPointF offset = view_rect.topLeft() * mins * scale_; + p.setRenderHint(QPainter::SmoothPixmapTransform); + p.setPen(Qt::white); + for (int ix = sx; ix < ex; ++ix) { + for (int iy = sy; iy < ey; ++iy) { + QRectF r((ix)*ts, (iy)*ts, ts, ts); + r.translate(-offset); + auto tile = cache->getTile((OSM::TileIndex){zoom_level, ix, iy}); + if (!tile.isEmpty()) { + p.drawPixmap(r, tile.pixmap, QRectF(tile.pixmap.rect())); + } else { + p.fillRect(r, brush_tr); + } + // p.setPen(Qt::white); + // p.drawText(r, Qt::AlignCenter, QString("%1x%2").arg(ix).arg(iy)); + } + } + // qDebug() << sx << sy << ex << ey << ts; + update(); +} + + +void MapView::drawItems(QPainter & p) { + auto src_tr = p.transform(); + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::SmoothPixmapTransform); + QPoint mouse = mapFromGlobal(QCursor::pos()); + MapItemBase * hover = nullptr; + for (auto * i: items_) { + if (!i->isVisible()) continue; + QPointF ipos = mapFromNorm(i->norm_pos); + p.setTransform(src_tr); + p.translate(ipos); + p.rotate(i->getRotation()); + if (!i->ignore_scale) p.scale(i->getScale().x(), i->getScale().y()); + i->draw(&p); + QTransform mtr; + mtr.rotate(-i->getRotation()); + mtr.translate(-ipos.x(), -ipos.y()); + QPoint m = mtr.map(mouse); + if (i->m_bounding.contains(m)) { + hover = i; + } + } + if (hover) + setCursor(hover->cursor()); + else + unsetCursor(); +} + + +void MapView::checkZoom() { + static const double max = 20; + if (zoom_ < 0.) zoom_ = 0.; + if (zoom_ > max) zoom_ = max; + double mins = qMin(width(), height()) / appScale(this); + scale_ = std::pow(2., zoom_) * tileSize / mins; + updateViewRect(); +} + + +void MapView::zoomLevelChanged(int new_level) { + zoom_level = qBound(0, new_level, max_level); + // qDebug() << "level changed" << new_level << zoom_level; + tiles_side = 1 << zoom_level; + // for (int x = 0; x < tiles_side; ++x) + // for (int y = 0; y < tiles_side; ++y) + // downloader->queueTile(OSM::TileIndex{zoom_level, x, y}); +} + + +void MapView::updateZoomLevel() { + int zl = qRound(zoom_); + if (zl != zoom_level) zoomLevelChanged(zl); +} + + +void MapView::updateViewRect() { + double mins = qMin(width(), height()); + if (mins <= 0) return; + view_rect.setRect(0, 0, (width() / mins) / scale_, (height() / mins) / scale_); + view_rect.moveCenter(center_); + for (auto * i: items_) + i->zoomChanged(); +} + + +void MapView::updateMouse(QPoint mouse) { + MapItemBase * new_hover = nullptr; + for (auto * i: items_) { + if (!i->isVisible() || !i->isInteracive()) continue; + QPointF ipos = mapFromNorm(i->norm_pos); + QTransform mtr; + if (!i->ignore_scale) mtr.scale(1. / i->getScale().x(), 1. / i->getScale().y()); + mtr.rotate(-i->getRotation()); + mtr.translate(-ipos.x(), -ipos.y()); + QPoint m = mtr.map(mouse); + if (i->m_bounding.contains(m)) { + new_hover = i; + } + } + if (new_hover) + setCursor(new_hover->cursor()); + else + unsetCursor(); + if (new_hover != hover) { + if (hover) emit itemLeaved(hover); + if (new_hover) emit itemEntered(new_hover); + } + hover = new_hover; +} + + +double MapView::scalePx2M(QPointF norm) { + double lat = OSM::y2lat(norm.y()) * M_PI / 180.; + px2m = qMax(view_rect.width(), view_rect.height()) / qMax(width(), height()) * tileSize; + px2m *= 156543.03 * cos(lat); + // qDebug() << px2m << lat; + return px2m; +} + + +QPointF MapView::mapToNorm(QPoint screen) const { + QPointF s(screen.x() / (double)width(), screen.y() / (double)height()); + return view_rect.topLeft() + QPointF(view_rect.width() * s.x(), view_rect.height() * s.y()); +} + + +QPoint MapView::mapFromNorm(QPointF norm) const { + QPointF s = norm - view_rect.topLeft(); + return QPoint(qRound((s.x() / view_rect.width()) * width()), qRound((s.y() / view_rect.height()) * height())); +} + + +void MapView::zoom(double factor) { + zoom(factor, rect().center()); +} + + +void MapView::zoom(double factor, QPoint anchor) { + QPointF prev_center = mapToNorm(anchor); + zoom_ += (factor - 1.); + checkZoom(); + QPointF new_center = mapToNorm(anchor); + center_ += prev_center - new_center; + view_rect.translate(prev_center - new_center); + updateZoomLevel(); + drawBackground(); +} + + +void MapView::zoomTo(double level) { + zoom_ = level; + checkZoom(); + updateZoomLevel(); + drawBackground(); +} + + +void MapView::centerTo(QPointF coord) { + center_ = OSM::geo2xy(coord); + updateViewRect(); + drawBackground(); +} diff --git a/libs/map/mapview.h b/libs/map/mapview.h new file mode 100644 index 0000000..b3e68e8 --- /dev/null +++ b/libs/map/mapview.h @@ -0,0 +1,104 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef mapview_h +#define mapview_h + +#include "mapitembase.h" +#include "qad_map_export.h" + +#include +#include + + +class OSMDownloader; +class OSMTileCache; + +class QAD_MAP_EXPORT MapView: public QWidget { + Q_OBJECT + Q_PROPERTY(QPointF center READ center WRITE setCenter) + Q_PROPERTY(double zoom READ getZoom WRITE setZoom) + + friend class OSMDownloader; + friend class OSMTileCache; + friend class MapItemBase; + +public: + explicit MapView(QWidget * parent = 0); + ~MapView(); + + void addItem(MapItemBase * item); + void removeItem(MapItemBase * item); + + QPointF center() const; + void setCenter(QPointF c); + + double getZoom() const { return zoom_; } + void setZoom(double z); + +private: + QSize sizeHint() const override { return QSize(200, 200); } + void mousePressEvent(QMouseEvent * e) override; + void mouseReleaseEvent(QMouseEvent * e) override; + void mouseDoubleClickEvent(QMouseEvent * e) override; + void mouseMoveEvent(QMouseEvent * e) override; + void wheelEvent(QWheelEvent * e) override; + void paintEvent(QPaintEvent *) override; + void resizeEvent(QResizeEvent *) override; + void drawBackground(); + void drawItems(QPainter & p); + void checkZoom(); + void zoomLevelChanged(int new_level); + void updateZoomLevel(); + void updateViewRect(); + void updateMouse(QPoint mouse); + double scalePx2M(QPointF norm); + + QPointF mapToNorm(QPoint screen) const; + QPoint mapFromNorm(QPointF norm) const; + + OSMDownloader * downloader = nullptr; + OSMTileCache * cache = nullptr; + MapItemBase * hover = nullptr; + QPointF center_ = QPointF(0.5, 0.5); + QPoint press_point; + QPixmap background; + QRectF view_rect; + QVector items_; + QBrush brush_tr; + bool is_pan = false; + double zoom_ = 1., scale_ = 1., px2m = 1.; + int zoom_level = 0, tiles_side = 1, max_level = 19; + +public slots: + void zoom(double factor); + void zoom(double factor, QPoint anchor); + void zoomTo(double level); + void centerTo(QPointF coord); + +private slots: + +signals: + void itemClicked(MapItemBase * item); + void itemEntered(MapItemBase * item); + void itemLeaved(MapItemBase * item); +}; + + +#endif diff --git a/libs/map/osm_downloader.cpp b/libs/map/osm_downloader.cpp new file mode 100644 index 0000000..5229ed6 --- /dev/null +++ b/libs/map/osm_downloader.cpp @@ -0,0 +1,73 @@ +#include "mapview.h" +#include "osm_downloader_p.h" +#include "osm_tile_cache_p.h" + + +OSMDownloader::OSMDownloader(MapView * p): QThread() { + qRegisterMetaType(); + nam = new QNetworkAccessManager(); + parent = p; + provider = "http://tile.openstreetmap.org"; + start(); +} + + +OSMDownloader::~OSMDownloader() { + requestInterruption(); + cond.wakeAll(); + wait(); + delete nam; +} + + +void OSMDownloader::queueTile(OSM::TileIndex index) { + // auto hash = tile.hash(); + cond_mutex.lock(); + if (!queue.contains(index) && !in_progress.contains(index.hash())) { + queue.enqueue(index); + cond.wakeOne(); + } + cond_mutex.unlock(); +} + + +void OSMDownloader::requestTile(OSM::TileIndex index) { + QNetworkRequest req(provider + QString("/%1/%2/%3.png").arg(index.z).arg(index.x).arg(index.y)); + req.setHeader(QNetworkRequest::UserAgentHeader, "Qt/5"); + auto * r = nam->get(req); + if (!r) return; + connect(r, &QNetworkReply::finished, this, [this, r, index]() { + r->deleteLater(); + if (r->error() != QNetworkReply::NoError) { + qDebug() << "Error:" << r->error(); + } else { + QByteArray data = r->readAll(); + if (!data.isEmpty()) { + QPixmap tim; + tim.loadFromData(data, "png"); + if (!tim.isNull()) parent->cache->tileDownloaded(index, tim); + } + } + cond_mutex.lock(); + in_progress.remove(index.hash()); + cond_mutex.unlock(); + }); +} + + +void OSMDownloader::run() { + while (!isInterruptionRequested()) { + cond_mutex.lock(); + if (queue.isEmpty()) + cond.wait(&cond_mutex); + else { + auto t = queue.dequeue(); + in_progress.insert(t.hash()); + QMetaObject::invokeMethod( + parent, + [this, t]() { requestTile(t); }, + Qt::QueuedConnection); + } + cond_mutex.unlock(); + } +} diff --git a/libs/map/osm_downloader_p.h b/libs/map/osm_downloader_p.h new file mode 100644 index 0000000..18fd9cd --- /dev/null +++ b/libs/map/osm_downloader_p.h @@ -0,0 +1,66 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef osm_downloader_h +#define osm_downloader_h + +#include "osm_types_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class MapView; + +class OSMDownloader: public QThread { + Q_OBJECT + +public: + explicit OSMDownloader(MapView * p); + ~OSMDownloader(); + + void queueTile(OSM::TileIndex index); + + +private: + void run() override; + void requestTile(OSM::TileIndex index); + + MapView * parent; + QNetworkAccessManager * nam = nullptr; + QString provider; + QWaitCondition cond; + QMutex cond_mutex; + QQueue queue; + QSet in_progress; + +public slots: + +private slots: + +signals: +}; + + +#endif diff --git a/libs/map/osm_math_p.h b/libs/map/osm_math_p.h new file mode 100644 index 0000000..df52275 --- /dev/null +++ b/libs/map/osm_math_p.h @@ -0,0 +1,66 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef osm_math_p_h +#define osm_math_p_h + +#include +#include + +// lon -> -180..+180 +// lat -> -85.0511..+85.0511 + +namespace OSM { + + +inline double lng2x(double lon) { + return (lon + 180.0) / 360.0; +} + + +inline double lat2y(double lat) { + double latrad = lat * M_PI / 180.0; + return (1.0 - asinh(tan(latrad)) / M_PI) / 2.0; +} + + +inline double x2lng(double x) { + return x * 360.0 - 180; +} + + +inline double y2lat(double y) { + double n = M_PI - 2.0 * M_PI * y; + return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n))); +} + + +inline QPointF xy2geo(QPointF p) { + return QPointF(y2lat(p.y()), x2lng(p.x())); +} + + +inline QPointF geo2xy(QPointF p) { + return QPointF(lng2x(p.y()), lat2y(p.x())); +} + + +} // namespace OSM + +#endif diff --git a/libs/map/osm_tile_cache.cpp b/libs/map/osm_tile_cache.cpp new file mode 100644 index 0000000..0f63253 --- /dev/null +++ b/libs/map/osm_tile_cache.cpp @@ -0,0 +1,100 @@ +#include "mapview.h" +#include "osm_downloader_p.h" +#include "osm_tile_cache_p.h" +#include "qad_locations.h" + + +OSMTileCache::OSMTileCache(MapView * p): QThread() { + qRegisterMetaType(); + qRegisterMetaType(); + parent = p; + cache_root = QAD::userPath(QAD::ltCache, "map_osm") + "/"; + cache_dir.setPath(cache_root); + qDebug() << "[OSMTileCache] save cache to" << cache_root; + if (!cache_dir.exists()) cache_dir.mkpath("."); + setAdditionalCacheSize(64); + start(); +} + + +OSMTileCache::~OSMTileCache() { + requestInterruption(); + cond.wakeAll(); + wait(); +} + + +void OSMTileCache::tileDownloaded(OSM::TileIndex index, const QPixmap & pixmap) { + auto * tile = new OSM::TilePixmap(); + tile->index = index; + tile->pixmap = pixmap; + saveTile(tile); +} + + +OSM::TilePixmap OSMTileCache::getTile(OSM::TileIndex index) { + OSM::TilePixmap ret; + ret.index = index; + auto * tile = tile_cache[index.hash()]; + if (tile) { + ret.pixmap = tile->pixmap; + } else { + QString hashdir = index.cacheDir(); + if (cache_dir.exists(hashdir)) { + QString hashname = hashdir + "/" + index.hashName(); + if (cache_dir.exists(hashname)) { + ret.pixmap.load(cache_dir.absoluteFilePath(hashname), "png"); + tile = new OSM::TilePixmap(); + tile->index = index; + tile->pixmap = ret.pixmap; + tile_cache.insert(tile->index.hash(), tile, tile->pixmapBytes()); + return ret; + } + } + parent->downloader->queueTile(index); + } + return ret; +} + + +void OSMTileCache::updateCacheSize() { + tile_cache.setMaxCost(fixed_size_b + add_size_b); +} + + +void OSMTileCache::saveTile(OSM::TilePixmap * tile) { + tile_cache.insert(tile->index.hash(), tile, tile->pixmapBytes()); + emit tileReady(*tile); + cond_mutex.lock(); + if (!queue.contains(*tile)) { + queue.enqueue(*tile); + cond.wakeOne(); + } + cond_mutex.unlock(); +} + + +void OSMTileCache::writeToDisk(const OSM::TilePixmap & tile) { + QString hashdir = tile.index.cacheDir(); + if (!cache_dir.exists(hashdir)) cache_dir.mkdir(hashdir); + QFile f(cache_dir.absoluteFilePath(hashdir + "/" + tile.index.hashName())); + if (!f.open(QIODevice::ReadWrite)) return; + f.resize(0); + tile.pixmap.save(&f, "png"); +} + + +void OSMTileCache::run() { + while (!isInterruptionRequested()) { + OSM::TilePixmap tile; + cond_mutex.lock(); + if (queue.isEmpty()) { + cond.wait(&cond_mutex); + cond_mutex.unlock(); + } else { + tile = queue.dequeue(); + cond_mutex.unlock(); + writeToDisk(tile); + } + } +} diff --git a/libs/map/osm_tile_cache_p.h b/libs/map/osm_tile_cache_p.h new file mode 100644 index 0000000..b3bcfe1 --- /dev/null +++ b/libs/map/osm_tile_cache_p.h @@ -0,0 +1,77 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef osm_tile_cache_h +#define osm_tile_cache_h + +#include "osm_types_p.h" + +#include +#include +#include +#include +#include +#include + +class MapView; + +class OSMTileCache: public QThread { + Q_OBJECT + +public: + explicit OSMTileCache(MapView * p); + ~OSMTileCache(); + + void setMinimumCacheSize(int bytes) { + fixed_size_b = bytes; + updateCacheSize(); + } + void setAdditionalCacheSize(int mb) { + add_size_b = mb * 1024 * 1024; + updateCacheSize(); + } + + void tileDownloaded(OSM::TileIndex index, const QPixmap & pixmap); + OSM::TilePixmap getTile(OSM::TileIndex index); + +private: + void updateCacheSize(); + void saveTile(OSM::TilePixmap * tile); + void writeToDisk(const OSM::TilePixmap & tile); + void run() override; + + MapView * parent; + QString cache_root; + QDir cache_dir; + QWaitCondition cond; + QMutex cond_mutex; + int fixed_size_b = 0, add_size_b = 0; + QQueue queue; + QCache tile_cache; + +public slots: + +private slots: + +signals: + void tileReady(OSM::TilePixmap); +}; + + +#endif diff --git a/libs/map/osm_types.cpp b/libs/map/osm_types.cpp new file mode 100644 index 0000000..e9ea923 --- /dev/null +++ b/libs/map/osm_types.cpp @@ -0,0 +1,62 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include "osm_types_p.h" + +#include + +using namespace OSM; + + +QString TileIndex ::hashName() const { + return QString("%1.%2.%3").arg(z).arg(x).arg(y); +} + + +quint64 TileIndex::hash() const { + union _Hash { + quint64 ret; + struct { + quint64 z: 8; + quint64 x: 24; + quint64 y: 24; + }; + }; + _Hash h; + h.ret = 0; + h.z = z; + h.x = x; + h.y = y; + return h.ret; +} + + +QString TileIndex::cacheDir() const { + return QString::number(qHash(hash()) % 256); +}; + + +bool TilePixmap::isEmpty() const { + return pixmap.isNull(); +} + + +int TilePixmap::pixmapBytes() const { + return pixmap.width() * pixmap.height() * pixmap.depth() / 8; +} diff --git a/libs/map/osm_types_p.h b/libs/map/osm_types_p.h new file mode 100644 index 0000000..4203b26 --- /dev/null +++ b/libs/map/osm_types_p.h @@ -0,0 +1,56 @@ +/* + QAD - Qt ADvanced + + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef osm_types_h +#define osm_types_h + +#include + +namespace OSM { + +struct TileIndex { + int z; + int x; + int y; + QString hashName() const; + quint64 hash() const; + QString cacheDir() const; +}; + +struct TilePixmap { + TileIndex index; + QPixmap pixmap; + bool isEmpty() const; + int pixmapBytes() const; +}; + +} // namespace OSM + +inline bool operator==(const OSM::TileIndex & v0, const OSM::TileIndex & v1) { + return (v0.z == v1.z) && (v0.x == v1.x) && (v0.y == v1.y); +} + +inline bool operator==(const OSM::TilePixmap & v0, const OSM::TilePixmap & v1) { + return v0.index == v1.index; +} + +Q_DECLARE_METATYPE(OSM::TileIndex); +Q_DECLARE_METATYPE(OSM::TilePixmap); + +#endif diff --git a/libs/map/plugin/CMakeLists.txt b/libs/map/plugin/CMakeLists.txt new file mode 100644 index 0000000..ff0f90e --- /dev/null +++ b/libs/map/plugin/CMakeLists.txt @@ -0,0 +1 @@ +qad_plugin(map "Gui;Widgets" "") diff --git a/libs/map/plugin/mapplugin.cpp b/libs/map/plugin/mapplugin.cpp new file mode 100644 index 0000000..6da93f5 --- /dev/null +++ b/libs/map/plugin/mapplugin.cpp @@ -0,0 +1,70 @@ +#include "mapplugin.h" + +#include "mapview.h" + +#include + + +MapPlugin::MapPlugin(QObject * parent): QObject(parent) { + m_initialized = false; +} + + +void MapPlugin::initialize(QDesignerFormEditorInterface * /* core */) { + if (m_initialized) return; + + // Add extension registrations, etc. here + + m_initialized = true; +} + + +bool MapPlugin::isInitialized() const { + return m_initialized; +} + + +QWidget * MapPlugin::createWidget(QWidget * parent) { + MapView * w = new MapView(parent); + return w; +} + + +QString MapPlugin::name() const { + return QLatin1String("MapView"); +} + + +QString MapPlugin::group() const { + return QLatin1String("Display Widgets"); +} + + +QIcon MapPlugin::icon() const { + return QIcon("://icons/maps.png"); +} + + +QString MapPlugin::toolTip() const { + return QLatin1String(""); +} + + +QString MapPlugin::whatsThis() const { + return QLatin1String(""); +} + + +bool MapPlugin::isContainer() const { + return false; +} + + +QString MapPlugin::domXml() const { + return QLatin1String("\n\n"); +} + + +QString MapPlugin::includeFile() const { + return QLatin1String("mapview.h"); +} diff --git a/libs/map/plugin/mapplugin.h b/libs/map/plugin/mapplugin.h new file mode 100644 index 0000000..ecfddb6 --- /dev/null +++ b/libs/map/plugin/mapplugin.h @@ -0,0 +1,33 @@ +#ifndef MAPPLUGIN_H +#define MAPPLUGIN_H + +#include +#include + + +class MapPlugin + : public QObject + , public QDesignerCustomWidgetInterface { + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) + +public: + explicit MapPlugin(QObject * parent = 0); + + bool isContainer() const; + bool isInitialized() const; + QIcon icon() const; + QString domXml() const; + QString group() const; + QString includeFile() const; + QString name() const; + QString toolTip() const; + QString whatsThis() const; + QWidget * createWidget(QWidget * parent); + void initialize(QDesignerFormEditorInterface * core); + +private: + bool m_initialized; +}; + +#endif diff --git a/libs/map/plugin/mapview_designerplugin.cpp b/libs/map/plugin/mapview_designerplugin.cpp new file mode 100644 index 0000000..0523614 --- /dev/null +++ b/libs/map/plugin/mapview_designerplugin.cpp @@ -0,0 +1,13 @@ +#include "mapview_designerplugin.h" + +#include "mapplugin.h" + + +MapDesignerPlugin::MapDesignerPlugin(QObject * parent): QObject(parent) { + m_widgets.append(new MapPlugin(this)); +} + + +QList MapDesignerPlugin::customWidgets() const { + return m_widgets; +} diff --git a/libs/map/plugin/mapview_designerplugin.h b/libs/map/plugin/mapview_designerplugin.h new file mode 100644 index 0000000..b7ab702 --- /dev/null +++ b/libs/map/plugin/mapview_designerplugin.h @@ -0,0 +1,23 @@ +#ifndef MAP_DESIGNERPLUGIN_H +#define MAP_DESIGNERPLUGIN_H + +#include +#include + + +class MapDesignerPlugin + : public QObject + , public QDesignerCustomWidgetCollectionInterface { + Q_OBJECT + Q_PLUGIN_METADATA(IID "qad.map") + Q_INTERFACES(QDesignerCustomWidgetCollectionInterface) + +public: + MapDesignerPlugin(QObject * parent = 0); + virtual QList customWidgets() const; + +private: + QList m_widgets; +}; + +#endif diff --git a/libs/map/qad_map.qrc b/libs/map/qad_map.qrc new file mode 100644 index 0000000..6546225 --- /dev/null +++ b/libs/map/qad_map.qrc @@ -0,0 +1,5 @@ + + + ../../icons/maps.png + + diff --git a/utils/mapviewer/CMakeLists.txt b/utils/mapviewer/CMakeLists.txt new file mode 100644 index 0000000..6d5a248 --- /dev/null +++ b/utils/mapviewer/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(PIP) +if (PIP_FOUND) + + project(mapviewer) + if(APPLE) + set(APP_ICON "") + elseif(WIN32) + set(APP_ICON "icons/maps.ico") + else() + set(APP_ICON "icons/maps.png") + endif() + set(APP_INFO "Map viewer") + qad_application(${PROJECT_NAME} "Gui;Widgets" "qad_map") + +endif() diff --git a/utils/mapviewer/icons/maps.ico b/utils/mapviewer/icons/maps.ico new file mode 100644 index 0000000000000000000000000000000000000000..6853c7eae50faa52dde509d5501c3b0b5828a823 GIT binary patch literal 19531 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 literal 0 HcmV?d00001 diff --git a/utils/mapviewer/icons/maps.png b/utils/mapviewer/icons/maps.png new file mode 100644 index 0000000000000000000000000000000000000000..ae68d5483413487ef2d91a9e4098c2d797c33b9e GIT binary patch literal 20841 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 Xt{Q>V*RlD800000NkvXXu0mjftL8%x literal 0 HcmV?d00001 diff --git a/utils/mapviewer/main.cpp b/utils/mapviewer/main.cpp new file mode 100644 index 0000000..054a865 --- /dev/null +++ b/utils/mapviewer/main.cpp @@ -0,0 +1,72 @@ +#include "mapitemellipse.h" +#include "mapitemgeopolygon.h" +#include "mapitemgeopolyline.h" +#include "mapitemimage.h" +#include "mapitempolygon.h" +#include "mapitemtext.h" +#include "mapview.h" +#include "qad_types.h" + +#include +#include +#include +#include +#include + +int main(int argc, char * argv[]) { + QApplication a(argc, argv); + enableHighDPI(); + MapView w; + w.resize(800, 600); + w.show(); + /*QVector g = {QPointF(55.583055, 37.580008), QPointF(55.583055, 37.590008), QPointF(55.593055, 37.580008)}; + QVector p = {QPointF(0, 0), QPointF(200, 0), QPointF(0, 100)}; + MapItemPolygon * pol = new MapItemPolygon(QPolygonF(p)); + MapItemGeoPolyline * gpol = new MapItemGeoPolyline(QPolygonF(g)); + MapItemEllipse * ell = new MapItemEllipse(QPointF(), 50, 50); + MapItemImage * im = new MapItemImage(QPixmap(":/icons/maps.png")); + MapItemText * it = new MapItemText(QString::fromUtf8("Это Ваня!")); + im->setPosition({55.583055, 37.580008}); + im->setScale(0.2, 0.5); + im->setAlignment(Qt::AlignRight | Qt::AlignTop); + im->setCursor(Qt::PointingHandCursor); + im->setInteracive(true); + it->setPosition({55.583055, 37.580008}); + it->setBrush(QColor(127, 0, 0, 127)); + it->setPen(QColor(64, 255, 64)); + it->setFont(QFont("times", 18)); + it->setCursor(Qt::OpenHandCursor); + it->setInteracive(true); + pol->setPosition({55.583055, 37.580008}); + pol->setUnits(MapItemNonGeoGeometryBase::Pixels); + gpol->setBrush(QColor(0, 0, 255, 64)); + gpol->setPen(QPen(QColor(64, 64, 255), 3)); + ell->setPosition({55.583055, 37.580008}); + ell->setStartAngle(-20); + ell->setEndAngle(20); + ell->setEllipse(QPointF(100, 0), 50, 50); + ell->setInteracive(true); + QTimer t; + QObject::connect(&w, &MapView::itemClicked, [](MapItemBase * item) { qDebug() << "click" << item; }); + QObject::connect(&w, &MapView::itemEntered, [](MapItemBase * item) { qDebug() << "enter" << item; }); + QObject::connect(&w, &MapView::itemLeaved, [](MapItemBase * item) { qDebug() << "leave" << item; }); + + QObject::connect(&t, &QTimer::timeout, [im, it, pol, ell]() { + im->rotate(1); + it->rotate(-0.1); + // pol->rotate(0.2); + ell->rotate(-0.2); + static double t = 0.; + t += 0.025; + ell->setScale((sin(t) / 2. + 1.)); + }); + t.start(100); + w.addItem(im); + w.addItem(gpol); + w.addItem(pol); + w.addItem(it); + w.addItem(ell);*/ + w.centerTo({55.583055, 37.580008}); + w.zoomTo(17); + return a.exec(); +} diff --git a/utils/mapviewer/mapviewer.qrc b/utils/mapviewer/mapviewer.qrc new file mode 100644 index 0000000..f49b0df --- /dev/null +++ b/utils/mapviewer/mapviewer.qrc @@ -0,0 +1,5 @@ + + + icons/maps.png + +