├── .gitignore ├── CMakeLists.txt ├── GraphicsMapLib_global.h ├── README.md ├── Resources.qrc ├── Resources └── location.png ├── graphicsmap.cpp ├── graphicsmap.h ├── interactivemap.cpp ├── interactivemap.h ├── mapellipseitem.cpp ├── mapellipseitem.h ├── maplabelitem.cpp ├── maplabelitem.h ├── maplineitem.cpp ├── maplineitem.h ├── mapobjectitem.cpp ├── mapobjectitem.h ├── mapoperator.cpp ├── mapoperator.h ├── mappieitem.cpp ├── mappieitem.h ├── mappolygonitem.cpp ├── mappolygonitem.h ├── maprangeringitem.cpp ├── maprangeringitem.h ├── maprectitem.cpp ├── maprectitem.h ├── maprouteitem.cpp ├── maprouteitem.h ├── mapscutcheonitem.cpp ├── mapscutcheonitem.h ├── maptableitem.cpp ├── maptableitem.h ├── maptrailitem.cpp └── maptrailitem.h /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(GraphicsMapLib LANGUAGES CXX) 4 | set(CMAKE_DEBUG_POSTFIX d) 5 | 6 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 7 | set(CMAKE_AUTOUIC ON) 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | set(CMAKE_CXX_STANDARD 14) 11 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 12 | 13 | 14 | add_library(${PROJECT_NAME} SHARED 15 | Resources.qrc 16 | graphicsmap.cpp 17 | graphicsmap.h 18 | interactivemap.cpp 19 | interactivemap.h 20 | mapellipseitem.cpp 21 | mapellipseitem.h 22 | maplabelitem.h 23 | maplabelitem.cpp 24 | maplineitem.cpp 25 | maplineitem.h 26 | mapobjectitem.cpp 27 | mapobjectitem.h 28 | mapoperator.cpp 29 | mapoperator.h 30 | mappieitem.cpp 31 | mappieitem.h 32 | mappolygonitem.cpp 33 | mappolygonitem.h 34 | maprangeringitem.cpp 35 | maprangeringitem.h 36 | maprouteitem.cpp 37 | maprouteitem.h 38 | maptrailitem.cpp 39 | maptrailitem.h 40 | maprectitem.cpp 41 | maprectitem.h 42 | maptableitem.h 43 | maptableitem.cpp 44 | mapscutcheonitem.h 45 | mapscutcheonitem.cpp 46 | ) 47 | add_library(Lib::GraphicsMap ALIAS ${PROJECT_NAME}) 48 | 49 | # 50 | find_package(Qt5 COMPONENTS Core Widgets Positioning REQUIRED) 51 | 52 | # 53 | target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}) 54 | target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core Qt5::Widgets Qt5::Positioning) 55 | 56 | # 57 | target_compile_definitions(${PROJECT_NAME} PRIVATE GRAPHICSMAPLIB_LIBRARY) 58 | 59 | # 60 | install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION install) 61 | -------------------------------------------------------------------------------- /GraphicsMapLib_global.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHICSMAPLIB_GLOBAL_H 2 | #define GRAPHICSMAPLIB_GLOBAL_H 3 | 4 | #include 5 | 6 | #if defined(GRAPHICSMAPLIB_LIBRARY) 7 | # define GRAPHICSMAPLIB_EXPORT Q_DECL_EXPORT 8 | #else 9 | # define GRAPHICSMAPLIB_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | #endif // GRAPHICSMAPLIB_GLOBAL_H 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My Environment 2 | 3 | - 操作系统:Windows 10 4 | - Qt版本:5.12.8 5 | 6 | ## 1. Build & Install 7 | 8 | 这是一个基于CMake构建的工程,你可以在你自己的CMake工程里面添加它,或者是单独编译之后再将输出文件添加到自己的工程里面。 9 | 10 | 如果使用QtCreator,那么是非常方便的,你可以直接在QtCreator打开该工程的CMakeLists.txt文件,然后执行构建命令(Ctrl+B)编译工程。 11 | 12 | 编译成功之后,你可以在*项目*页面里面执行下面两部操作,完成执行文件的安装部署: 13 | 14 | 1. 在*CMake*页里面修改`CMAKE_INSTALL_PREFIX`为自己希望的安装路径,并点击`Apply Configuration Changes`; 15 | 2. 在*Build的步骤*页里面选择`targets`为`install`; 16 | 3. 执行构建命令(Ctrl+B)。 17 | 18 | 此时,在安装路径下将会出现一个install文件夹,install文件夹内也包含一个GraphicsMapLibd.dll文件。 19 | 20 | 如果你使用CMake工程来包含这个库,那上面的办法确实很方便,可以在每次编译完之后部署最新的dll,同时调试源码也非常方便。不过如果你使用非CMake工程来使用这个库,那么请手动拷贝.h文件、lib文件、dll文件。 21 | 22 | ## 2. Quick Start 23 | 24 | 1. 显示一个地图: 25 | ``` 26 | auto map = new GraphicsMap; 27 | map->setTilePath("E:/map/sate"); 28 | map->show(); 29 | ``` 30 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_sate.png) 31 | 32 | 2. 隐藏滚动条: 33 | 34 | ``` 35 | map->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); 36 | map->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); 37 | ``` 38 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_hide_scroll.png) 39 | 40 | 3. 设置缩放等级: 41 | 42 | ``` 43 | map->setZoomLevel(5); 44 | ``` 45 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_zoom.png) 46 | 47 | 4. 切换瓦片资源类型: 48 | 49 | ``` 50 | map->setTilePath("E:/map/road"); 51 | ``` 52 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_road.png) 53 | 54 | 5. 设置鼠标中心缩放和鼠标拖动地图: 55 | 56 | ``` 57 | map->setDragMode(QGraphicsView::ScrollHandDrag); 58 | map->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); 59 | ``` 60 | 61 | 6. 创建鼠标可交互地图: 62 | 63 | ``` 64 | auto map = new InteractiveMap; 65 | map->setTilePath("E:/map/sate"); 66 | map->show(); 67 | ``` 68 | 69 | 7. 为可交互地图绑定地图操作器 70 | 71 | - 圆形操作器:`map->setOperator(new MapPolygonOperator);` 72 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_ellipse.png) 73 | - 多边形操作器:`map->setOperator(new MapPolygonOperator);` 74 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_polygon.png) 75 | - 图标操作器:`map->setOperator(new MapObjectOperator);` 76 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_obj.png) 77 | - 路线操作器: `map->setOperator(new MapRouteOperator);` 78 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_route.png) 79 | - 测距操作器:`map->setOperator(new MapRangeLineOperator);` 80 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_range.png) 81 | 82 | 8. 手动添加地图元素: `map->scene()->addItem()` 83 | 84 | ``` 85 | auto range = new MapRangeRingItem; // 距离环 86 | range->setCoordinate({0,30}); 87 | range->setRadius(90); 88 | map->scene()->addItem(range); 89 | auto ellipse = new MapEllipseItem; // 圆 90 | ellipse->setCenter({1.0, 30}); 91 | ellipse->setSize({20e3, 20e3}); 92 | ellipse->setBrush(Qt::green); 93 | map->scene()->addItem(ellipse); 94 | ``` 95 | 96 | ![](https://raw.githubusercontent.com/Mud-Player/MudPic/main/02GraphicsMapLib/quick_custom.png) 97 | 98 | ## 3. Class List 99 | 100 | ### 3.1 Map 101 | 102 | 1. GraphicsMap:封装基础的地图加载算法 103 | 2. InteractiveMap:封装地图交互的框架 104 | 105 | ### 3.2 Map Items 106 | 107 | 1. MapEllipseItem:圆形图形/椭圆图形 108 | 2. MapLabelItem:文本标签,由标题和内容组成 109 | 3. MapLineItem:线段 110 | 4. MapObjectItem:图标对象 111 | 5. MapPieItem:扇形,由三角形和梯形组成 112 | 6. MapPolygonItem:多边形 113 | 7. MapRangeRingItem:距离环 114 | 8. MapRouteItem:航路 115 | 9. MapTrailItem:轨迹线 116 | 117 | ### 3.3 Map Operators 118 | 119 | 1. InteractiveMapOperator:地图操作器基类,定义处理交互事件的接口 120 | 2. MapEllipseOperator:圆形操作器 121 | 3. MapPolygonOperator:多边形操作器 122 | 4. MapObjectOperator:图标对象操作器 123 | 5. MapRouteOperator:航路操作器 124 | 6. MapRangeLineOperator:测距操作器 125 | 126 | ## 4. Bugs 127 | 128 | 1. OpenGL窗口下,MapObjectItem::setIconColor的第二个对象开始渲染会变成黑色方块 129 | 130 | -------------------------------------------------------------------------------- /Resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Resources/location.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /Resources/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mud-Player/GraphicsMapLib/576130334f597235227951f637014b3c9186c2cb/Resources/location.png -------------------------------------------------------------------------------- /graphicsmap.cpp: -------------------------------------------------------------------------------- 1 | #include "graphicsmap.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define ZOOM_BASE 10 ///< ZOOM_BASE级瓦片正好缩放为原比例(1:1),低于ZOOM_BASE级的放大,反之缩小 14 | #define TILE_LEN 256 ///< 瓦片长度,标准的都是256 * 256 15 | #define SCENE_LEN ((1<setScene(new QGraphicsScene); 29 | qRegisterMetaType("GraphicsMap::TileSpec"); 30 | qRegisterMetaType("GraphicsMap::TileRegion"); 31 | viewport()->setObjectName("GraphicsMap"); 32 | 33 | init(); 34 | // 35 | this->scene()->setSceneRect(-SCENE_LEN/2, -SCENE_LEN/2, SCENE_LEN, SCENE_LEN); 36 | setZoomLevel(2); 37 | } 38 | 39 | GraphicsMap::~GraphicsMap() 40 | { 41 | // 在此处从场景移出瓦片,防止和多线程析构冲突 42 | for(auto item : m_tiles) { 43 | this->scene()->removeItem(item); 44 | } 45 | delete scene(); 46 | delete m_mapThread; 47 | } 48 | 49 | void GraphicsMap::setFrameRate(int fps) 50 | { 51 | if(fps <= 0) { 52 | this->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate); 53 | disconnect(&m_updateTimer, &QTimer::timeout, viewport(), qOverload<>(&QGraphicsView::update)); 54 | m_updateTimer.stop(); 55 | } 56 | else { 57 | this->setViewportUpdateMode(QGraphicsView::NoViewportUpdate); 58 | connect(&m_updateTimer, &QTimer::timeout, viewport(), qOverload<>(&QGraphicsView::update), Qt::ConnectionType(Qt::DirectConnection|Qt::UniqueConnection)); 59 | m_updateTimer.start(1000/fps); 60 | } 61 | } 62 | 63 | void GraphicsMap::setTilePath(const QString &path) 64 | { 65 | m_type = mapType(path); 66 | emit pathRequested(path); 67 | updateTile(); 68 | } 69 | 70 | void GraphicsMap::setZoomLevel(float zoom) 71 | { 72 | auto boundZoom = qBound(m_minZoom, zoom, m_maxZoom); 73 | if(m_zoom == boundZoom) 74 | return; 75 | 76 | m_zoom = boundZoom; 77 | auto zoomLevelDiff = m_zoom - ZOOM_BASE; 78 | auto scaleValue = qPow(2, zoomLevelDiff); 79 | this->setTransform(QTransform::fromScale(scaleValue, scaleValue).rotate(-m_rotation)); 80 | // 81 | if(m_isloading) 82 | m_hasPendingLoad = true; 83 | else 84 | updateTile(); 85 | emit zoomChanged(m_zoom); 86 | } 87 | 88 | const float &GraphicsMap::zoomLevel() const 89 | { 90 | return m_zoom; 91 | } 92 | 93 | void GraphicsMap::setZoomRange(int min, int max) 94 | { 95 | if(min > max) 96 | return; 97 | m_preferMinZoom = min; 98 | m_maxZoom = qBound(min, max, 20); 99 | } 100 | 101 | void GraphicsMap::setRotation(const qreal °ree) 102 | { 103 | if(m_rotation == degree) 104 | return; 105 | // NOTE: as for rotate function, QGraphicsView process it for QGraphicsScene, here we need to revert it. 106 | // if set to 10 degree, actually we need to make sure the north 10 degreen is y axis forword from screen 107 | this->rotate(m_rotation - degree); 108 | m_rotation = degree; 109 | updateTile(); 110 | } 111 | 112 | void GraphicsMap::setTileCacheCount(const int &count) 113 | { 114 | m_mapThread->setTileCacheCount(count); 115 | } 116 | 117 | void GraphicsMap::setTMSMode(const bool &on) 118 | { 119 | m_mapThread->setTMSMode(on); 120 | } 121 | 122 | void GraphicsMap::centerOn(const QGeoCoordinate &coord) 123 | { 124 | auto pos = toScene(coord); 125 | QGraphicsView::centerOn(pos); 126 | } 127 | 128 | QGeoCoordinate GraphicsMap::toCoordinate(const QPoint &point) const 129 | { 130 | auto scenePos = this->mapToScene(point); 131 | return toCoordinate(scenePos); 132 | } 133 | 134 | QPoint GraphicsMap::toPoint(const QGeoCoordinate &coord) const 135 | { 136 | auto scenePos = toScene(coord); 137 | return this->mapFromScene(scenePos); 138 | } 139 | 140 | /// \see https://blog.csdn.net/iispring/article/details/8565177 141 | /// R = SCENE_LEN / 2PI 142 | QGeoCoordinate GraphicsMap::toCoordinate(const QPointF &point) 143 | { 144 | auto radLon = point.x() * 2 * M_PI/ SCENE_LEN; 145 | auto radLat = 2 * qAtan(qPow(M_E, 2*M_PI*point.y()/SCENE_LEN)) - M_PI_2; 146 | // NOTO: as for Qt, it's y asscending from up to bottom 147 | return {qRadiansToDegrees(-radLat), qRadiansToDegrees(radLon), 0}; 148 | } 149 | 150 | /// \see https://blog.csdn.net/iispring/article/details/8565177 151 | /// R = SCENE_LEN / 2PI 152 | QPointF GraphicsMap::toScene(const QGeoCoordinate &coord) 153 | { 154 | /// NOTE: R = SCENE_LEN / 2PI 155 | double radLon = qDegreesToRadians(coord.longitude()); 156 | double radLat = qDegreesToRadians(coord.latitude()); 157 | double x = SCENE_LEN * radLon / 2.0 / M_PI; 158 | double y = SCENE_LEN / 2.0 / M_PI * qLn( qTan(M_PI_4+radLat/2.0) ); 159 | // NOTO: as for Qt, it's y asscending from up to bottom 160 | return {x, -y}; 161 | } 162 | 163 | /// 从1编号 164 | quint8 GraphicsMap::mapType(const QString &path) 165 | { 166 | if(!m_mapTypes.contains(path)) 167 | m_mapTypes.append(path); 168 | return m_mapTypes.indexOf(path)+1; 169 | } 170 | 171 | void GraphicsMap::resizeEvent(QResizeEvent *event) 172 | { 173 | double len = qMax(event->size().width(), event->size().height()); 174 | auto zoom = log(len/SCENE_LEN) / log(2); 175 | float minZoom = zoom + ZOOM_BASE; 176 | // we setted a zoom level range alreayd 177 | if(m_preferMinZoom >= 0) { 178 | if(minZoom < m_preferMinZoom) 179 | m_minZoom = m_preferMinZoom; 180 | else 181 | m_minZoom = minZoom; 182 | } 183 | // just set a most suitable minimum level 184 | else { 185 | m_minZoom = minZoom; 186 | } 187 | setZoomLevel(m_zoom); 188 | // 189 | QGraphicsView::resizeEvent(event); 190 | } 191 | 192 | void GraphicsMap::init() 193 | { 194 | m_mapThread = new GraphicsMapThread; 195 | // connect those necessary slot for map tile loading 196 | connect(this, &GraphicsMap::tileRequested, m_mapThread, &GraphicsMapThread::requestTile, Qt::QueuedConnection); 197 | connect(this, &GraphicsMap::pathRequested, m_mapThread, &GraphicsMapThread::requestPath, Qt::QueuedConnection); 198 | // 199 | connect(m_mapThread, &GraphicsMapThread::tileToAdd, this, [&](QGraphicsItem* item){ 200 | this->scene()->addItem(item); 201 | m_tiles.insert(item); 202 | }, Qt::QueuedConnection); 203 | connect(m_mapThread, &GraphicsMapThread::tileToRemove, this, [&](QGraphicsItem* item){ 204 | this->scene()->removeItem(item); 205 | m_tiles.remove(item); 206 | }, Qt::QueuedConnection); 207 | connect(m_mapThread, &GraphicsMapThread::requestFinished, this, [&](){ 208 | m_isloading = false; 209 | if(m_hasPendingLoad) { 210 | updateTile(); 211 | m_hasPendingLoad = false; 212 | } 213 | }, Qt::QueuedConnection); 214 | // TODO: We have to use Qt::QueuedConnection, if not, we will see the map twinkle when scale 215 | connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, [&](){ 216 | if(m_isloading) 217 | m_hasPendingLoad = true; 218 | else 219 | updateTile(); 220 | }, Qt::QueuedConnection); 221 | connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, [&](){ 222 | if(m_isloading) 223 | m_hasPendingLoad = true; 224 | else 225 | updateTile(); 226 | }, Qt::QueuedConnection); 227 | } 228 | 229 | void GraphicsMap::updateTile() 230 | { 231 | quint8 intZoom = qFloor(m_zoom+0.5); 232 | // 233 | qint32 tileCount = qPow(2, intZoom); 234 | auto topLeftPos = mapToScene(viewport()->geometry().topLeft()-QPoint(256,256)); 235 | auto topRightPos = mapToScene(viewport()->geometry().topRight()); 236 | auto bottomLeftPos = mapToScene(viewport()->geometry().bottomLeft()); 237 | qint32 xOrigin = (topLeftPos.x()+SCENE_LEN/2) / SCENE_LEN * tileCount; 238 | qint32 yOrigin = (topLeftPos.y()+SCENE_LEN/2) / SCENE_LEN * tileCount; 239 | qint32 xHor = (topRightPos.x()+SCENE_LEN/2) / SCENE_LEN * tileCount; 240 | qint32 yHor = (topRightPos.y()+SCENE_LEN/2) / SCENE_LEN * tileCount; 241 | qint32 xVer = (bottomLeftPos.x()+SCENE_LEN/2) / SCENE_LEN * tileCount; 242 | qint32 yVer = (bottomLeftPos.y()+SCENE_LEN/2) / SCENE_LEN * tileCount; 243 | auto horCount = QVector2D(xOrigin, yOrigin).distanceToPoint(QVector2D(xHor, yHor)); 244 | auto verCount = QVector2D(xOrigin, yOrigin).distanceToPoint(QVector2D(xVer, yVer)); 245 | TileSpec origin{m_type, intZoom, static_cast(xOrigin), static_cast(yOrigin)}; 246 | TileRegion region{origin, m_rotation, static_cast(horCount+2), static_cast(verCount+2)}; 247 | // 248 | if(m_tileRegion == region || xOrigin < 0 || xOrigin >= tileCount) 249 | return; 250 | m_tileRegion = region; 251 | m_isloading = true; 252 | emit tileRequested(m_tileRegion); 253 | } 254 | 255 | GraphicsMapThread::TileCacheNode::~TileCacheNode() 256 | { 257 | delete value; 258 | } 259 | 260 | GraphicsMapThread::GraphicsMapThread(): 261 | m_bTMS(false) 262 | { 263 | m_tileCache.setMaxCost(1000); 264 | // 265 | QThread *thread = new QThread; 266 | thread->setObjectName("MapThread"); 267 | this->moveToThread(thread); 268 | thread->start(); 269 | } 270 | 271 | GraphicsMapThread::~GraphicsMapThread() 272 | { 273 | this->thread()->quit(); 274 | this->thread()->wait(); 275 | delete this->thread(); 276 | } 277 | 278 | void GraphicsMapThread::requestTile(const GraphicsMap::TileRegion ®ion) 279 | { 280 | // hide all tile items if tile resource path is invalid 281 | if(m_path.isEmpty()) { 282 | for(auto &tile : m_tileShowedSet) { 283 | hideItem(tile); 284 | } 285 | emit requestFinished(); 286 | return; 287 | } 288 | // just ignore the requeset if rect arec not changed 289 | if(m_tileRegion == region) { 290 | emit requestFinished(); 291 | return; 292 | } 293 | 294 | // 295 | m_tileRegion = region; 296 | 297 | // methoad: 将矩形区域视作从初始方向绕orgin为原点作旋转,然后求得所有瓦片编号新旋转后的坐标 298 | QSet curViewSet; 299 | const auto &origin = region.origin; 300 | const auto &type = origin.type; 301 | const auto &zoom = origin.zoom; 302 | { 303 | QMatrix rotMat; 304 | rotMat.rotate(region.rotation); 305 | QRect boundRect = rotMat.mapRect(QRectF(0, 0, region.horCount, region.verCount)).toRect(); 306 | // noto: y向下递增 307 | const auto xMax = boundRect.x() + boundRect.width(); 308 | const auto yMax = boundRect.y() + boundRect.height(); 309 | for(auto y = boundRect.y(); y <= yMax; ++y) { 310 | for(auto x = boundRect.x(); x <= xMax; ++x) { 311 | curViewSet.insert({type, zoom, origin.x + x, origin.y + y}); 312 | } 313 | } 314 | } 315 | 316 | // compute which to load and which to unload 317 | QSet needToHideTileSet = m_tileTriedToShowdSet; 318 | m_tileTriedToShowdSet.clear(); 319 | { 320 | QSetIterator iter(curViewSet); 321 | while (iter.hasNext()) { 322 | auto tileSpec = iter.next(); 323 | createAscendingTileCache(tileSpec, m_tileTriedToShowdSet); 324 | } 325 | } 326 | QSet realToHideTileSet = needToHideTileSet - m_tileTriedToShowdSet; 327 | 328 | // update the scene tiles 329 | { 330 | QSetIterator iter(m_tileTriedToShowdSet); 331 | while (iter.hasNext()) { 332 | auto &tileSpec = iter.next(); 333 | showItem(tileSpec); 334 | } 335 | } 336 | { 337 | QSetIterator iter(realToHideTileSet); 338 | while (iter.hasNext()) { 339 | auto &tileSpec = iter.next(); 340 | hideItem(tileSpec); 341 | } 342 | } 343 | 344 | emit requestFinished(); 345 | } 346 | 347 | /// \note 该槽函数应该在多线程通过队列调用,以免多线程正在进行上一次资源路径的加载操作 348 | void GraphicsMapThread::requestPath(const QString &path) 349 | { 350 | if(path == m_path) 351 | return; 352 | m_path = path; 353 | } 354 | 355 | void GraphicsMapThread::setTileCacheCount(const int &count) 356 | { 357 | m_tileCache.setMaxCost(count); 358 | } 359 | 360 | void GraphicsMapThread::setTMSMode(const bool &on) 361 | { 362 | m_bTMS = on; 363 | } 364 | 365 | void GraphicsMapThread::showItem(const GraphicsMap::TileSpec &tileSpec) 366 | { 367 | if(m_tileShowedSet.contains(tileSpec)) 368 | return; 369 | 370 | auto tileItem = m_tileCache.object(tileSpec); 371 | if(tileItem->value) { 372 | emit tileToAdd(tileItem->value); 373 | m_tileShowedSet.insert(tileSpec); 374 | } 375 | } 376 | 377 | void GraphicsMapThread::hideItem(const GraphicsMap::TileSpec &tileSpec) 378 | { 379 | // 看不见的直接不管 380 | if(!m_tileShowedSet.contains(tileSpec)) 381 | return; 382 | 383 | auto tileItem = m_tileCache.object(tileSpec); 384 | if(tileItem->value) { 385 | emit tileToRemove(tileItem->value); 386 | m_tileShowedSet.remove(tileSpec); 387 | } 388 | } 389 | 390 | /*! 391 | * \brief MudMapThread::loadTileItem 392 | * \note QTransform的srt顺序对结果有影响,并且和三维矩阵用法不一样, 393 | * 在该函数实现中,先scale再translate,可以理解成将瓦片按照原始大小排列在矩形中(比如1层有四张瓦片,那么排列在256*4->256*4的矩形中), 394 | * 然后这个矩形从右下角整个向左上角缩放达到和sceneRect()正好重合,以实现所有不同zoom的瓦片都重叠在sceneRect()上,也达到了缺省瓦片通过上层瓦片显示的效果。 395 | * 为了方便经纬度和场景坐标的转换,这里将经纬度(0,0)映射在了场景坐标的(0,0)处,所以注意xOff和yOff是先移动到以(0,0)为原点的位置,再向左上移动半个场景的宽度和高度 396 | */ 397 | QGraphicsPixmapItem *GraphicsMapThread::loadTileItem(const GraphicsMap::TileSpec &tileSpec) 398 | { 399 | int tileCount = qPow(2, tileSpec.zoom); 400 | // 401 | QString fileName = QString("%1/%2/%3/%4") 402 | .arg(m_path) 403 | .arg(tileSpec.zoom) 404 | .arg(tileSpec.x) 405 | .arg(m_bTMS ? tileCount - tileSpec.y - 1 : tileSpec.y); 406 | if(QFileInfo::exists(fileName+".jpg")) 407 | fileName += ".jpg"; 408 | else if(QFileInfo::exists(fileName+".png")) 409 | fileName += ".png"; 410 | else 411 | return nullptr; 412 | 413 | auto tileItem = new QGraphicsPixmapItem(fileName); 414 | tileItem->setZValue(tileSpec.zoom - 20); 415 | 416 | // 417 | double xOff = TILE_LEN * (tileSpec.x - tileCount/2.0); // see also: TILE_LEN * tileSpec.x - (TILE_LEN*tileCount) / 2; 418 | double yOff = TILE_LEN * (tileSpec.y - tileCount/2.0); // see also: TILE_LEN * tileSpec.y - (TILE_LEN*tileCount) / 2; 419 | double scaleFac = 1.0 / qPow(2, (tileSpec.zoom-ZOOM_BASE)); 420 | QTransform transform; 421 | transform.scale(scaleFac, scaleFac) 422 | .translate(xOff, yOff); 423 | tileItem->setTransform(transform); 424 | return tileItem; 425 | } 426 | 427 | void GraphicsMapThread::createAscendingTileCache(const GraphicsMap::TileSpec &tileSpec, QSet &sets) 428 | { 429 | auto tileCacheItem = m_tileCache.object(tileSpec); 430 | 431 | // 432 | if(!tileCacheItem) { 433 | tileCacheItem = new GraphicsMapThread::TileCacheNode; 434 | tileCacheItem->value = loadTileItem(tileSpec); 435 | tileCacheItem->tileSpec = tileSpec; 436 | m_tileCache.insert(tileSpec, tileCacheItem); 437 | } 438 | sets.insert(tileSpec); 439 | 440 | if(!tileCacheItem->value && tileSpec.zoom != 0) 441 | createAscendingTileCache(tileSpec.rise(), sets); 442 | } 443 | -------------------------------------------------------------------------------- /graphicsmap.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHICSMAP_H 2 | #define GRAPHICSMAP_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class GraphicsMapThread; 13 | /*! 14 | * \brief 基于Graphics View的地图 15 | * \details 其仅用于显示瓦片地图,要实现地图以外的功能可以继承该类 16 | * \note 鼠标拖拽地图可通过setDragMode(QGraphicsView::ScrollHandDrag)实现 17 | * \bug QGraphicsView::centerOn函数会造成1个像素的抖动问题,参见源码https://github.com/qt/qtbase/blob/5.12.8/src/widgets/graphicsview/qgraphicsview.cpp 1936行 18 | */ 19 | class GRAPHICSMAPLIB_EXPORT GraphicsMap : public QGraphicsView 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | /// 瓦片参数描述结构体 25 | struct TileSpec { 26 | quint8 type; ///< 瓦片类型(用于标识不同路劲资源的地图) 27 | quint8 zoom; ///< 瓦片层级 28 | quint32 x; ///< 瓦片X轴编号 29 | quint32 y; ///< 瓦片Y轴编号 30 | inline TileSpec rise() const { 31 | return GraphicsMap::TileSpec({type, static_cast(zoom-1), x/2, y/2}); 32 | } 33 | inline bool operator< (const TileSpec &rhs) const { 34 | return this->toLong() < rhs.toLong(); 35 | }; 36 | inline bool operator== (const TileSpec &rhs) const { 37 | return this->toLong() == rhs.toLong(); 38 | }; 39 | inline qlonglong toLong() const { 40 | return (qlonglong(type)<<52) | (qlonglong(zoom)<< 44) | (qlonglong(x)<< 22) | y; 41 | }; 42 | }; 43 | /// 显示瓦片区域 44 | struct TileRegion { 45 | GraphicsMap::TileSpec origin; ///< 起始瓦片 46 | qreal rotation; ///< 旋转角度 47 | quint8 horCount; ///< 水平方向瓦片数量 48 | quint8 verCount; ///< 垂直方向瓦片数量 49 | inline bool operator== (const TileRegion &rhs) const { 50 | return origin == rhs.origin && rotation == rhs.rotation && horCount == rhs.horCount && verCount == rhs.verCount; 51 | } 52 | }; 53 | 54 | GraphicsMap(QWidget *parent = nullptr); 55 | ~GraphicsMap(); 56 | /// 设置更新帧率\param fps 地图定时刷新的帧率,0或者负值可切换为按需更新 57 | void setFrameRate(int fps); 58 | /// 设置瓦片路径 59 | void setTilePath(const QString &path); 60 | /// 设置缩放等级 61 | void setZoomLevel(float zoom); 62 | const float &zoomLevel() const; 63 | /// 设置缩放等级范围 64 | void setZoomRange(int min, int max); 65 | /// 设置朝向,正北为起始,向右为正,向左为负 \bug 由于QGraphicsView滚动条精度问题,会造成中心点抖动 66 | void setRotation(const qreal °ree); 67 | /// 获取当前朝向 68 | const qreal &rotation() const; 69 | /// 设置瓦片缓存数量 默认1000张瓦片 70 | void setTileCacheCount(const int &count); 71 | /// 设置TMS瓦片协议 默认XYZ协议(在TMS协议中,y=0的瓦片是最南边的瓦片,而在XYZ模式(OGC WMTS也使用)中,y=0的瓦片是最北边的瓦片) 72 | void setTMSMode(const bool &on); 73 | using QGraphicsView::centerOn; 74 | /// 居中 75 | void centerOn(const QGeoCoordinate &coord); 76 | /// 获取窗口坐标对应的经纬度 77 | QGeoCoordinate toCoordinate(const QPoint &point) const; 78 | /// 获取经纬度对应的窗口坐标 79 | QPoint toPoint(const QGeoCoordinate &coord) const; 80 | 81 | public: 82 | /// 获取场景坐标对应的经纬度 83 | static QGeoCoordinate toCoordinate(const QPointF &point); 84 | /// 获取经纬度对应的场景坐标 85 | static QPointF toScene(const QGeoCoordinate &coord); 86 | /// 通过资源路径,获取唯一对应的资源类型 87 | static quint8 mapType(const QString &path); 88 | 89 | 90 | signals: 91 | void zoomChanged(const float &zoom); 92 | void tileRequested(const TileRegion ®ion); 93 | void pathRequested(const QString &path); 94 | 95 | protected: 96 | virtual void resizeEvent(QResizeEvent *event) override; ///< 用于限制地图最小缩放等级 97 | 98 | private: 99 | void init(); 100 | void updateTile(); 101 | 102 | private: 103 | static QStringList m_mapTypes; ///< 资源路径类型 104 | private: 105 | GraphicsMapThread *m_mapThread; 106 | QSet m_tiles; ///< 已显示瓦片 107 | quint8 m_type; ///< 瓦片资源类型 108 | QTimer m_updateTimer; ///< 更新定时器 109 | // 110 | TileRegion m_tileRegion; ///< 显示瓦片区域 111 | // 112 | bool m_isloading; ///< 正在加载地图 113 | bool m_hasPendingLoad; ///< 是否有挂起的加载请求 114 | float m_zoom; ///< 当前层级 115 | float m_minZoom; ///< 最小缩放层级,刚好适应窗口大小 116 | float m_maxZoom; ///< 最大缩放层级,防止无限放大 117 | float m_preferMinZoom; ///< 预期最小缩放层级,-1代表未设置 118 | qreal m_rotation; ///< 旋转角度 119 | }; 120 | Q_DECLARE_METATYPE(GraphicsMap::TileSpec); 121 | Q_DECLARE_METATYPE(GraphicsMap::TileRegion); 122 | 123 | inline uint qHash(const GraphicsMap::TileSpec &key, uint seed) 124 | { 125 | qlonglong keyVal = (qlonglong(key.zoom)<<48) + (qlonglong(key.x)<< 24) + key.y; 126 | return qHash(keyVal, seed); 127 | } 128 | 129 | /*! 130 | * \brief 瓦片地图管理线程 131 | * \details 负责加载瓦片、卸载瓦片 132 | */ 133 | class GraphicsMapThread : public QObject 134 | { 135 | Q_OBJECT 136 | 137 | /// 瓦片缓存节点,配合QCache实现缓存机制 138 | struct TileCacheNode { 139 | GraphicsMap::TileSpec tileSpec; 140 | QGraphicsItem *value = nullptr; 141 | ~TileCacheNode(); 142 | }; 143 | 144 | public: 145 | GraphicsMapThread(); 146 | ~GraphicsMapThread(); 147 | 148 | 149 | public slots: 150 | /// 请求刷新瓦片区域 151 | void requestTile(const GraphicsMap::TileRegion ®ion); 152 | /// 请求更改瓦片资源来源 153 | void requestPath(const QString &path); 154 | 155 | public: 156 | /// 设置瓦片缓存数量 默认1000张瓦片 157 | void setTileCacheCount(const int &count); 158 | /// 设置TMS瓦片协议 默认XYZ协议(在TMS协议中,y=0的瓦片是最南边的瓦片,而在XYZ模式(OGC WMTS也使用)中,y=0的瓦片是最北边的瓦片) 159 | void setTMSMode(const bool &on); 160 | 161 | signals: 162 | void tileToAdd(QGraphicsItem *tile); 163 | void tileToRemove(QGraphicsItem *tile); 164 | void requestFinished(); 165 | 166 | private: 167 | void showItem(const GraphicsMap::TileSpec &tileSpec); 168 | void hideItem(const GraphicsMap::TileSpec &tileSpec); 169 | /// 从磁盘加载瓦片文件 170 | QGraphicsPixmapItem* loadTileItem(const GraphicsMap::TileSpec &tileSpec); 171 | void createAscendingTileCache(const GraphicsMap::TileSpec &tileSpec, QSet &sets); 172 | 173 | private: 174 | QCache m_tileCache; ///<已加载瓦片缓存 175 | QSet m_tileTriedToShowdSet; ///<已尝试显示瓦片编号集合(上一次调用过showItem的所有瓦片,存在依赖关系的瓦片,实际上只有顶层才显示) 176 | QSet m_tileShowedSet; ///<实际显示瓦片编号集合 177 | // 178 | GraphicsMap::TileRegion m_tileRegion; ///< 请求的瓦片区域 179 | // 180 | QString m_path; 181 | bool m_bTMS; 182 | }; 183 | 184 | #endif // GRAPHICSMAP_H 185 | -------------------------------------------------------------------------------- /interactivemap.cpp: -------------------------------------------------------------------------------- 1 | #include "interactivemap.h" 2 | #include "mapellipseitem.h" 3 | #include "mappolygonitem.h" 4 | #include "maprouteitem.h" 5 | #include "mapobjectitem.h" 6 | #include "maprangeringitem.h" 7 | #include "maptrailitem.h" 8 | #include "maplineitem.h" 9 | #include 10 | 11 | InteractiveMap::InteractiveMap(QWidget *parent) : GraphicsMap(parent), 12 | m_centerObj(nullptr), 13 | m_scaleable(true) 14 | { 15 | 16 | } 17 | 18 | bool InteractiveMap::pushOperator(MapOperator *op) 19 | { 20 | if(!op) 21 | return false; 22 | else if(m_operators.contains(op)) 23 | return false; 24 | // process end funtion with previos operator 25 | if(!m_operators.isEmpty()) { 26 | auto pre = m_operators.top(); 27 | if(pre->mode() == MapOperator::EditOnly) 28 | popOperator(); 29 | else 30 | pre->end(); 31 | } 32 | 33 | m_operators.push(op); 34 | // 35 | op->setScene(this->scene()); 36 | op->setMap(this); 37 | if(op->mode() == MapOperator::EditOnly) 38 | connect(op, &MapOperator::completed, this, &InteractiveMap::popOperator, Qt::ConnectionType(Qt::AutoConnection|Qt::UniqueConnection)); 39 | connect(op, &MapOperator::modeChanged, this, &InteractiveMap::onOperatorModeChanged, Qt::ConnectionType(Qt::AutoConnection|Qt::UniqueConnection)); 40 | // process ready funtion with newlly operator 41 | op->ready(); 42 | return true; 43 | } 44 | 45 | bool InteractiveMap::popOperator() 46 | { 47 | if(m_operators.isEmpty()) 48 | return false; 49 | // unset current 50 | auto op = m_operators.pop(); 51 | disconnect(op, &MapOperator::completed, this, &InteractiveMap::popOperator); 52 | op->end(); 53 | 54 | // set new current 55 | // todo: ignore event for newOp at first 56 | if(!m_operators.isEmpty()) { 57 | auto newOp = m_operators.top(); 58 | newOp->ready(); 59 | } 60 | return true; 61 | } 62 | 63 | MapOperator *InteractiveMap::topOperator() const 64 | { 65 | if(m_operators.isEmpty()) 66 | return nullptr; 67 | return m_operators.top(); 68 | } 69 | 70 | void InteractiveMap::clearOperator() 71 | { 72 | while (!m_operators.isEmpty()) { 73 | popOperator(); 74 | } 75 | } 76 | 77 | void InteractiveMap::setCenter(const MapObjectItem *obj) 78 | { 79 | if(m_centerObj) 80 | disconnect(obj, &MapObjectItem::coordinateChanged, this, qOverload(&GraphicsMap::centerOn)); 81 | // only case that we center on object at first that we should to save drag mode and anchor mode 82 | else { 83 | m_dragMode = this->dragMode(); 84 | m_anchor = this->transformationAnchor(); 85 | } 86 | // 87 | m_centerObj = obj; 88 | if(m_centerObj) { 89 | // We should not to drag map when object is always center on map 90 | connect(obj, &MapObjectItem::coordinateChanged, this, qOverload(&GraphicsMap::centerOn), Qt::ConnectionType(Qt::AutoConnection|Qt::UniqueConnection)); 91 | this->setDragMode(QGraphicsView::NoDrag); 92 | this->setTransformationAnchor(QGraphicsView::AnchorViewCenter); 93 | centerOn(obj->coordinate()); 94 | } 95 | // restore previous drag mode if centerOn is unset 96 | else { 97 | this->setDragMode(m_dragMode); 98 | this->setTransformationAnchor(m_anchor); 99 | } 100 | } 101 | 102 | void InteractiveMap::setZoomable(bool on) 103 | { 104 | m_scaleable = on; 105 | } 106 | 107 | void InteractiveMap::wheelEvent(QWheelEvent *e) 108 | { 109 | if(!m_scaleable) { 110 | e->accept(); 111 | return; 112 | } 113 | bool increase = e->angleDelta().y() > 0; 114 | if(increase) 115 | this->setZoomLevel(zoomLevel() + 0.2); 116 | else 117 | this->setZoomLevel(zoomLevel() - 0.2); 118 | e->accept(); 119 | } 120 | 121 | void InteractiveMap::keyPressEvent(QKeyEvent *event) 122 | { 123 | auto op = m_operators.isEmpty() ? nullptr : m_operators.top(); 124 | if(!op || !op->handleKeyPressEvent(event)) 125 | GraphicsMap::keyPressEvent(event); 126 | } 127 | 128 | void InteractiveMap::keyReleaseEvent(QKeyEvent *event) 129 | { 130 | auto op = m_operators.isEmpty() ? nullptr : m_operators.top(); 131 | if(!op || !op->handleKeyReleaseEvent(event)) 132 | GraphicsMap::keyReleaseEvent(event); 133 | } 134 | 135 | void InteractiveMap::mouseMoveEvent(QMouseEvent *event) 136 | { 137 | auto op = m_operators.isEmpty() ? nullptr : m_operators.top(); 138 | // TODO: move event will be generated whether press event is triggerd or not 139 | if( !op || !op->handleMouseMoveEvent(event)) 140 | GraphicsMap::mouseMoveEvent(event); 141 | } 142 | 143 | void InteractiveMap::mousePressEvent(QMouseEvent *event) 144 | { 145 | auto op = m_operators.isEmpty() ? nullptr : m_operators.top(); 146 | if(!op || !op->handleMousePressEvent(event)) 147 | GraphicsMap::mousePressEvent(event); 148 | 149 | viewport()->setCursor(Qt::ArrowCursor); 150 | } 151 | 152 | void InteractiveMap::mouseReleaseEvent(QMouseEvent *event) 153 | { 154 | auto op = m_operators.isEmpty() ? nullptr : m_operators.top(); 155 | if(!op || !op->handleMouseReleaseEvent(event)) 156 | GraphicsMap::mouseReleaseEvent(event); 157 | 158 | viewport()->setCursor(Qt::ArrowCursor); 159 | } 160 | 161 | void InteractiveMap::mouseDoubleClickEvent(QMouseEvent *event) 162 | { 163 | auto op = m_operators.isEmpty() ? nullptr : m_operators.top(); 164 | if(!op || !op->handleMouseDoubleClickEvent(event)) 165 | GraphicsMap::mouseDoubleClickEvent(event); 166 | } 167 | 168 | 169 | void InteractiveMap::onOperatorModeChanged() 170 | { 171 | auto op = dynamic_cast(sender()); 172 | if(op->mode() == MapOperator::EditOnly) { 173 | connect(op, &MapOperator::completed, this, &InteractiveMap::popOperator, Qt::ConnectionType(Qt::AutoConnection|Qt::UniqueConnection)); 174 | } 175 | else { 176 | disconnect(op, &MapOperator::completed, this, &InteractiveMap::popOperator); 177 | } 178 | } 179 | 180 | MapOperator::MapOperator(QObject *parent) : QObject(parent) 181 | { 182 | 183 | } 184 | 185 | void MapOperator::setMode(OperatorMode mode) 186 | { 187 | if(mode == m_mode) 188 | return; 189 | m_mode = mode; 190 | emit modeChanged(mode); 191 | } 192 | 193 | void MapOperator::ready() 194 | { 195 | 196 | } 197 | 198 | void MapOperator::end() 199 | { 200 | // waitting press event to active @enable 201 | m_keyEventEnable = false; 202 | m_mouseEventEnable = false; 203 | } 204 | 205 | bool MapOperator::handleKeyPressEvent(QKeyEvent *event) 206 | { 207 | m_keyEventEnable = true; 208 | return keyPressEvent(event); 209 | } 210 | 211 | bool MapOperator::handleKeyReleaseEvent(QKeyEvent *event) 212 | { 213 | if(m_skipOnceKeyEvent) { 214 | m_skipOnceKeyEvent = false; 215 | return false; 216 | } 217 | else if(m_keyEventEnable) 218 | return keyReleaseEvent(event); 219 | return false; 220 | } 221 | 222 | bool MapOperator::handleMousePressEvent(QMouseEvent *event) 223 | { 224 | // press event means a new event round 225 | m_mouseEventEnable = true; 226 | m_mouseMoveEventEnable = true; 227 | return mousePressEvent(event); 228 | } 229 | 230 | bool MapOperator::handleMouseMoveEvent(QMouseEvent *event) 231 | { 232 | // we should propagate event when we in case of mouseTracking 233 | if(m_mouseMoveTrackingEnable) { 234 | return mouseMoveEvent(event); 235 | } 236 | else if(m_mouseEventEnable && m_mouseMoveEventEnable && !m_skipOnceMouseEvent) 237 | return mouseMoveEvent(event); 238 | 239 | return false; 240 | } 241 | 242 | bool MapOperator::handleMouseReleaseEvent(QMouseEvent *event) 243 | { 244 | m_mouseMoveEventEnable = false; 245 | if(m_skipOnceMouseEvent) { 246 | m_skipOnceMouseEvent = false; 247 | return false; 248 | } 249 | else if(m_mouseEventEnable) { 250 | return mouseReleaseEvent(event); 251 | } 252 | return false; 253 | } 254 | 255 | bool MapOperator::handleMouseDoubleClickEvent(QMouseEvent *event) 256 | { 257 | m_mouseMoveEventEnable = true; 258 | if(m_skipOnceMouseEvent) { 259 | return false; 260 | } 261 | else if(m_mouseEventEnable) 262 | return mouseDoubleClickEvent(event); 263 | return false; 264 | } 265 | -------------------------------------------------------------------------------- /interactivemap.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERACTIVEMAP_H 2 | #define INTERACTIVEMAP_H 3 | 4 | #include "graphicsmap.h" 5 | #include 6 | 7 | class MapOperator; 8 | class MapObjectItem; 9 | 10 | /*! 11 | * \brief 可交互地图 12 | * \details 1.提供众多对地图元素添加、获取的模板接口 13 | * 2.该地图可实现鼠标缩放地图,配合Operator操作器实现地图对象的创建(预设了几个简单的操作器,复杂操作请自己继承实现) 14 | * \note 通过该类管理的对象,不要手动从场景移除,请调用该类的删除接口 15 | */ 16 | class GRAPHICSMAPLIB_EXPORT InteractiveMap : public GraphicsMap 17 | { 18 | Q_OBJECT 19 | public: 20 | InteractiveMap(QWidget *parent = nullptr); 21 | 22 | /// 创建地图元素 23 | template 24 | T *addMapItem(); 25 | /// 删除地图元素 26 | template 27 | void removeMapItem(T* item); 28 | /// 清空该类管理的所有圆形 29 | template 30 | void clearMapItem(); 31 | 32 | /// 设置事件交互操作器(不可重复) 33 | bool pushOperator(MapOperator *op); 34 | bool popOperator(); 35 | MapOperator *topOperator() const; 36 | void clearOperator(); 37 | /// 保持对象居中,传空值可以取消设置 38 | void setCenter(const MapObjectItem *obj); 39 | /// 设置鼠标是否可以交互缩放 40 | void setZoomable(bool on); 41 | 42 | protected: 43 | virtual void wheelEvent(QWheelEvent *e) override; 44 | // 将要传递给操作器的事件 45 | virtual void keyPressEvent(QKeyEvent *event) override; 46 | virtual void keyReleaseEvent(QKeyEvent *event) override; 47 | virtual void mouseMoveEvent(QMouseEvent *event) override; 48 | virtual void mousePressEvent(QMouseEvent *event) override; 49 | virtual void mouseReleaseEvent(QMouseEvent *event) override; 50 | virtual void mouseDoubleClickEvent(QMouseEvent *event) override; 51 | 52 | private: 53 | void onOperatorModeChanged(); 54 | 55 | private: 56 | QStack m_operators; ///< 操作器栈 57 | const MapObjectItem *m_centerObj; ///< 居中对象 58 | QGraphicsView::DragMode m_dragMode; ///< 拖拽模式(用于取消居中之后回到之前的模式) 59 | QGraphicsView::ViewportAnchor m_anchor; ///< 鼠标锚点(用于取消居中之后回到之前的模式) 60 | // 61 | bool m_scaleable; ///< 是否可以鼠标缩放 62 | }; 63 | 64 | template 65 | T *InteractiveMap::addMapItem() 66 | { 67 | auto item = new T(); 68 | scene()->addItem(item); 69 | return item; 70 | } 71 | 72 | template 73 | void InteractiveMap::removeMapItem(T *item) 74 | { 75 | scene()->removeItem(item); 76 | delete item; 77 | } 78 | 79 | /*! 80 | * \brief 可交互操作器 81 | * \details InteractiveMap将会调用该类的事件接口,以提供固定的地图处理功能,比如创建一个圆形 82 | * \note 在多个事件处理函数中,如果返回true,表示事件已被处理不希望再被传递,反之false表示希望该事件继续被传递处理 83 | * 另外,一个鼠标事件周期包含两种:press release 和 doubleClick release 84 | * \todo 为了统计操作器的交互习惯,建议左键双击完成编辑、右键赋予具体的编辑功能 85 | * \warning 重写子类的ready和end时,必须调用基类函数 86 | */ 87 | class GRAPHICSMAPLIB_EXPORT MapOperator : public QObject 88 | { 89 | Q_OBJECT 90 | friend InteractiveMap; 91 | 92 | public: 93 | /// 操作模式 94 | enum OperatorMode { 95 | EditOnly, ///< 编辑模式,当其completed之后将会自动被取消(自动瞬态) 96 | CreateOnly, ///< 创建模式 97 | CreateEdit ///< 创建+编辑 98 | }; 99 | 100 | MapOperator(QObject *parent = nullptr); 101 | 102 | /// 设置操作模式 103 | void setMode(OperatorMode mode); 104 | inline OperatorMode mode() const { return m_mode;} 105 | 106 | inline QGraphicsScene *scene() const {return m_scene;}; 107 | inline GraphicsMap *map() const {return m_map;}; 108 | 109 | signals: 110 | /// 编辑完成信号 111 | void completed(); 112 | void modeChanged(MapOperator::OperatorMode mode); 113 | 114 | protected: 115 | /// 是否追踪鼠标移动事件(如果启用,moveEvent将不会首受到skipOnceMouseEvent的影响) 116 | inline void setMouseTracking(bool enable) {m_mouseMoveTrackingEnable = enable;} 117 | /// 忽略一次按键事件循环,从press到release的事件,通常在mousePressEvent调用 118 | inline void skipOnceKeyEvent() {m_skipOnceKeyEvent = true;} 119 | /// 忽略一次鼠标事件循环,从press到release的事件,通常在keyPressEvent调用 120 | inline void skipOnceMouseEvent() {m_skipOnceMouseEvent = true;} 121 | 122 | 123 | protected: 124 | virtual void ready(); /// 重新被设置为操作器的时候将会被调用 125 | virtual void end(); /// 操作器被取消的时候将会被调用 126 | virtual bool keyPressEvent(QKeyEvent *event) {return false;} 127 | virtual bool keyReleaseEvent(QKeyEvent *event) {return false;}; 128 | virtual bool mouseDoubleClickEvent(QMouseEvent *event) {return false;}; 129 | virtual bool mousePressEvent(QMouseEvent *event) {return false;}; 130 | virtual bool mouseMoveEvent(QMouseEvent *event) {return false;}; 131 | virtual bool mouseReleaseEvent(QMouseEvent *event) {return false;}; 132 | 133 | private: 134 | inline void setScene(QGraphicsScene *scene) {m_scene = scene;}; 135 | inline void setMap(InteractiveMap *map) {m_map = map;}; 136 | bool handleKeyPressEvent(QKeyEvent *event); 137 | bool handleKeyReleaseEvent(QKeyEvent *event); 138 | bool handleMousePressEvent(QMouseEvent *event); 139 | bool handleMouseMoveEvent(QMouseEvent *event); 140 | bool handleMouseReleaseEvent(QMouseEvent *event); 141 | bool handleMouseDoubleClickEvent(QMouseEvent *event); 142 | 143 | protected: 144 | QGraphicsScene *m_scene; 145 | InteractiveMap *m_map; 146 | OperatorMode m_mode = CreateEdit; ///< 操作模式 147 | 148 | private: 149 | bool m_mouseMoveTrackingEnable = false; ///< 即便没有按下鼠标也能出发move事件 150 | bool m_skipOnceKeyEvent = false; ///< 跳过一次release直到新的press 151 | bool m_skipOnceMouseEvent = false; ///< 跳过多次move和一次release直到新的press 152 | bool m_keyEventEnable = false; ///< 当突然被切换到该操作器时,防止前一个操作器的release事件没有处理 153 | bool m_mouseEventEnable = false; ///< 当突然被切换到该操作器时,防止前一个操作器的release事件没有处理 154 | bool m_mouseMoveEventEnable = false; ///< press和doubleClick按下后触发move事件 155 | }; 156 | 157 | #endif // INTERACTIVEMAP_H 158 | -------------------------------------------------------------------------------- /mapellipseitem.cpp: -------------------------------------------------------------------------------- 1 | #include "mapellipseitem.h" 2 | #include "graphicsmap.h" 3 | #include 4 | #include 5 | #include 6 | 7 | QSet MapEllipseItem::m_items; 8 | 9 | MapEllipseItem::MapEllipseItem(): 10 | m_editable(false), 11 | m_center(0,0), 12 | m_size(1e3, 1e3) 13 | { 14 | // keep the outline width of 1-pixel when item scales 15 | auto pen = this->pen(); 16 | pen.setWidth(1); 17 | pen.setCosmetic(true); 18 | this->setPen(pen); 19 | // 20 | m_firstCtrl.setRect(-4, -4, 8, 8); 21 | m_secondCtrl.setRect(-4, -4, 8, 8); 22 | m_rectCtrl.setParentItem(this); 23 | m_firstCtrl.setParentItem(this); 24 | m_secondCtrl.setParentItem(this); 25 | // 26 | pen.setColor(Qt::lightGray); 27 | pen.setStyle(Qt::DashLine); 28 | m_rectCtrl.setPen(pen); 29 | m_firstCtrl.setAcceptHoverEvents(true); 30 | m_firstCtrl.setPen(QPen(Qt::gray)); 31 | m_firstCtrl.setBrush(Qt::lightGray); 32 | m_firstCtrl.setCursor(Qt::DragMoveCursor); 33 | m_firstCtrl.setFlag(QGraphicsItem::ItemIgnoresTransformations); 34 | m_firstCtrl.setFlag(QGraphicsItem::ItemIsMovable); 35 | m_firstCtrl.setCursor(Qt::DragMoveCursor); 36 | m_secondCtrl.setAcceptHoverEvents(true); 37 | m_secondCtrl.setPen(QPen(Qt::gray)); 38 | m_secondCtrl.setBrush(Qt::lightGray); 39 | m_secondCtrl.setCursor(Qt::DragMoveCursor); 40 | m_secondCtrl.setFlag(QGraphicsItem::ItemIgnoresTransformations); 41 | m_secondCtrl.setFlag(QGraphicsItem::ItemIsMovable); 42 | m_secondCtrl.setCursor(Qt::DragMoveCursor); 43 | // 44 | m_items.insert(this); 45 | updateEditable(); 46 | } 47 | 48 | MapEllipseItem::~MapEllipseItem() 49 | { 50 | m_items.remove(this); 51 | } 52 | 53 | void MapEllipseItem::setEditable(const bool &editable) 54 | { 55 | if(m_editable == editable) 56 | return; 57 | 58 | m_editable = editable; 59 | emit editableChanged(editable); 60 | updateEditable(); 61 | } 62 | 63 | bool MapEllipseItem::isEditable() const 64 | { 65 | return m_editable; 66 | } 67 | 68 | void MapEllipseItem::toggleEditable() 69 | { 70 | setEditable(!m_editable); 71 | } 72 | 73 | void MapEllipseItem::setCenter(const QGeoCoordinate ¢er) 74 | { 75 | if(center == m_center) 76 | return; 77 | m_center = center; 78 | // We should to compute topleft and botoom right coordinate to keep previous size unchanged 79 | auto leftCoord = m_center.atDistanceAndAzimuth(m_size.width()/2, -90); 80 | auto rightCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 90); 81 | auto topCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 0); 82 | auto bottomCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 180); 83 | m_topLeftCoord = {topCoord.latitude(), leftCoord.longitude()}; 84 | m_bottomRightCoord = {bottomCoord.latitude(), rightCoord.longitude()}; 85 | updateEllipse(); 86 | // 87 | emit centerChanged(center); 88 | } 89 | 90 | void MapEllipseItem::setSize(const QSizeF &size) 91 | { 92 | if(m_size == size) 93 | return; 94 | m_size = size; 95 | // We should to compute topleft and botoom right coordinate from new size 96 | auto leftCoord = m_center.atDistanceAndAzimuth(m_size.width()/2, -90); 97 | auto rightCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 90); 98 | auto topCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 0); 99 | auto bottomCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 180); 100 | m_topLeftCoord = {topCoord.latitude(), leftCoord.longitude()}; 101 | m_bottomRightCoord = {bottomCoord.latitude(), rightCoord.longitude()}; 102 | updateEllipse(); 103 | // 104 | emit sizeChanged(size); 105 | } 106 | 107 | void MapEllipseItem::setRect(const QGeoCoordinate &first, const QGeoCoordinate &second) 108 | { 109 | // compute the left right top bottom, which help up to compute the center and the width&height 110 | double left, right, top, bottom; 111 | if(first.longitude() <= second.longitude()) { 112 | left = first.longitude(); 113 | right = second.longitude(); 114 | } else { 115 | left = second.longitude(); 116 | right = first.longitude(); 117 | } 118 | if(first.latitude() <= second.latitude()) { 119 | bottom = first.latitude(); 120 | top = second.latitude(); 121 | } else { 122 | bottom = second.latitude(); 123 | top = first.latitude(); 124 | } 125 | // ignore setter requeset if shape and position has no differece 126 | const QGeoCoordinate tlCoord(top, left); 127 | const QGeoCoordinate brCoord(bottom, right); 128 | if(tlCoord == m_topLeftCoord && brCoord == m_bottomRightCoord) 129 | return; 130 | m_topLeftCoord = tlCoord; 131 | m_bottomRightCoord = brCoord; 132 | 133 | // compute new center and size 134 | m_center = {(top+right)/2, (left+right)/2}; 135 | auto width = QGeoCoordinate(m_center.latitude(), left).distanceTo(QGeoCoordinate(m_center.latitude(), right)); 136 | auto height = QGeoCoordinate(bottom, m_center.longitude()).distanceTo(QGeoCoordinate(top, m_center.longitude())); 137 | m_size = {width, height}; 138 | 139 | // rebuild ellipse shape 140 | updateEllipse(); 141 | 142 | // 143 | emit centerChanged(m_center); 144 | emit sizeChanged(m_size); 145 | } 146 | 147 | const QGeoCoordinate &MapEllipseItem::center() const 148 | { 149 | return m_center; 150 | } 151 | 152 | const QSizeF &MapEllipseItem::size() const 153 | { 154 | return m_size; 155 | } 156 | 157 | const QSet &MapEllipseItem::items() 158 | { 159 | return m_items; 160 | } 161 | 162 | bool MapEllipseItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event) 163 | { 164 | if(!m_editable) 165 | return false; 166 | auto ctrlPoint = watched == &m_firstCtrl ? &m_firstCtrl : &m_secondCtrl; 167 | switch (event->type()) { 168 | case QEvent::GraphicsSceneMouseMove: 169 | case QEvent::GraphicsSceneMouseRelease: 170 | { 171 | // We should to compute center and size 172 | // and then update ellipse item rect 173 | auto firstCtrl = watched == &m_firstCtrl ? &m_firstCtrl : &m_secondCtrl; 174 | auto secondCtrl = firstCtrl == &m_firstCtrl ? &m_secondCtrl : &m_firstCtrl; 175 | // compute center 176 | auto centerPoint = (firstCtrl->pos() + secondCtrl->pos()) / 2; 177 | m_center = GraphicsMap::toCoordinate(centerPoint); 178 | // compute left right top bottom 179 | auto first = firstCtrl->pos(); 180 | auto second = secondCtrl->pos(); 181 | double left, right, top, bottom; 182 | if(first.x() <= second.x()) { 183 | left = first.x(); 184 | right = second.x(); 185 | } else { 186 | left = second.x(); 187 | right = first.x(); 188 | } 189 | if(first.y() <= second.y()) { 190 | bottom = first.y(); 191 | top = second.y(); 192 | } else { 193 | bottom = second.y(); 194 | top = first.y(); 195 | } 196 | 197 | QPointF leftPoint(left, centerPoint.y()); 198 | QPointF rightPoint(right, centerPoint.y()); 199 | QPointF topPoint(centerPoint.x(), top); 200 | QPointF bottomPoint(centerPoint.x(), bottom); 201 | m_size.setWidth(GraphicsMap::toCoordinate(leftPoint).distanceTo(GraphicsMap::toCoordinate(rightPoint))); 202 | m_size.setHeight(GraphicsMap::toCoordinate(topPoint).distanceTo(GraphicsMap::toCoordinate(bottomPoint))); 203 | auto topLeftPoint = QPointF(left, top); 204 | auto bottomRightPoint = QPointF(right, bottom); 205 | m_rectCtrl.setRect({topLeftPoint, bottomRightPoint}); 206 | QGraphicsEllipseItem::setRect({topLeftPoint, bottomRightPoint}); 207 | // 208 | emit centerChanged(m_center); 209 | emit sizeChanged(m_size); 210 | // compute 211 | break; 212 | } 213 | case QEvent::GraphicsSceneHoverEnter: 214 | { 215 | ctrlPoint->setBrush(Qt::white); 216 | ctrlPoint->setScale(1.2); 217 | break; 218 | } 219 | case QEvent::GraphicsSceneHoverLeave: 220 | { 221 | ctrlPoint->setBrush(Qt::lightGray); 222 | ctrlPoint->setScale(1); 223 | break; 224 | } 225 | default: 226 | break; 227 | } 228 | 229 | return false; 230 | } 231 | 232 | QVariant MapEllipseItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) 233 | { 234 | if(change != ItemSceneHasChanged) 235 | return QGraphicsEllipseItem::itemChange(change, value); 236 | 237 | m_firstCtrl.installSceneEventFilter(this); 238 | m_secondCtrl.installSceneEventFilter(this); 239 | return QGraphicsEllipseItem::itemChange(change, value); 240 | } 241 | 242 | void MapEllipseItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) 243 | { 244 | QGraphicsEllipseItem::mouseDoubleClickEvent(event); 245 | emit doubleClicked(); 246 | } 247 | 248 | /*! 249 | * \brief MapEllipseItem::updateEllipse 250 | * \details 更新圆形实际是根据左上和右下两个点进行重建,所以在其他Setter函数必须提前将两个计算好 251 | */ 252 | void MapEllipseItem::updateEllipse() 253 | { 254 | // compute topleft point and bottomright point in scene 255 | auto topLeftPoint = GraphicsMap::toScene(m_topLeftCoord); 256 | auto bottomRightPoint = GraphicsMap::toScene(m_bottomRightCoord); 257 | // update ellipse outlook 258 | QGraphicsEllipseItem::setRect({topLeftPoint, bottomRightPoint}); 259 | // update ellipse's contorl points 260 | m_firstCtrl.setPos(topLeftPoint); 261 | m_secondCtrl.setPos(bottomRightPoint); 262 | m_rectCtrl.setRect({topLeftPoint, bottomRightPoint}); 263 | } 264 | 265 | void MapEllipseItem::updateEditable() 266 | { 267 | auto pen = this->pen(); 268 | pen.setWidth(0); 269 | pen.setColor(m_editable ? Qt::white : Qt::lightGray); 270 | setPen(pen); 271 | 272 | m_rectCtrl.setVisible(m_editable); 273 | m_firstCtrl.setVisible(m_editable); 274 | m_secondCtrl.setVisible(m_editable); 275 | } 276 | -------------------------------------------------------------------------------- /mapellipseitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPELLIPSEITEM_H 2 | #define MAPELLIPSEITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | 8 | /*! 9 | * \brief 椭圆形/正圆 10 | * \note 暂缺少图形拖动的实现 11 | */ 12 | class GRAPHICSMAPLIB_EXPORT MapEllipseItem : public QObject, public QGraphicsEllipseItem 13 | { 14 | Q_OBJECT 15 | public: 16 | MapEllipseItem(); 17 | ~MapEllipseItem(); 18 | /// 控制可编辑性 19 | void setEditable(const bool &editable); 20 | bool isEditable() const; 21 | void toggleEditable(); 22 | /// 设置中心 23 | void setCenter(const QGeoCoordinate ¢er); 24 | /// 设置尺寸 米 25 | void setSize(const QSizeF &size); 26 | /// 设置包围矩形两个对角顶点来自动生成圆形 27 | void setRect(const QGeoCoordinate &first, const QGeoCoordinate &second); 28 | /// 获取中心 29 | const QGeoCoordinate ¢er() const; 30 | /// 获取尺寸 31 | const QSizeF &size() const; 32 | 33 | public: 34 | /// 获取所有的实例 35 | static const QSet &items(); 36 | 37 | signals: 38 | void centerChanged(const QGeoCoordinate ¢er); 39 | void sizeChanged(const QSizeF ¢er); 40 | void doubleClicked(); 41 | void editableChanged(bool editable); 42 | 43 | protected: 44 | virtual bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override; 45 | /// 被添加到场景后,为控制点添加事件过滤器 46 | virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; 47 | virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; 48 | 49 | private: 50 | void updateEllipse(); 51 | void updateEditable(); 52 | 53 | private: 54 | static QSet m_items; ///< 所有实例 55 | 56 | private: 57 | bool m_editable; ///< 鼠标是否可交互编辑 58 | // 59 | QGeoCoordinate m_center; ///< 中心 60 | QSizeF m_size; ///< 宽高 61 | // 62 | QGeoCoordinate m_topLeftCoord; ///< 左上经纬度 63 | QGeoCoordinate m_bottomRightCoord; ///< 右下经纬度 64 | // 65 | QGraphicsRectItem m_rectCtrl; ///< 包围矩形(辅助示意) 66 | QGraphicsEllipseItem m_firstCtrl; ///< 对角控制点1 67 | QGraphicsEllipseItem m_secondCtrl; ///< 对角控制点2 68 | }; 69 | 70 | #endif // MAPELLIPSEITEM_H 71 | -------------------------------------------------------------------------------- /maplabelitem.cpp: -------------------------------------------------------------------------------- 1 | #include "maplabelitem.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | MapLabelItem::MapLabelItem() 8 | { 9 | this->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); 10 | this->setFlag(QGraphicsItem::ItemIsMovable, true); 11 | // 12 | auto font = m_title.font(); 13 | auto brush = m_title.brush(); 14 | font.setFamily("Microsoft YaHei"); 15 | font.setPointSizeF(16); 16 | brush.setColor(Qt::white); 17 | m_title.setFont(font); 18 | m_title.setBrush(brush); 19 | m_title.setParentItem(this); 20 | // 21 | font.setPointSizeF(12); 22 | m_text.setFont(font); 23 | m_text.setBrush(brush); 24 | m_text.setParentItem(this); 25 | } 26 | 27 | void MapLabelItem::setBackground(const QPixmap &pixmap) 28 | { 29 | this->setOffset(0, 0); 30 | this->setPixmap(pixmap); 31 | this->setOffset(-this->boundingRect().center()); 32 | updateLayout(); 33 | } 34 | 35 | void MapLabelItem::setTitle(const QString &title) 36 | { 37 | bool move = m_text.boundingRect().isNull() && !this->pixmap().isNull(); 38 | m_title.setText(title); 39 | if(move) 40 | updateLayout(); 41 | } 42 | 43 | void MapLabelItem::setText(const QString &text) 44 | { 45 | bool move = m_text.boundingRect().isNull() && !this->pixmap().isNull(); 46 | m_text.setText(text); 47 | if(move) 48 | updateLayout(); 49 | } 50 | 51 | void MapLabelItem::updateLayout() 52 | { 53 | auto parentBound = this->boundingRect(); 54 | auto titleBound = m_title.boundingRect(); 55 | auto textBound = m_text.boundingRect(); 56 | auto spacing = (parentBound.height() - titleBound.height() - textBound.height()) / 3; 57 | // 58 | auto x = - titleBound.width() / 2; 59 | auto y = - (parentBound.height()/2 - spacing); 60 | m_title.setPos(x, y); 61 | // 62 | x = - textBound.width() / 2; 63 | y = - (parentBound.height()/2 - spacing - textBound.height()); 64 | m_text.setPos(x, y); 65 | } 66 | -------------------------------------------------------------------------------- /maplabelitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPLABELITEM_H 2 | #define MAPLABELITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | 8 | /*! 9 | * \brief 地图文本标签对象 10 | * \details 可显示文字和背景图片。请确保先设置文本字体样式再设置文本 11 | */ 12 | class GRAPHICSMAPLIB_EXPORT MapLabelItem : public QGraphicsPixmapItem 13 | { 14 | public: 15 | MapLabelItem(); 16 | /// 设置背景图片 17 | void setBackground(const QPixmap &pixmap); 18 | /// 设置标题 19 | void setTitle(const QString &title); 20 | /// 获取标题对象 21 | QGraphicsSimpleTextItem *title(); 22 | /// 设置文本内容 23 | void setText(const QString &text); 24 | /// 获取标题对象 25 | QGraphicsSimpleTextItem *text(); 26 | 27 | private: 28 | void updateLayout(); 29 | 30 | private: 31 | QGraphicsSimpleTextItem m_title; 32 | QGraphicsSimpleTextItem m_text; 33 | }; 34 | 35 | #endif // MAPLABELITEM_H 36 | -------------------------------------------------------------------------------- /maplineitem.cpp: -------------------------------------------------------------------------------- 1 | #include "maplineitem.h" 2 | #include "graphicsmap.h" 3 | #include 4 | 5 | QSet MapLineItem::m_items; 6 | 7 | MapLineItem::MapLineItem() 8 | { 9 | // 10 | m_items.insert(this); 11 | 12 | // 13 | auto pen = this->pen(); 14 | pen.setWidth(2); 15 | pen.setCosmetic(true); // it will be always 2 pixmap whatever scale transform 16 | pen.setCapStyle(Qt::RoundCap); 17 | pen.setJoinStyle(Qt::RoundJoin); 18 | pen.setColor(Qt::white); 19 | this->setPen(pen); 20 | // 21 | auto font = m_text.font(); 22 | font.setPointSizeF(14); 23 | font.setFamily("Microsoft YaHei"); 24 | m_text.setFont(font); 25 | m_text.setBrush(Qt::yellow); 26 | m_text.setPen(QColor(0, 255, 0)); 27 | m_text.setParentItem(this); 28 | m_text.setFlag(ItemIgnoresTransformations); 29 | // 30 | m_startIcon.setParentItem(this); 31 | m_startIcon.setFlag(ItemIgnoresTransformations); 32 | m_endIcon.setParentItem(this); 33 | m_endIcon.setFlag(ItemIgnoresTransformations); 34 | } 35 | 36 | MapLineItem::~MapLineItem() 37 | { 38 | m_items.remove(this); 39 | } 40 | 41 | void MapLineItem::setStartPoint(const QGeoCoordinate & pt) 42 | { 43 | if(m_endings.first == pt) 44 | return; 45 | m_endings.first = pt; 46 | 47 | if(!m_endings.second.isValid()) 48 | m_endings.second = pt; 49 | 50 | updateEndings(); 51 | } 52 | 53 | void MapLineItem::setEndPoint(const QGeoCoordinate & pt) 54 | { 55 | if(m_endings.second == pt) 56 | return; 57 | m_endings.second = pt; 58 | 59 | if(!m_endings.first.isValid()) 60 | m_endings.first = pt; 61 | 62 | updateEndings(); 63 | } 64 | 65 | 66 | void MapLineItem::setText(const QString & text, Qt::Alignment align) 67 | { 68 | m_text.setText(text); 69 | auto lineBound = this->boundingRect(); 70 | auto textBound = m_text.boundingRect(); 71 | QPointF pos(-textBound.center()); 72 | if (align & Qt::AlignLeft) { 73 | pos.setX(-lineBound.width()/2); 74 | } 75 | else if (align & Qt::AlignHCenter) { 76 | pos.setX(0); 77 | } 78 | else if (align & Qt::AlignRight) { 79 | pos.setX(lineBound.width() / 2); 80 | } 81 | if (align & Qt::AlignTop) { 82 | pos.setY(- lineBound.height() / 2); 83 | } 84 | else if (align & Qt::AlignVCenter) { 85 | pos.setY(0); 86 | } 87 | else if (align & Qt::AlignBottom) { 88 | pos.setY(lineBound.height() / 2); 89 | } 90 | m_text.setPos(pos); 91 | } 92 | 93 | void MapLineItem::setTextColor(const QColor & color) 94 | { 95 | m_text.setPen(color); 96 | } 97 | 98 | void MapLineItem::setFontSizeF(const qreal size) 99 | { 100 | auto font = m_text.font(); 101 | font.setPointSizeF(size); 102 | m_text.setFont(font); 103 | } 104 | 105 | void MapLineItem::setLineWidth(const int width) 106 | { 107 | auto pen = this->pen(); 108 | pen.setWidth(width); 109 | this->setPen(pen); 110 | } 111 | 112 | void MapLineItem::setStartIcon(const QPixmap &pixmap, Qt::Alignment align) 113 | { 114 | m_startIcon.setPixmap(pixmap); 115 | auto boundRect = m_startIcon.boundingRect(); 116 | QPointF offset(-boundRect.width() / 2, -boundRect.height()); 117 | 118 | if (align & Qt::AlignLeft) { 119 | offset.setX(-boundRect.width()); 120 | } 121 | else if (align & Qt::AlignHCenter) { 122 | offset.setX(-boundRect.width() / 2); 123 | } 124 | else if (align & Qt::AlignRight) { 125 | offset.setX(0); 126 | } 127 | if (align & Qt::AlignTop) { 128 | offset.setY(-boundRect.height()); 129 | } 130 | else if (align & Qt::AlignVCenter) { 131 | offset.setY(0); 132 | } 133 | else if (align & Qt::AlignBottom) { 134 | offset.setY(boundRect.height()); 135 | } 136 | m_startIcon.setOffset(offset); 137 | updateEndings(); 138 | } 139 | 140 | void MapLineItem::setEndIcon(const QPixmap &pixmap, Qt::Alignment align) 141 | { 142 | m_endIcon.setPixmap(pixmap); 143 | auto boundRect = m_endIcon.boundingRect(); 144 | QPointF offset(-boundRect.width() / 2, -boundRect.height()); 145 | 146 | if (align & Qt::AlignLeft) { 147 | offset.setX(-boundRect.width()); 148 | } 149 | else if (align & Qt::AlignHCenter) { 150 | offset.setX(-boundRect.width() / 2); 151 | } 152 | else if (align & Qt::AlignRight) { 153 | offset.setX(boundRect.width() / 2); 154 | } 155 | if (align & Qt::AlignTop) { 156 | offset.setY(-boundRect.height()); 157 | } 158 | else if (align & Qt::AlignVCenter) { 159 | offset.setY(0); 160 | } 161 | else if (align & Qt::AlignBottom) { 162 | offset.setY(boundRect.height()); 163 | } 164 | m_endIcon.setOffset(offset); 165 | updateEndings(); 166 | } 167 | 168 | const QPair &MapLineItem::endings() 169 | { 170 | return m_endings; 171 | } 172 | 173 | void MapLineItem::attach(MapObjectItem *obj, MapLabelItem *label) 174 | { 175 | 176 | } 177 | 178 | const QSet &MapLineItem::items() 179 | { 180 | return m_items; 181 | } 182 | 183 | void MapLineItem::updateEndings() 184 | { 185 | auto ending0 = GraphicsMap::toScene(m_endings.first); 186 | auto ending1 = GraphicsMap::toScene(m_endings.second); 187 | auto origin = (ending0 + ending1) / 2; 188 | auto p0 = ending0 - origin; 189 | auto p1 = ending1 - origin; 190 | this->setPos(origin); 191 | this->setLine({p0, p1}); 192 | // 193 | m_startIcon.setPos(p0); 194 | m_endIcon.setPos(p1); 195 | } 196 | -------------------------------------------------------------------------------- /maplineitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPLINEITEM_H 2 | #define MAPLINEITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class MapObjectItem; 11 | class MapLabelItem; 12 | 13 | /*! 14 | * \brief 直线 15 | */ 16 | class GRAPHICSMAPLIB_EXPORT MapLineItem : public QObject, public QGraphicsLineItem 17 | { 18 | Q_OBJECT 19 | public: 20 | MapLineItem(); 21 | ~MapLineItem(); 22 | /// 设置起始点 23 | void setStartPoint(const QGeoCoordinate & pt); 24 | /// 设置末端点 25 | void setEndPoint(const QGeoCoordinate & pt); 26 | /// 设置文字 27 | void setText(const QString &text, Qt::Alignment align = Qt::AlignCenter); 28 | /// 设置文字颜色 29 | void setTextColor(const QColor &color); 30 | /// 设置文字大小 31 | void setFontSizeF(const qreal size); 32 | /// 设置线宽 33 | void setLineWidth(const int width); 34 | /// 设置线段起始图标 35 | void setStartIcon(const QPixmap &pixmap, Qt::Alignment align = Qt::AlignCenter); 36 | /// 设置线段末端图标 37 | void setEndIcon(const QPixmap &pixmap, Qt::Alignment align = Qt::AlignCenter); 38 | /// 获取线段两点位置 39 | const QPair &endings(); 40 | /// 依附到地图对象和标签对象,将会自动更新位置 41 | void attach(MapObjectItem *obj, MapLabelItem *label); 42 | /// 取消依附地图对象,后续手动更新位置 43 | void detach(); 44 | 45 | public: 46 | /// 获取所有的实例 47 | static const QSet &items(); 48 | 49 | private: 50 | void updateEndings(); 51 | 52 | private: 53 | static QSet m_items; ///< 所有实例 54 | 55 | private: 56 | // 57 | QGraphicsSimpleTextItem m_text; 58 | QGraphicsPixmapItem m_startIcon; ///<起始图标 59 | QGraphicsPixmapItem m_endIcon; ///<末端图标 60 | QPair m_endings; ///<线段两点 61 | }; 62 | 63 | #endif // MAPLINEITEM_H 64 | -------------------------------------------------------------------------------- /mapobjectitem.cpp: -------------------------------------------------------------------------------- 1 | #include "mapobjectitem.h" 2 | #include "graphicsmap.h" 3 | #include "maptableitem.h" 4 | #include "mapscutcheonitem.h" 5 | #include 6 | #include 7 | #include 8 | 9 | /* XPM */ 10 | static const char *default_xpm[] = { 11 | /* columns rows colors chars-per-pixel */ 12 | "32 32 9 1 ", 13 | " c None", 14 | ". c #19F929", 15 | "X c #1AF928", 16 | "o c #1AF929", 17 | "O c #19FA29", 18 | "+ c #1AFA29", 19 | "@ c #1BFA29", 20 | "# c #1AFB29", 21 | "$ c #1AFB2A", 22 | /* pixels */ 23 | " ", 24 | " ", 25 | " ++ ", 26 | " ++++ ", 27 | " ++++ ", 28 | " ++++ ", 29 | " ++++++++++ ", 30 | " ++++++++++++ ", 31 | " ++++++++++++++++ ", 32 | " +++++ ++++ +++++ ", 33 | " #++++ ++++ ++++# ", 34 | " +++++ ++ +++++ ", 35 | " ++++ +++# ", 36 | " +++ +++ ", 37 | " ++++++++ ++++++++ ", 38 | " ++++++++++ ++++++++++ ", 39 | " ++++++++++ ++++++++++ ", 40 | " +++++++++ +++++++++ ", 41 | " +++ +++ ", 42 | " ++++ ++++ ", 43 | " +++++ ++++ +++++ ", 44 | " +++++ ++++ +++++ ", 45 | " ++++O ++++ +++++ ", 46 | " ++++++++++++++++ ", 47 | " #+++++++++++ ", 48 | " +#++++++#+ ", 49 | " ++++ ", 50 | " ++++ ", 51 | " ++++ ", 52 | " ++ ", 53 | " ", 54 | " " 55 | }; 56 | 57 | QSet MapObjectItem::m_items; 58 | 59 | MapObjectItem::MapObjectItem(const QGeoCoordinate &coord) 60 | { 61 | auto font = m_text.font(); 62 | font.setFamily("Microsoft YaHei"); 63 | font.setPointSize(10); 64 | m_text.setFont(font); 65 | m_text.setBrush(Qt::black); 66 | m_text.setParentItem(this); 67 | m_border.setPen(QPen(Qt::lightGray)); 68 | m_border.setVisible(false); 69 | m_border.setParentItem(this); 70 | // 71 | this->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); 72 | this->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); 73 | this->setTransformationMode(Qt::SmoothTransformation); 74 | setIcon(QString()); 75 | // 76 | m_items.insert(this); 77 | 78 | //m_Suct = new MapTableItem(coord, this); 79 | m_Suct = new MapSuctcheonItem(this); 80 | m_Suct->setBackBrush(QColor(30,144,255, 200)); 81 | m_Suct->addField(u8"编号", true); 82 | m_Suct->addField(u8"名称", true); 83 | m_Suct->addField(u8"经度", false); 84 | m_Suct->addField(u8"维度", false); 85 | m_Suct->addField(u8"高度", false); 86 | m_Suct->addField(u8"方位角", false); 87 | m_Suct->addField(u8"俯仰角", false); 88 | m_Suct->addField(u8"滚转角", false); 89 | m_Suct->addField(u8"速度", false); 90 | // m_Suct->addField(u8"转速", false); 91 | // m_Suct->addField(u8"余油量", false); 92 | // m_Suct->addField(u8"已飞航程", false); 93 | m_Suct->setValue(u8"编号", u8""); 94 | m_Suct->setValue(u8"名称", u8""); 95 | m_Suct->setValue(u8"经度", u8"0°"); 96 | m_Suct->setValue(u8"维度", u8"0°"); 97 | m_Suct->setValue(u8"高度", u8"0"); 98 | m_Suct->setValue(u8"方位角", u8"0°"); 99 | m_Suct->setValue(u8"俯仰角", u8"0°"); 100 | m_Suct->setValue(u8"滚转角", u8"0°"); 101 | m_Suct->setValue(u8"速度", u8"0"); 102 | // m_Suct->setValue(u8"转速", u8"0"); 103 | // m_Suct->setValue(u8"余油量", u8"0"); 104 | // m_Suct->setValue(u8"已飞航程", u8"0"); 105 | 106 | QPen pen; 107 | pen.setColor(QColor(255, 255, 255)); 108 | m_Suct->setFieldPen(u8"编号", pen); 109 | m_Suct->setFieldPen(u8"名称", pen); 110 | m_Suct->setFieldPen(u8"经度", pen); 111 | m_Suct->setFieldPen(u8"维度", pen); 112 | m_Suct->setFieldPen(u8"高度", pen); 113 | m_Suct->setFieldPen(u8"方位角", pen); 114 | m_Suct->setFieldPen(u8"俯仰角", pen); 115 | m_Suct->setFieldPen(u8"滚转角", pen); 116 | m_Suct->setFieldPen(u8"速度", pen); 117 | // m_Suct->setFieldPen(u8"转速", pen); 118 | // m_Suct->setFieldPen(u8"余油量", pen); 119 | // m_Suct->setFieldPen(u8"已飞航程", pen); 120 | m_Suct->setValuePen(u8"编号", pen); 121 | m_Suct->setValuePen(u8"名称", pen); 122 | m_Suct->setValuePen(u8"经度", pen); 123 | m_Suct->setValuePen(u8"维度", pen); 124 | m_Suct->setValuePen(u8"高度", pen); 125 | m_Suct->setValuePen(u8"方位角", pen); 126 | m_Suct->setValuePen(u8"俯仰角", pen); 127 | m_Suct->setValuePen(u8"滚转角", pen); 128 | m_Suct->setValuePen(u8"速度", pen); 129 | // m_Suct->setValuePen(u8"转速", pen); 130 | // m_Suct->setValuePen(u8"余油量", pen); 131 | // m_Suct->setValuePen(u8"已飞航程", pen); 132 | 133 | // 134 | setCoordinate(coord); 135 | } 136 | 137 | MapObjectItem::~MapObjectItem() 138 | { 139 | m_items.remove(this); 140 | } 141 | 142 | void MapObjectItem::setCoordinate(const QGeoCoordinate &coord) 143 | { 144 | if(m_coord == coord) 145 | return; 146 | 147 | m_coord = coord; 148 | this->setPos(GraphicsMap::toScene(coord)); 149 | emit coordinateChanged(coord); 150 | } 151 | 152 | const QGeoCoordinate &MapObjectItem::coordinate() const 153 | { 154 | return m_coord; 155 | } 156 | 157 | void MapObjectItem::setEuler(const QVector3D &euler) 158 | { 159 | if(m_euler == euler) 160 | return; 161 | 162 | m_euler = euler; 163 | this->setRotation(euler.x()); 164 | emit eulerChanged(euler); 165 | } 166 | 167 | const QVector3D &MapObjectItem::euler() const 168 | { 169 | return m_euler; 170 | } 171 | 172 | void MapObjectItem::setIcon(const QPixmap &pixmap) 173 | { 174 | this->setOffset(0, 0); 175 | // Reset to default icon 176 | if(pixmap.isNull()) { 177 | this->setPixmap(QPixmap(default_xpm)); 178 | } 179 | else { 180 | this->setPixmap(pixmap); 181 | } 182 | // make sure that the center of icon is positioned at current position 183 | auto boundRect = this->boundingRect(); 184 | setOffset(-boundRect.center()); 185 | } 186 | 187 | void MapObjectItem::setIconColor(const QColor &color, qreal strength) 188 | { 189 | // We should to unset previous color 190 | if(!color.isValid()) { 191 | this->setGraphicsEffect(nullptr); 192 | return; 193 | } 194 | 195 | auto effect = dynamic_cast(graphicsEffect()); 196 | if(!effect) { 197 | effect = new QGraphicsColorizeEffect; 198 | this->setGraphicsEffect(effect); 199 | } 200 | effect->setColor(color); 201 | effect->setStrength(strength); 202 | } 203 | 204 | void MapObjectItem::setText(const QString &text, Qt::Alignment align) 205 | { 206 | m_text.setText(text); 207 | auto iconBound = this->boundingRect(); 208 | auto textBound = m_text.boundingRect(); 209 | QPointF pos(-textBound.center()); 210 | if(align & Qt::AlignLeft) { 211 | pos.setX(-iconBound.width()/2 - textBound.center().x()); 212 | } 213 | else if(align & Qt::AlignHCenter) { 214 | pos.setX(-textBound.center().x()); 215 | } 216 | else if(align & Qt::AlignRight) { 217 | pos.setX(iconBound.width()/2); 218 | } 219 | if(align & Qt::AlignTop) { 220 | pos.setY(-iconBound.height()/2 - textBound.height()); 221 | } 222 | else if(align & Qt::AlignVCenter) { 223 | pos.setY(-textBound.center().y()); 224 | } 225 | else if(align & Qt::AlignBottom) { 226 | pos.setY(iconBound.height()/2); 227 | } 228 | m_text.setPos(pos); 229 | } 230 | 231 | void MapObjectItem::setTextColor(const QColor &color) 232 | { 233 | m_text.setPen(color); 234 | m_text.setBrush(color); 235 | } 236 | 237 | void MapObjectItem::setAllowMouseEvent(bool enable) 238 | { 239 | m_enableMouse = enable; 240 | } 241 | 242 | void MapObjectItem::setMoveable(bool movable) 243 | { 244 | this->setFlag(QGraphicsItem::ItemIsMovable, movable); 245 | this->setAcceptHoverEvents(movable); 246 | } 247 | 248 | void MapObjectItem::setCheckable(bool checkable) 249 | { 250 | // be sure that setChecked is called first 251 | // and then do operator= 252 | if(checkable == false) 253 | setChecked(false); 254 | m_checkable = checkable; 255 | } 256 | 257 | void MapObjectItem::setChecked(bool checked) 258 | { 259 | if(!m_checkable) 260 | return; 261 | if(m_checked == checked) 262 | return; 263 | m_checked = checked; 264 | if(checked) { 265 | m_border.setRect(this->boundingRect()); 266 | m_border.setVisible(true); 267 | } 268 | else { 269 | m_border.setVisible(false); 270 | } 271 | } 272 | 273 | void MapObjectItem::toggle() 274 | { 275 | setChecked(!m_checked); 276 | } 277 | 278 | bool MapObjectItem::isChecked() const 279 | { 280 | return m_checked; 281 | } 282 | 283 | void MapObjectItem::setRoute(MapRouteItem *route) 284 | { 285 | if(m_route == route) 286 | return; 287 | m_route = route; 288 | emit routeChanged(route); 289 | } 290 | 291 | MapRouteItem *MapObjectItem::route() const 292 | { 293 | return m_route; 294 | } 295 | 296 | void MapObjectItem::setSpeed(double speed) 297 | { 298 | m_speed = speed; 299 | } 300 | 301 | const double MapObjectItem::getSpeed() const 302 | { 303 | return m_speed; 304 | } 305 | 306 | const QSet &MapObjectItem::items() 307 | { 308 | return m_items; 309 | } 310 | 311 | QVariant MapObjectItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) 312 | { 313 | if(change == ItemRotationHasChanged) { 314 | // auto rotate = this->rotation(); 315 | // m_Suct->setRotation(-rotate); 316 | emit rotationChanged(this->rotation()); 317 | } 318 | return QGraphicsPixmapItem::itemChange(change, value); 319 | } 320 | 321 | void MapObjectItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) 322 | { 323 | QGraphicsPixmapItem::hoverEnterEvent(event); 324 | if(this->flags() & QGraphicsItem::ItemIsMovable) { 325 | this->setScale(1.2); 326 | this->setCursor(Qt::DragMoveCursor); 327 | } 328 | } 329 | 330 | void MapObjectItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) 331 | { 332 | QGraphicsPixmapItem::hoverEnterEvent(event); 333 | if(this->flags() & QGraphicsItem::ItemIsMovable) { 334 | this->setScale(1.1); 335 | this->setCursor(Qt::ArrowCursor); 336 | } 337 | } 338 | 339 | void MapObjectItem::mousePressEvent(QGraphicsSceneMouseEvent *event) 340 | { 341 | QGraphicsPixmapItem::mousePressEvent(event); 342 | 343 | if(event->buttons() & Qt::RightButton){ 344 | if(m_enableMouse) 345 | event->accept(); 346 | emit menuRequest(); 347 | return; 348 | } 349 | 350 | 351 | if(m_enableMouse) 352 | event->accept(); 353 | // else will no longer propagate event to mouseMoveEvent and mouseReleaseEvent 354 | m_pressPos = event->screenPos(); 355 | emit pressed(); 356 | } 357 | 358 | void MapObjectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) 359 | { 360 | QGraphicsPixmapItem::mouseMoveEvent(event); 361 | 362 | m_coord = GraphicsMap::toCoordinate(this->scenePos()); 363 | emit coordinateDragged(m_coord); 364 | } 365 | 366 | void MapObjectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) 367 | { 368 | QGraphicsPixmapItem::mouseReleaseEvent(event); 369 | // QGraphicsItem::ItemIsMovable will case the function be called 370 | if(!m_enableMouse) 371 | return; 372 | // if moved some distance, we ignore switch-check 373 | if(m_checkable && ((m_pressPos-event->screenPos()).manhattanLength() < 3) 374 | && this->contains(event->pos())) { 375 | setChecked(!m_checked); 376 | emit toggled(m_checked); 377 | } 378 | emit released(); 379 | emit clicked(m_checked); 380 | } 381 | 382 | void MapObjectItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) 383 | { 384 | QGraphicsPixmapItem::mouseDoubleClickEvent(event); 385 | emit doubleClicked(); 386 | } 387 | -------------------------------------------------------------------------------- /mapobjectitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPOBJECTITEM_H 2 | #define MAPOBJECTITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include "maprouteitem.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class MapTableItem; 12 | class MapSuctcheonItem; 13 | 14 | /*! 15 | * \brief 地图对象 16 | * \details 可用于将图标添加到地图上 17 | * \note 1.该对象不受地图缩放影响,将会始终保持屏幕大小 18 | * 2.默认会阻断鼠标时间穿透到地图上,但是要想穿透鼠标事件请调用setAllowMouseEvent(false) 19 | * 3.pressed信号总是可以收到 20 | */ 21 | class GRAPHICSMAPLIB_EXPORT MapObjectItem : public QObject, public QGraphicsPixmapItem 22 | { 23 | Q_OBJECT 24 | public: 25 | MapObjectItem(const QGeoCoordinate &coord = {0, 0, 0}); 26 | ~MapObjectItem(); 27 | /// 设置经纬度位置 28 | void setCoordinate(const QGeoCoordinate &coord); 29 | /// 获取当前经纬度位置 30 | const QGeoCoordinate &coordinate() const; 31 | /// 设置欧拉角 修改当前地图飞机对象 32 | void setEuler(const QVector3D &euler); 33 | /// 获取欧拉角 34 | const QVector3D &euler() const; 35 | /// 设置图标,无效资源将使用默认图标 36 | void setIcon(const QPixmap &pixmap); 37 | /// 设置图标为纯色,传QColor()可以取消纯色 note: 高分辨图片对该接口性能影响较大 38 | /// \bug QGraphicsView使用OpenGL窗口时,第二个实例调用该函数会变成黑框 39 | void setIconColor(const QColor &color, qreal strength = 1.0); 40 | /// 设置文字 41 | void setText(const QString &text, Qt::Alignment align = Qt::AlignCenter); 42 | /// 设置文字颜色 43 | void setTextColor(const QColor &color); 44 | /// 设置是否允许鼠标事件,影响是否能够像QAbstractButton一样触发点击信号(但是可以收到press信号)以及切换选中状态 45 | void setAllowMouseEvent(bool enable); 46 | /// 设置鼠标可拖拽 47 | void setMoveable(bool movable); 48 | /// 设置选中性 49 | void setCheckable(bool checkable); 50 | /// 设置选中状态(不会触发信号) 51 | void setChecked(bool checked); 52 | /// 切换选中状态(不会触发信号) 53 | void toggle(); 54 | /// 是否选中 55 | bool isChecked() const; 56 | /// 绑定的航线信息 57 | void setRoute(MapRouteItem *route); 58 | MapRouteItem *route() const; 59 | /// 节点速度 note:支持航路节点速度存储 亦可指代当前节点初始速度 60 | void setSpeed(double speed); 61 | const double getSpeed() const; 62 | 63 | //MapTableItem * getMapTabel() const { return m_Suct; } 64 | MapSuctcheonItem * getMapTabel() const { return m_Suct; } 65 | public: 66 | /// 获取所有的实例 67 | static const QSet &items(); 68 | 69 | signals: 70 | void clicked(bool checked = false); 71 | void doubleClicked(); 72 | void pressed(); 73 | void released(); 74 | void toggled(bool checked); 75 | void coordinateChanged(const QGeoCoordinate &coord); 76 | void eulerChanged(const QVector3D &euler); 77 | void coordinateDragged(const QGeoCoordinate &coord); 78 | void rotationChanged(qreal degree); 79 | void routeChanged(MapRouteItem *route); 80 | void menuRequest(); 81 | protected: 82 | /// 获取rotation信号和移动信号 83 | virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; 84 | virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; 85 | virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; 86 | virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; 87 | virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; 88 | virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; 89 | virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; 90 | private: 91 | static QSet m_items; ///< 所有实例 92 | 93 | private: 94 | QGeoCoordinate m_coord; 95 | QVector3D m_euler; 96 | QGraphicsEllipseItem m_border; 97 | QGraphicsSimpleTextItem m_text; 98 | MapRouteItem *m_route = nullptr; 99 | // 100 | bool m_enableMouse = true; 101 | bool m_checkable = false; 102 | bool m_checked = false; 103 | // 104 | QPoint m_pressPos; 105 | // 节点速度 note:支持航路节点速度存储 亦可指代当前节点初始速度 106 | double m_speed; 107 | 108 | //MapTableItem *m_Suct = nullptr; //显示标牌对象 109 | MapSuctcheonItem *m_Suct = nullptr; 110 | }; 111 | 112 | #endif // MAPOBJECTITEM_H 113 | -------------------------------------------------------------------------------- /mapoperator.cpp: -------------------------------------------------------------------------------- 1 | #include "mapoperator.h" 2 | #include "mapellipseitem.h" 3 | #include "mappolygonitem.h" 4 | #include "maprouteitem.h" 5 | #include "mapobjectitem.h" 6 | #include "maprangeringitem.h" 7 | #include "maptrailitem.h" 8 | #include "maplineitem.h" 9 | #include "maprectitem.h" 10 | #include 11 | 12 | MapEllipseOperator::MapEllipseOperator(QObject *parent) : MapOperator(parent) 13 | { 14 | } 15 | 16 | void MapEllipseOperator::takeOver(MapEllipseItem *item) 17 | { 18 | m_ellipse = item; 19 | } 20 | 21 | void MapEllipseOperator::ready() 22 | { 23 | MapOperator::ready(); 24 | m_ellipse = nullptr; 25 | } 26 | 27 | void MapEllipseOperator::end() 28 | { 29 | MapOperator::end(); 30 | if(m_ellipse) 31 | m_ellipse->setEditable(false); 32 | m_ellipse = nullptr; 33 | } 34 | 35 | bool MapEllipseOperator::keyPressEvent(QKeyEvent *event) 36 | { 37 | if(!m_ellipse) 38 | return false; 39 | if(event->key() == Qt::Key_Backspace) { 40 | emit deleted(m_ellipse); 41 | m_ellipse = nullptr; 42 | } 43 | return false; 44 | } 45 | 46 | bool MapEllipseOperator::mouseDoubleClickEvent(QMouseEvent *event) 47 | { 48 | skipOnceMouseEvent(); 49 | // finish 50 | if(event->buttons() & Qt::LeftButton) { 51 | detach(); 52 | emit completed(); 53 | } 54 | return false; 55 | } 56 | 57 | bool MapEllipseOperator::mousePressEvent(QMouseEvent *event) 58 | { 59 | m_first = m_map->toCoordinate(event->pos()); 60 | // we should ignore event whatever we press at EditOnly mode 61 | if(mode() == EditOnly) { 62 | skipOnceMouseEvent(); 63 | return false; 64 | } 65 | // just create one 66 | else if(mode() == CreateOnly) { 67 | detach(); 68 | m_first = m_map->toCoordinate(event->pos()); 69 | return true; 70 | } 71 | // else, do creating operation 72 | else { //CreateEdit 73 | if(auto ctrlPoint = dynamic_cast(m_map->itemAt(event->pos()))) { 74 | auto cast = dynamic_cast(ctrlPoint->parentItem()); 75 | // we should ignore event if we pressed the control point 76 | if(cast && cast == m_ellipse) { 77 | skipOnceMouseEvent(); 78 | return false; 79 | } 80 | } 81 | detach(); 82 | return true; 83 | } 84 | return false; 85 | } 86 | 87 | bool MapEllipseOperator::mouseReleaseEvent(QMouseEvent *event) 88 | { 89 | if(!m_ellipse) 90 | return false; 91 | // Check that if the two point is too close 92 | auto point0 = m_map->toPoint(m_first); 93 | auto point1 = event->pos(); 94 | if((point0 - point1).manhattanLength() < 50) { 95 | point1 = point0 + QPoint(25, 25); 96 | } 97 | auto second = m_map->toCoordinate(point1); 98 | m_ellipse->setRect(m_first, second); 99 | return true; 100 | } 101 | 102 | bool MapEllipseOperator::mouseMoveEvent(QMouseEvent *event) 103 | { 104 | // lambda function to create ellipse 105 | auto createEllipse = [=]() { 106 | // unset editable for previous created item 107 | auto ellipse = m_map->addMapItem(); 108 | ellipse->setRect(m_first, m_first); 109 | ellipse->setEditable(true); 110 | // 111 | return ellipse; 112 | }; 113 | 114 | if(!m_ellipse) { 115 | m_ellipse = createEllipse(); 116 | emit created(m_ellipse); 117 | } 118 | 119 | auto second = m_map->toCoordinate(event->pos()); 120 | m_ellipse->setRect(m_first, second); 121 | // Press Event didn't propagte to QGraphicsView , 122 | // so we should to return false that helps up to zooming on cursor, 123 | // and map will not be moved by cursor move 124 | return false; 125 | } 126 | 127 | void MapEllipseOperator::detach() 128 | { 129 | if(m_ellipse) { 130 | m_ellipse->setEditable(false); 131 | m_ellipse = nullptr; 132 | } 133 | } 134 | 135 | MapPolygonOperator::MapPolygonOperator(QObject *parent) : MapOperator(parent) 136 | { 137 | 138 | } 139 | 140 | void MapPolygonOperator::takeOver(MapPolygonItem *item) 141 | { 142 | if(m_polygon) { 143 | m_polygon->setEditable(false); 144 | } 145 | m_polygon = item; 146 | } 147 | 148 | void MapPolygonOperator::ready() 149 | { 150 | MapOperator::ready(); 151 | m_polygon = nullptr; 152 | } 153 | 154 | void MapPolygonOperator::end() 155 | { 156 | MapOperator::end(); 157 | detach(); 158 | } 159 | 160 | bool MapPolygonOperator::keyPressEvent(QKeyEvent *event) 161 | { 162 | if(!m_polygon) 163 | return false; 164 | if(event->key() == Qt::Key_Backspace) { 165 | m_polygon->remove(m_polygon->points().size()-1); 166 | } 167 | return false; 168 | } 169 | 170 | bool MapPolygonOperator::mouseDoubleClickEvent(QMouseEvent *event) 171 | { 172 | Q_UNUSED(event) 173 | // the last point have been created since previous mouse release event 174 | if(m_polygon) { 175 | this->skipOnceMouseEvent(); 176 | detach(); 177 | emit completed(); 178 | return true; // prevent double Click propagate to item 179 | } 180 | return false; 181 | } 182 | 183 | bool MapPolygonOperator::mousePressEvent(QMouseEvent *event) 184 | { 185 | if(m_mode == EditOnly) { 186 | skipOnceMouseEvent(); 187 | return false; 188 | } 189 | m_pressPos = event->pos(); 190 | return false; 191 | } 192 | 193 | bool MapPolygonOperator::mouseReleaseEvent(QMouseEvent *event) 194 | { 195 | // do nothing 196 | if(m_pressPos != event->pos()) 197 | return false; 198 | // create begin or append 199 | if(!m_polygon) { 200 | m_polygon = new MapPolygonItem; 201 | m_polygon->setEditable(true); 202 | m_scene->addItem(m_polygon); 203 | // 204 | emit created(m_polygon); 205 | } 206 | m_polygon->append(m_map->toCoordinate(event->pos())); 207 | return false; 208 | } 209 | 210 | void MapPolygonOperator::detach() 211 | { 212 | if(m_polygon) 213 | m_polygon->setEditable(false); 214 | m_polygon = nullptr; 215 | } 216 | 217 | MapObjectOperator::MapObjectOperator(QObject *parent) : MapOperator(parent) 218 | { 219 | } 220 | 221 | void MapObjectOperator::takeOver(MapObjectItem *item) 222 | { 223 | detach(); 224 | m_obj = item; 225 | item->setCheckable(true); 226 | item->setChecked(true); 227 | item->setMoveable(true); 228 | } 229 | 230 | void MapObjectOperator::ready() 231 | { 232 | MapOperator::ready(); 233 | } 234 | 235 | void MapObjectOperator::end() 236 | { 237 | MapOperator::end(); 238 | detach(); 239 | } 240 | 241 | bool MapObjectOperator::keyPressEvent(QKeyEvent *event) 242 | { 243 | if(!m_obj) 244 | return false; 245 | if(event->key() == Qt::Key_Backspace) { 246 | emit deleted(m_obj); 247 | m_obj = nullptr; 248 | } 249 | return false; 250 | } 251 | 252 | bool MapObjectOperator::mousePressEvent(QMouseEvent *event) 253 | { 254 | if(this->mode() == EditOnly) { 255 | return false; 256 | } 257 | m_pressPos = event->pos(); 258 | return false; 259 | } 260 | 261 | bool MapObjectOperator::mouseReleaseEvent(QMouseEvent *event) 262 | { 263 | if(this->mode() == EditOnly) { 264 | return false; 265 | } 266 | // do nothing 267 | if(m_pressPos != event->pos()) 268 | return false; 269 | 270 | // create 271 | auto coord = m_map->toCoordinate(event->pos()); 272 | auto object = new MapObjectItem; 273 | m_scene->addItem(object); 274 | object->setZValue(1); 275 | object->setCoordinate(coord); 276 | // 277 | emit created(object); 278 | return false; 279 | } 280 | 281 | bool MapObjectOperator::mouseDoubleClickEvent(QMouseEvent *event) 282 | { 283 | // finish 284 | if(event->buttons() & Qt::LeftButton) { 285 | detach(); 286 | emit completed(); 287 | } 288 | return false; 289 | } 290 | 291 | void MapObjectOperator::detach() 292 | { 293 | if(m_obj) { 294 | m_obj->setCheckable(false); 295 | m_obj->setChecked(false); 296 | m_obj->setMoveable(false); 297 | } 298 | m_obj = nullptr; 299 | } 300 | 301 | MapRouteOperator::MapRouteOperator(QObject *parent) : MapOperator(parent) 302 | { 303 | } 304 | 305 | void MapRouteOperator::takeOver(MapRouteItem *item) 306 | { 307 | if(m_route) { 308 | m_route->setMoveable(false); 309 | m_route->setCheckable(false); 310 | } 311 | m_route = item; 312 | m_route->setMoveable(true); 313 | m_route->setCheckable(true); 314 | } 315 | 316 | void MapRouteOperator::setWaypointIcon(const QPixmap &pixmap) 317 | { 318 | m_waypointIcon = pixmap; 319 | } 320 | 321 | void MapRouteOperator::ready() 322 | { 323 | m_route = nullptr; 324 | } 325 | 326 | void MapRouteOperator::end() 327 | { 328 | if(m_route) { 329 | m_route->setMoveable(false); 330 | m_route->setCheckable(false); 331 | } 332 | m_route = nullptr; 333 | } 334 | 335 | bool MapRouteOperator::keyPressEvent(QKeyEvent *event) 336 | { 337 | if(!m_route) 338 | return false; 339 | if(event->key() == Qt::Key_Backspace) { 340 | auto points = m_route->checked(); 341 | for(auto point : qAsConst(points)) { 342 | m_route->remove(point); 343 | } 344 | } 345 | return false; 346 | } 347 | 348 | bool MapRouteOperator::mouseDoubleClickEvent(QMouseEvent *event) 349 | { 350 | Q_UNUSED(event) 351 | // the last point have been craeted since previous mouse release event 352 | // complete 353 | if(m_route) { 354 | m_route->setMoveable(false); 355 | m_route->setCheckable(false); 356 | } 357 | m_route = nullptr; 358 | emit completed(); 359 | return false; 360 | } 361 | 362 | bool MapRouteOperator::mousePressEvent(QMouseEvent *event) 363 | { 364 | // complete 365 | if(event->buttons() & Qt::RightButton) { 366 | if(m_route) { 367 | m_route->setMoveable(false); 368 | m_route->setCheckable(false); 369 | } 370 | m_route = nullptr; 371 | skipOnceMouseEvent(); 372 | emit completed(); 373 | return false; 374 | } 375 | m_pressPos = event->pos(); 376 | if(!m_route) 377 | return false; 378 | 379 | // check that if we clicked waypoint 380 | auto mouseItems = m_map->items(event->pos()); 381 | for(auto item : qAsConst(mouseItems)) { 382 | // find MapObjectItem child 383 | if(m_route->childItems().contains(item)) { 384 | skipOnceMouseEvent(); 385 | return false; // the waypoint itself will process setChecked 386 | } 387 | } 388 | return false; 389 | } 390 | 391 | bool MapRouteOperator::mouseReleaseEvent(QMouseEvent *event) 392 | { 393 | // do nothing 394 | if((m_pressPos-event->pos()).manhattanLength() > 3) 395 | return false; 396 | 397 | // create begin or append 398 | auto coord = m_map->toCoordinate(event->pos()); 399 | coord.setAltitude(0); 400 | if(!m_route) { // create route 401 | m_route = new MapRouteItem; 402 | m_scene->addItem(m_route); 403 | m_route->setMoveable(true); 404 | m_route->setExclusive(true); 405 | m_route->setCheckable(true); 406 | // 407 | emit created(m_route); 408 | } 409 | auto checked = m_route->checkedIndex(); 410 | auto index = checked.isEmpty() ? -1 : checked.last(); 411 | // append coordinate for route 412 | m_route->insert(index + 1, coord)->setIcon(m_waypointIcon); 413 | auto pointItem = m_route->points().at(index + 1); 414 | pointItem->setText(QString::number(index + 1)); //文本不对 415 | m_route->setChecked(index + 1); 416 | return false; 417 | } 418 | MapRangeLineOperator::MapRangeLineOperator(QObject *parent) : MapOperator(parent) 419 | { 420 | } 421 | 422 | void MapRangeLineOperator::ready() 423 | { 424 | MapOperator::ready(); 425 | m_line = nullptr; 426 | } 427 | 428 | void MapRangeLineOperator::end() 429 | { 430 | MapOperator::end(); 431 | m_line = nullptr; 432 | } 433 | 434 | bool MapRangeLineOperator::keyPressEvent(QKeyEvent *event) 435 | { 436 | if(!m_line) 437 | return false; 438 | if(event->key() == Qt::Key_Backspace) { 439 | emit deleted(m_line); 440 | m_line = nullptr; 441 | } 442 | return false; 443 | } 444 | 445 | bool MapRangeLineOperator::mousePressEvent(QMouseEvent *event) 446 | { 447 | if (!(event->buttons() & Qt::LeftButton)) { 448 | skipOnceMouseEvent(); 449 | return false; 450 | } 451 | 452 | m_pressFirstPos = event->pos(); 453 | if (!m_line) { // create route 454 | m_line = new MapLineItem; 455 | m_scene->addItem(m_line); 456 | m_line->setStartPoint(m_map->toCoordinate(event->pos())); 457 | m_line->setStartIcon(QPixmap(":/Resources/location.png"), Qt::AlignHCenter | Qt::AlignTop); 458 | m_line->setEndIcon(QPixmap(":/Resources/location.png"), Qt::AlignHCenter | Qt::AlignTop); 459 | // 460 | emit created(m_line); 461 | return true; 462 | } 463 | return false; 464 | } 465 | 466 | bool MapRangeLineOperator::mouseReleaseEvent(QMouseEvent *event) 467 | { 468 | Q_UNUSED(event) 469 | if (m_line) { 470 | m_line = nullptr; 471 | return false; 472 | } 473 | return false; 474 | } 475 | 476 | bool MapRangeLineOperator::mouseMoveEvent(QMouseEvent *event) 477 | { 478 | auto second = m_map->toCoordinate(event->pos()); 479 | m_line->setEndPoint(second); 480 | double dis = m_line->endings().first.distanceTo(m_line->endings().second); 481 | if (dis > 1000.0) { 482 | dis = dis * 1E-3; 483 | QString str = QString::number(dis, 'f', 2) + QString("km"); 484 | m_line->setText(str); 485 | } 486 | else if (dis < 1000.0) { 487 | QString str = QString::number(dis,'f',2) + QString("m"); 488 | m_line->setText(str); 489 | } 490 | // Press Event didn't propagte to QGraphicsView , 491 | // so we should to return false that helps up to zooming on cursor, 492 | // and map will not be moved by cursor move 493 | return false; 494 | } 495 | 496 | MapRectOperator::MapRectOperator(QObject *parent) : MapOperator(parent) 497 | { 498 | } 499 | 500 | void MapRectOperator::takeOver(MapRectItem *item) 501 | { 502 | end(); 503 | m_rect = item; 504 | } 505 | 506 | void MapRectOperator::ready() 507 | { 508 | MapOperator::ready(); 509 | m_rect = nullptr; 510 | } 511 | 512 | void MapRectOperator::end() 513 | { 514 | MapOperator::end(); 515 | if(m_rect) 516 | m_rect->setEditable(false); 517 | m_rect = nullptr; 518 | } 519 | 520 | bool MapRectOperator::keyPressEvent(QKeyEvent *event) 521 | { 522 | if(!m_rect) 523 | return false; 524 | if(event->key() == Qt::Key_Backspace) { 525 | emit deleted(m_rect); 526 | m_rect = nullptr; 527 | } 528 | return false; 529 | } 530 | 531 | bool MapRectOperator::mousePressEvent(QMouseEvent *event) 532 | { 533 | if(mode() == EditOnly) { 534 | skipOnceMouseEvent(); 535 | return false; 536 | } 537 | // CreateOnly & CreateEdit 538 | // Ignore the event when click on the control point 539 | if(auto ctrlPoint = dynamic_cast(m_map->itemAt(event->pos()))) { 540 | if(dynamic_cast(ctrlPoint->parentItem()) == m_rect) { 541 | skipOnceMouseEvent(); 542 | return false; 543 | } 544 | } 545 | 546 | // unset editable for previous created item 547 | detach(); 548 | 549 | m_first = m_map->toCoordinate(event->pos()); 550 | return true; 551 | } 552 | 553 | bool MapRectOperator::mouseDoubleClickEvent(QMouseEvent *event) 554 | { 555 | // todo: ignore release event and move event because doubleClick will prepagate that event 556 | skipOnceMouseEvent(); 557 | 558 | // finish 559 | if(event->buttons() & Qt::LeftButton) { 560 | detach(); 561 | emit completed(); 562 | return false; 563 | } 564 | return false; 565 | } 566 | 567 | bool MapRectOperator::mouseReleaseEvent(QMouseEvent *event) 568 | { 569 | if(!m_rect) 570 | return false; 571 | // Check that if the two point is too close, we should delete such an rect 572 | auto point0 = m_map->toPoint(m_first); 573 | auto point1 = event->pos(); 574 | if((point0 - point1).manhattanLength() < 50) { 575 | point1 = point0 + QPoint(25, 25); 576 | } 577 | auto second = m_map->toCoordinate(point1); 578 | m_rect->setRect(m_first, second); 579 | 580 | return false; 581 | } 582 | 583 | bool MapRectOperator::mouseMoveEvent(QMouseEvent *event) 584 | { 585 | if(!m_rect) { 586 | m_rect = m_map->addMapItem(); 587 | m_rect->setEditable(true); 588 | emit created(m_rect); 589 | } 590 | auto second = m_map->toCoordinate(event->pos()); 591 | m_rect->setRect(m_first, second); 592 | // Press Event didn't propagte to QGraphicsView , 593 | // so we should to return false that helps up to zooming on cursor, 594 | // and map will not be moved by cursor move 595 | return false; 596 | } 597 | 598 | void MapRectOperator::detach() 599 | { 600 | if(m_rect) { 601 | m_rect->setEditable(false); 602 | m_rect = nullptr; 603 | } 604 | } 605 | 606 | MapScutcheonOperator::MapScutcheonOperator(QObject *parent) : MapOperator(parent) 607 | { 608 | m_Offset = QPoint(0, 0); 609 | m_bMousePress = false; 610 | } 611 | 612 | void MapScutcheonOperator::takeOver(MapTableItem *item) 613 | { 614 | end(); 615 | m_toolTip = item; 616 | } 617 | 618 | void MapScutcheonOperator::setIgnoreMouseEvent(bool bIgnore) 619 | { 620 | m_bIgnoreEvent = bIgnore; 621 | } 622 | 623 | void MapScutcheonOperator::setOffset(const QPoint &offset) 624 | { 625 | m_Offset = offset; 626 | emit changeOffset(); 627 | } 628 | 629 | void MapScutcheonOperator::ready() 630 | { 631 | MapOperator::ready(); 632 | m_toolTip = nullptr; 633 | } 634 | 635 | void MapScutcheonOperator::end() 636 | { 637 | MapOperator::end(); 638 | // if(m_toolTip) 639 | // m_toolTip->setEditable(false); 640 | m_toolTip = nullptr; 641 | } 642 | 643 | bool MapScutcheonOperator::keyPressEvent(QKeyEvent *event) 644 | { 645 | if(!m_toolTip) 646 | return false; 647 | if(event->key() == Qt::Key_Backspace) { 648 | //emit deleted(m_toolTip); 649 | m_toolTip = nullptr; 650 | } 651 | return false; 652 | } 653 | 654 | bool MapScutcheonOperator::mousePressEvent(QMouseEvent *event) 655 | { 656 | if(!m_bIgnoreEvent){ 657 | m_bMousePress = true; 658 | m_PressPosition = event->screenPos().toPoint(); 659 | return true; 660 | } 661 | return false; 662 | } 663 | 664 | bool MapScutcheonOperator::mouseMoveEvent(QMouseEvent *event) 665 | { 666 | if (!m_bIgnoreEvent && m_bMousePress) 667 | { 668 | auto mousePos = event->screenPos().toPoint(); 669 | QPoint moveSpan = mousePos - m_PressPosition; 670 | m_PressPosition = mousePos; 671 | m_Offset += moveSpan; 672 | emit changeOffset(); 673 | } 674 | return false; 675 | } 676 | 677 | bool MapScutcheonOperator::mouseReleaseEvent(QMouseEvent *event) 678 | { 679 | if(!m_bIgnoreEvent){ 680 | m_bMousePress = false; 681 | } 682 | return false; 683 | } 684 | 685 | bool MapScutcheonOperator::mouseDoubleClickEvent(QMouseEvent *event) 686 | { 687 | //skipOnceMouseEvent(); 688 | 689 | // finish 690 | if(event->buttons() & Qt::LeftButton) { 691 | //detach(); 692 | emit completed(); 693 | return false; 694 | } 695 | return false; 696 | } 697 | -------------------------------------------------------------------------------- /mapoperator.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPOPERATOR_H 2 | #define MAPOPERATOR_H 3 | 4 | #include "interactivemap.h" 5 | class MapOperator; 6 | class MapEllipseItem; 7 | class MapPolygonItem; 8 | class MapRouteItem; 9 | class MapObjectItem; 10 | class MapRangeRingItem; 11 | class MapTrailItem; 12 | class MapLineItem; 13 | class MapRectItem; 14 | class MapTableItem; 15 | 16 | /*! 17 | * \brief 圆形创建操作器 18 | * \details 左键双击完成创建 19 | */ 20 | class GRAPHICSMAPLIB_EXPORT MapEllipseOperator : public MapOperator 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | MapEllipseOperator(QObject *parent = nullptr); 26 | 27 | /// 接管已经创建的圆形(请确保被应用为操作器后再调用) 28 | void takeOver(MapEllipseItem *item); 29 | signals: 30 | void created(MapEllipseItem *item); 31 | void deleted(MapEllipseItem *item); 32 | protected: 33 | virtual void ready() override; 34 | virtual void end() override; 35 | virtual bool keyPressEvent(QKeyEvent *event) override; 36 | virtual bool mouseDoubleClickEvent(QMouseEvent *) override; 37 | virtual bool mousePressEvent(QMouseEvent *event) override; 38 | virtual bool mouseReleaseEvent(QMouseEvent *event) override; 39 | virtual bool mouseMoveEvent(QMouseEvent *event) override; 40 | 41 | private: 42 | void detach(); 43 | private: 44 | QGeoCoordinate m_first; 45 | MapEllipseItem *m_ellipse; 46 | }; 47 | 48 | /*! 49 | * \brief 多边形创建操作器 50 | * \details 左键双击结束编辑 51 | */ 52 | class GRAPHICSMAPLIB_EXPORT MapPolygonOperator : public MapOperator 53 | { 54 | Q_OBJECT 55 | 56 | public: 57 | MapPolygonOperator(QObject *parent = nullptr); 58 | 59 | /// 接管已经创建的多边行(请确保被应用为操作器后再调用) 60 | void takeOver(MapPolygonItem *item); 61 | signals: 62 | void created(MapPolygonItem *item); 63 | void deleted(MapPolygonItem *item); 64 | protected: 65 | virtual void ready() override; 66 | virtual void end() override; 67 | virtual bool keyPressEvent(QKeyEvent *event) override; 68 | virtual bool mouseDoubleClickEvent(QMouseEvent *event) override; 69 | virtual bool mousePressEvent(QMouseEvent *event) override; 70 | virtual bool mouseReleaseEvent(QMouseEvent *event) override; 71 | 72 | private: 73 | void detach(); 74 | private: 75 | QPoint m_pressPos; 76 | MapPolygonItem *m_polygon; 77 | }; 78 | 79 | /*! 80 | * \brief 图标对象创建操作器 81 | * \details 左键单击完成单图标对象的创建,右键双击结束编辑 82 | */ 83 | class GRAPHICSMAPLIB_EXPORT MapObjectOperator : public MapOperator 84 | { 85 | Q_OBJECT 86 | 87 | public: 88 | MapObjectOperator(QObject *parent = nullptr); 89 | 90 | /// 接管已经创建的对象 91 | void takeOver(MapObjectItem *item); 92 | 93 | signals: 94 | void created(MapObjectItem *item); 95 | void deleted(MapObjectItem *item); 96 | protected: 97 | virtual void ready() override; 98 | virtual void end() override; 99 | virtual bool keyPressEvent(QKeyEvent *event) override; 100 | virtual bool mousePressEvent(QMouseEvent *event) override; 101 | virtual bool mouseReleaseEvent(QMouseEvent *event) override; 102 | virtual bool mouseDoubleClickEvent(QMouseEvent *event) override; 103 | 104 | private: 105 | void detach(); 106 | 107 | private: 108 | QPoint m_pressPos; 109 | MapObjectItem *m_obj = nullptr; 110 | }; 111 | 112 | /*! 113 | * \brief 图标对象标签创建操作器 114 | * \details 115 | */ 116 | class GRAPHICSMAPLIB_EXPORT MapScutcheonOperator : public MapOperator 117 | { 118 | Q_OBJECT 119 | 120 | public: 121 | MapScutcheonOperator(QObject *parent = nullptr); 122 | 123 | /// 接管已经创建的对象 124 | void takeOver(MapTableItem *item); 125 | /// 设置是否忽略鼠标事件 126 | void setIgnoreMouseEvent(bool bIgnore); 127 | 128 | void setOffset(const QPoint &offset); 129 | 130 | QPoint GetOffset() const {return m_Offset; } 131 | signals: 132 | void changeOffset(); 133 | protected: 134 | virtual void ready() override; 135 | virtual void end() override; 136 | virtual bool keyPressEvent(QKeyEvent *event) override; 137 | virtual bool mouseMoveEvent(QMouseEvent * event) override; 138 | virtual bool mousePressEvent(QMouseEvent *event) override; 139 | virtual bool mouseReleaseEvent(QMouseEvent *event) override; 140 | virtual bool mouseDoubleClickEvent(QMouseEvent *event) override; 141 | private: 142 | bool m_bMousePress; 143 | QPoint m_PressPosition; 144 | QPoint m_Offset; 145 | MapTableItem *m_toolTip; 146 | bool m_bIgnoreEvent; // 鼠标忽略 147 | }; 148 | 149 | 150 | 151 | /*! 152 | * \brief 航路创建操作器 153 | * \details 双击和右键完成单航路的创建,多次点击实现新增航点 154 | */ 155 | class GRAPHICSMAPLIB_EXPORT MapRouteOperator : public MapOperator 156 | { 157 | Q_OBJECT 158 | 159 | public: 160 | MapRouteOperator(QObject *parent = nullptr); 161 | 162 | /// 接管已经创建的航路(请确保被应用为操作器后再调用) 163 | void takeOver(MapRouteItem *item); 164 | 165 | /// 设置航点的默认图标 166 | void setWaypointIcon(const QPixmap &pixmap); 167 | 168 | signals: 169 | ///< 创建信号 170 | void created(MapRouteItem *item); 171 | void deleted(MapRouteItem *item); 172 | protected: 173 | virtual void ready() override; 174 | virtual void end() override; 175 | virtual bool keyPressEvent(QKeyEvent *event) override; 176 | virtual bool mouseDoubleClickEvent(QMouseEvent *event) override; 177 | virtual bool mousePressEvent(QMouseEvent *event) override; 178 | virtual bool mouseReleaseEvent(QMouseEvent *event) override; 179 | 180 | private: 181 | QPoint m_pressPos; 182 | // 183 | MapRouteItem *m_route = nullptr; 184 | // 185 | QPixmap m_waypointIcon; 186 | }; 187 | 188 | 189 | /*! 190 | * \brief 测距线段操作器 191 | * \details 完成测距线段的创建及修改 192 | */ 193 | class GRAPHICSMAPLIB_EXPORT MapRangeLineOperator : public MapOperator 194 | { 195 | Q_OBJECT 196 | 197 | public: 198 | MapRangeLineOperator(QObject *parent = nullptr); 199 | 200 | signals: 201 | ///< 创建信号 202 | void created(MapLineItem *item); 203 | void deleted(MapLineItem *item); 204 | protected: 205 | virtual void ready() override; 206 | virtual void end() override; 207 | virtual bool keyPressEvent(QKeyEvent *event) override; 208 | virtual bool mousePressEvent(QMouseEvent *event) override; 209 | virtual bool mouseReleaseEvent(QMouseEvent *event) override; 210 | virtual bool mouseMoveEvent(QMouseEvent *event) override; 211 | 212 | private: 213 | QPoint m_pressEndPos; 214 | QPoint m_pressFirstPos; 215 | // 216 | MapLineItem *m_line; 217 | }; 218 | 219 | /*! 220 | * \brief 矩形创建操作器 221 | * \details 左键双击完成编辑 222 | */ 223 | class GRAPHICSMAPLIB_EXPORT MapRectOperator : public MapOperator 224 | { 225 | Q_OBJECT 226 | 227 | public: 228 | MapRectOperator(QObject *parent = nullptr); 229 | 230 | /// 接管已经创建的矩形(请确保被应用为操作器后再调用) 231 | void takeOver(MapRectItem *item); 232 | 233 | signals: 234 | void created(MapRectItem *item); 235 | void deleted(MapRectItem *item); 236 | protected: 237 | virtual void ready() override; 238 | virtual void end() override; 239 | virtual bool keyPressEvent(QKeyEvent *event) override; 240 | virtual bool mousePressEvent(QMouseEvent *event) override; 241 | virtual bool mouseDoubleClickEvent(QMouseEvent *event) override; 242 | virtual bool mouseReleaseEvent(QMouseEvent *event) override; 243 | virtual bool mouseMoveEvent(QMouseEvent *event) override; 244 | 245 | private: 246 | void detach(); 247 | 248 | private: 249 | bool m_ignoreEvent; 250 | QGeoCoordinate m_first; 251 | MapRectItem *m_rect; 252 | }; 253 | 254 | 255 | #endif // MAPOPERATOR_H 256 | -------------------------------------------------------------------------------- /mappieitem.cpp: -------------------------------------------------------------------------------- 1 | #include "mappieitem.h" 2 | #include "graphicsmap.h" 3 | #include "mapobjectitem.h" 4 | 5 | QSet MapPieItem::m_items; 6 | QSet MapTriTrapItem::m_items; 7 | 8 | MapPieItem::MapPieItem() : 9 | m_coord({0, 0, 0}), 10 | m_radius(10e3), 11 | m_azimuth(0), 12 | m_span(0) 13 | { 14 | // keep the outline width of 1-pixel when item scales 15 | auto pen = this->pen(); 16 | pen.setColor(Qt::lightGray); 17 | pen.setWidth(1); 18 | pen.setCosmetic(true); 19 | this->setPen(pen); 20 | setAngle(60); 21 | // 22 | m_items.insert(this); 23 | } 24 | 25 | MapPieItem::~MapPieItem() 26 | { 27 | m_items.remove(this); 28 | } 29 | 30 | void MapPieItem::setCoordinate(const QGeoCoordinate &coord) 31 | { 32 | if(m_coord == coord) 33 | return; 34 | m_coord = coord; 35 | // such will change Rect 36 | updatePie(); 37 | } 38 | 39 | void MapPieItem::setRadius(const qreal &meter) 40 | { 41 | if(m_radius == meter) 42 | return; 43 | m_radius = meter; 44 | // such will change Rect 45 | updatePie(); 46 | } 47 | 48 | void MapPieItem::setAzimuth(const qreal °ree) 49 | { 50 | if(m_azimuth == degree) 51 | return; 52 | m_azimuth = degree; 53 | // aizmuth will not change Rect 54 | auto startAngle = m_azimuth - m_span / 2; 55 | this->setStartAngle(startAngle * 16); 56 | this->setSpanAngle(m_span * 16); 57 | } 58 | 59 | void MapPieItem::setAngle(const qreal °ree) 60 | { 61 | if(m_span == degree) 62 | return; 63 | m_span = degree; 64 | // spanAngle will not change Rect 65 | auto startAngle = m_azimuth - m_span / 2; 66 | this->setStartAngle(startAngle * 16); 67 | this->setSpanAngle(m_span * 16); 68 | } 69 | 70 | void MapPieItem::updatePie() 71 | { 72 | auto up = m_coord.atDistanceAndAzimuth(m_radius, 0); 73 | auto right = m_coord.atDistanceAndAzimuth(m_radius, 90); 74 | auto upPoint = GraphicsMap::toScene(up); 75 | auto rightPoint = GraphicsMap::toScene(right); 76 | auto centerPoint = GraphicsMap::toScene(m_coord); 77 | // 78 | QPointF topLeft(2*centerPoint.rx() - rightPoint.rx(), upPoint.ry()); 79 | QPointF bottomRight(rightPoint.rx(), 2*centerPoint.ry() - upPoint.ry()); 80 | QRectF rect(topLeft, bottomRight); 81 | this->setRect(rect); 82 | } 83 | 84 | MapTriTrapItem::MapTriTrapItem(): 85 | m_coord({0, 0, 0}), 86 | m_near(10e3), 87 | m_far(20e3), 88 | m_azimuth(0), 89 | m_span(0), 90 | m_attachObj(nullptr), 91 | m_attachAzimuth(0) 92 | { 93 | // keep the outline width of 1-pixel when item scales 94 | auto pen = this->pen(); 95 | pen.setColor(Qt::NoPen); 96 | pen.setWidth(1); 97 | pen.setCosmetic(true); 98 | this->setPen(pen); 99 | // 100 | m_triangle.setParentItem(this); 101 | m_trapezoid.setParentItem(this); 102 | pen.setColor(Qt::lightGray); 103 | pen.setStyle(Qt::DotLine); 104 | m_triangle.setPen(pen); 105 | pen.setColor(Qt::red); 106 | pen.setStyle(Qt::SolidLine); 107 | m_trapezoid.setPen(pen); 108 | setAngle(60); 109 | // 110 | m_items.insert(this); 111 | } 112 | 113 | MapTriTrapItem::~MapTriTrapItem() 114 | { 115 | m_items.remove(this); 116 | } 117 | 118 | void MapTriTrapItem::setCoordinate(const QGeoCoordinate &coord) 119 | { 120 | if(m_coord == coord) 121 | return; 122 | m_coord = coord; 123 | // such will change Rect 124 | updateTrapezoid(); 125 | } 126 | 127 | void MapTriTrapItem::setNear(const qreal &meter) 128 | { 129 | if(m_near == meter) 130 | return; 131 | m_near = meter; 132 | // 133 | updateTrapezoid(); 134 | } 135 | 136 | void MapTriTrapItem::setAzimuth(const qreal °ree) 137 | { 138 | if(m_azimuth == degree) 139 | return; 140 | m_azimuth = degree; 141 | // 142 | updateTrapezoid(); 143 | } 144 | 145 | void MapTriTrapItem::setAngle(const qreal °ree) 146 | { 147 | if(m_span == degree) 148 | return; 149 | m_span = degree; 150 | // 151 | updateTrapezoid(); 152 | } 153 | 154 | QGraphicsPolygonItem *MapTriTrapItem::getTriangle() 155 | { 156 | return &m_triangle; 157 | } 158 | 159 | QGraphicsPolygonItem *MapTriTrapItem::getTrapezoid() 160 | { 161 | return &m_trapezoid; 162 | } 163 | 164 | void MapTriTrapItem::attach(MapObjectItem *obj) 165 | { 166 | if(m_attachObj) { 167 | disconnect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapTriTrapItem::setCoordinate); 168 | disconnect(m_attachObj, &MapObjectItem::rotationChanged, this, &MapTriTrapItem::on_attachRotationChanged); 169 | } 170 | if(obj) { 171 | m_attachObj = obj; 172 | connect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapTriTrapItem::setCoordinate); 173 | connect(m_attachObj, &MapObjectItem::rotationChanged, this, &MapTriTrapItem::on_attachRotationChanged); 174 | setCoordinate(m_attachObj->coordinate()); 175 | on_attachRotationChanged(m_attachObj->rotation()); 176 | } 177 | } 178 | 179 | void MapTriTrapItem::detach() 180 | { 181 | if(m_attachObj) { 182 | disconnect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapTriTrapItem::setCoordinate); 183 | disconnect(m_attachObj, &MapObjectItem::rotationChanged, this, &MapTriTrapItem::on_attachRotationChanged); 184 | m_attachAzimuth = 0; 185 | } 186 | m_attachObj = nullptr; 187 | } 188 | 189 | void MapTriTrapItem::updateTrapezoid() 190 | { 191 | auto span_2 = m_span / 2; 192 | auto beginAz = m_azimuth + m_attachAzimuth - span_2; 193 | auto endAz = m_azimuth + m_attachAzimuth + span_2; 194 | auto coord1 = m_coord.atDistanceAndAzimuth(m_near, beginAz); 195 | auto coord2 = m_coord.atDistanceAndAzimuth(m_near, endAz); 196 | auto coord3 = m_coord.atDistanceAndAzimuth(m_far, endAz); 197 | auto coord4 = m_coord.atDistanceAndAzimuth(m_far, beginAz); 198 | auto point0 = GraphicsMap::toScene(m_coord); 199 | auto point1 = GraphicsMap::toScene(coord1); 200 | auto point2 = GraphicsMap::toScene(coord2); 201 | auto point3 = GraphicsMap::toScene(coord3); 202 | auto point4 = GraphicsMap::toScene(coord4); 203 | { // Self 204 | QPolygonF polygon; 205 | polygon.append(point0); 206 | polygon.append(point3); 207 | polygon.append(point4); 208 | this->setPolygon(polygon); 209 | } 210 | { // Triangle 211 | QPolygonF polygon; 212 | polygon.append(point0); 213 | polygon.append(point1); 214 | polygon.append(point2); 215 | m_triangle.setPolygon(polygon); 216 | } 217 | { // Trapozoid 218 | QPolygonF polygon; 219 | polygon.append(point1); 220 | polygon.append(point2); 221 | polygon.append(point3); 222 | polygon.append(point4); 223 | m_trapezoid.setPolygon(polygon); 224 | } 225 | } 226 | 227 | void MapTriTrapItem::on_attachRotationChanged(const qreal °ree) 228 | { 229 | if(m_attachAzimuth == degree) 230 | return; 231 | m_attachAzimuth = degree; 232 | updateTrapezoid(); 233 | } 234 | -------------------------------------------------------------------------------- /mappieitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPPIEITEM_H 2 | #define MAPPIEITEM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class MapObjectItem; 10 | 11 | /*! 12 | * \brief 扇形 13 | * \details 可用于SAR、红外等效果的扇形区域 14 | */ 15 | class GRAPHICSMAPLIB_EXPORT MapPieItem : public QGraphicsEllipseItem 16 | { 17 | public: 18 | MapPieItem(); 19 | ~MapPieItem(); 20 | /// 设置位置 21 | void setCoordinate(const QGeoCoordinate &coord); 22 | /// 设置长度 23 | void setRadius(const qreal &meter); 24 | /// 设置朝向 25 | void setAzimuth(const qreal °ree); 26 | /// 设置张角 27 | void setAngle(const qreal °ree); 28 | 29 | public: 30 | /// 获取所有的实例 31 | static const QSet &items(); 32 | 33 | private: 34 | void updatePie(); 35 | 36 | private: 37 | static QSet m_items; ///< 所有实例 38 | 39 | private: 40 | QGeoCoordinate m_coord; ///< 位置 41 | qreal m_radius; ///< 长度 米 42 | qreal m_azimuth; ///< 朝向,以正北右偏为正 43 | qreal m_span; ///< 张角 度 44 | }; 45 | 46 | /*! 47 | * \brief 三角形梯形混合类扇形 48 | * \details 可用于SAR、红外等效果的威力区 49 | * \note 更改三角形和梯形外观,请通过getTriangle和getTrapezoid获取实例,然后更改画笔和画刷 50 | */ 51 | class GRAPHICSMAPLIB_EXPORT MapTriTrapItem : public QObject, public QGraphicsPolygonItem 52 | { 53 | Q_OBJECT 54 | public: 55 | MapTriTrapItem(); 56 | ~MapTriTrapItem(); 57 | /// 设置位置 58 | void setCoordinate(const QGeoCoordinate &coord); 59 | /// 设置近距长度 60 | void setNear(const qreal &meter); 61 | /// 设置远距长度 62 | void setFar(const qreal &meter); 63 | /// 设置朝向 64 | void setAzimuth(const qreal °ree); 65 | /// 设置张角 66 | void setAngle(const qreal °ree); 67 | /// 获取三角形 68 | QGraphicsPolygonItem *getTriangle(); 69 | /// 获取梯形 70 | QGraphicsPolygonItem *getTrapezoid(); 71 | /// 依附到地图对象,将会自动更新位置 72 | void attach(MapObjectItem *obj); 73 | /// 取消依附地图对象,后续手动更新位置 74 | void detach(); 75 | 76 | public: 77 | /// 获取所有的实例 78 | static const QSet &items(); 79 | 80 | private: 81 | void updateTrapezoid(); 82 | void on_attachRotationChanged(const qreal °ree); 83 | 84 | private: 85 | static QSet m_items; ///< 所有实例 86 | 87 | private: 88 | QGeoCoordinate m_coord; ///< 位置 89 | qreal m_near; ///< 近距 米 90 | qreal m_far; ///< 远距 米 91 | qreal m_azimuth; ///< 朝向,以正北右偏为正 92 | qreal m_span; ///< 张角 度 93 | // 94 | QGraphicsPolygonItem m_triangle; 95 | QGraphicsPolygonItem m_trapezoid; 96 | // 97 | MapObjectItem *m_attachObj; 98 | qreal m_attachAzimuth; 99 | }; 100 | #endif // MAPPIEITEM_H 101 | -------------------------------------------------------------------------------- /mappolygonitem.cpp: -------------------------------------------------------------------------------- 1 | #include "mappolygonitem.h" 2 | #include "graphicsmap.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | QSet MapPolygonItem::m_items; 9 | 10 | MapPolygonItem::MapPolygonItem() : 11 | m_editable(false), 12 | m_sceneAdded(false) 13 | { 14 | // keep the outline width of 1-pixel when item scales 15 | auto pen = this->pen(); 16 | pen.setWidth(1); 17 | pen.setCosmetic(true); 18 | this->setPen(pen); 19 | // 20 | m_items.insert(this); 21 | updateEditable(); 22 | } 23 | 24 | MapPolygonItem::~MapPolygonItem() 25 | { 26 | m_items.remove(this); 27 | qDeleteAll(m_ctrlPoints); 28 | m_ctrlPoints.clear(); 29 | } 30 | 31 | void MapPolygonItem::setEditable(bool editable) 32 | { 33 | if(m_editable == editable) 34 | return; 35 | 36 | m_editable = editable; 37 | emit editableChanged(editable); 38 | updateEditable(); 39 | } 40 | 41 | bool MapPolygonItem::isEditable() const 42 | { 43 | return m_editable; 44 | } 45 | 46 | void MapPolygonItem::toggleEditable() 47 | { 48 | setEditable(!m_editable); 49 | } 50 | 51 | void MapPolygonItem::append(const QGeoCoordinate &coord) 52 | { 53 | m_coords.append(coord); 54 | // Adding scene point 55 | auto point = GraphicsMap::toScene(coord); 56 | m_points.append(point); 57 | updatePolygon(); 58 | // 59 | emit added(m_points.size()-1, coord); 60 | } 61 | 62 | void MapPolygonItem::replace(const int &index, const QGeoCoordinate &coord) 63 | { 64 | if(m_coords.at(index) == coord) 65 | return; 66 | m_coords.replace(index, coord); 67 | // Modify scene point 68 | auto point = GraphicsMap::toScene(coord); 69 | m_points.replace(index, point); 70 | updatePolygon(); 71 | // 72 | emit updated(index, coord); 73 | } 74 | 75 | void MapPolygonItem::remove(int index) 76 | { 77 | if(index < 0 || index >= m_coords.size()) 78 | return; 79 | auto coord = m_coords.at(index); 80 | m_coords.removeAt(index); 81 | m_points.removeAt(index); 82 | // 83 | updatePolygon(); 84 | // 85 | emit removed(index, coord); 86 | } 87 | 88 | void MapPolygonItem::removeEnd() 89 | { 90 | remove(m_coords.size() - 1); 91 | } 92 | 93 | void MapPolygonItem::setPoints(const QVector &coords) 94 | { 95 | if(m_coords == coords) 96 | return; 97 | 98 | // Change previous coords and points 99 | m_coords = coords; 100 | m_points.clear(); 101 | for(auto &coord : coords) { 102 | auto point = GraphicsMap::toScene(coord); 103 | m_points.append(point); 104 | } 105 | updatePolygon(); 106 | // 107 | emit changed(); 108 | } 109 | 110 | const QVector &MapPolygonItem::points() const 111 | { 112 | return m_coords; 113 | } 114 | 115 | int MapPolygonItem::count() 116 | { 117 | return m_coords.size(); 118 | } 119 | 120 | const QGeoCoordinate &MapPolygonItem::at(int i) const 121 | { 122 | return m_coords.at(i); 123 | } 124 | 125 | const QSet &MapPolygonItem::items() 126 | { 127 | return m_items; 128 | } 129 | 130 | /// the function will take advantage of those Ctrl-Points's Position property, 131 | /// also, the function will determine the outlook of those Ctrl-Points 132 | bool MapPolygonItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event) 133 | { 134 | if(!m_editable) 135 | return false; 136 | auto ctrlPoint = dynamic_cast(watched); 137 | switch (event->type()) { 138 | case QEvent::GraphicsSceneMouseMove: 139 | case QEvent::GraphicsSceneMouseRelease: 140 | { 141 | // get the index which respond to polygon edge point 142 | int index = m_ctrlPoints.indexOf(ctrlPoint); 143 | // actully, the finally event which release event will adjust the correct position 144 | m_points.replace(index, watched->pos()); 145 | this->setPolygon(m_points); 146 | // it's required to updadte the scene transform result 147 | auto scenePos = watched->pos(); 148 | auto coord = GraphicsMap::toCoordinate(scenePos); 149 | m_coords.replace(index, coord); 150 | // 151 | emit updated(index, coord); 152 | break; 153 | } 154 | case QEvent::GraphicsSceneHoverEnter: 155 | { 156 | ctrlPoint->setBrush(Qt::white); 157 | ctrlPoint->setScale(1.2); 158 | break; 159 | } 160 | case QEvent::GraphicsSceneHoverLeave: 161 | { 162 | ctrlPoint->setBrush(Qt::lightGray); 163 | ctrlPoint->setScale(1); 164 | break; 165 | } 166 | default: 167 | break; 168 | } 169 | 170 | return false; 171 | } 172 | 173 | QVariant MapPolygonItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) 174 | { 175 | if(change != ItemSceneHasChanged) 176 | return QGraphicsPolygonItem::itemChange(change, value); 177 | 178 | m_sceneAdded = true; 179 | for(auto ctrlPoint : qAsConst(m_ctrlPoints)) { 180 | // control point's move event help us to move polygon point 181 | ctrlPoint->installSceneEventFilter(this); 182 | } 183 | return QGraphicsPolygonItem::itemChange(change, value); 184 | } 185 | 186 | void MapPolygonItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) 187 | { 188 | QGraphicsPolygonItem::mouseDoubleClickEvent(event); 189 | emit doubleClicked(); 190 | } 191 | 192 | void MapPolygonItem::updatePolygon() 193 | { 194 | // Reset polygon data to QGraphicsPolygonItem 195 | this->setPolygon(m_points); 196 | 197 | // Create new control point or Remove unused control point 198 | if(m_ctrlPoints.size() < m_points.size()) { // Create 199 | for(int i = m_ctrlPoints.size(); i < m_points.size(); ++i) { 200 | auto ctrlPoint = new QGraphicsEllipseItem; 201 | ctrlPoint->setParentItem(this); 202 | // 203 | ctrlPoint->setAcceptHoverEvents(true); 204 | ctrlPoint->setPen(QPen(Qt::gray)); 205 | ctrlPoint->setBrush(Qt::lightGray); 206 | ctrlPoint->setCursor(Qt::DragMoveCursor); 207 | ctrlPoint->setFlag(QGraphicsItem::ItemIgnoresTransformations); 208 | ctrlPoint->setFlag(QGraphicsItem::ItemIsMovable); 209 | ctrlPoint->setRect(-4, -4, 8, 8); 210 | 211 | // control point's move event help us to move polygon point 212 | if(m_sceneAdded) 213 | ctrlPoint->installSceneEventFilter(this); 214 | 215 | // 216 | m_ctrlPoints.append(ctrlPoint); 217 | } 218 | } 219 | else if(m_points.size() < m_ctrlPoints.size()) { // Create 220 | for(int i = m_points.size(); i < m_ctrlPoints.size(); ++i) { 221 | delete m_ctrlPoints.takeLast(); 222 | } 223 | } 224 | 225 | // Move Control Point to GeoCoordinate's postion 226 | for(int i = 0; i < m_points.size(); ++i) { 227 | m_ctrlPoints.at(i)->setPos(m_points.at(i)); 228 | } 229 | // 230 | updateEditable(); 231 | } 232 | 233 | void MapPolygonItem::updateEditable() 234 | { 235 | auto pen = this->pen(); 236 | pen.setColor(m_editable ? Qt::white : Qt::lightGray); 237 | this->setPen(pen); 238 | 239 | for(auto &ctrlPoint : m_ctrlPoints) { 240 | ctrlPoint->setVisible(m_editable); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /mappolygonitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPPOLYGONITEM_H 2 | #define MAPPOLYGONITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /*! 11 | * \brief 多边形 12 | * \note 暂缺少图形拖动的实现 13 | */ 14 | class GRAPHICSMAPLIB_EXPORT MapPolygonItem : public QObject, public QGraphicsPolygonItem 15 | { 16 | Q_OBJECT 17 | public: 18 | MapPolygonItem(); 19 | ~MapPolygonItem(); 20 | /// 控制可编辑性 21 | void setEditable(bool editable); 22 | bool isEditable() const; 23 | void toggleEditable(); 24 | /// 添加经纬点 25 | void append(const QGeoCoordinate &coord); 26 | /// 修改经纬点 27 | void replace(const int &index, const QGeoCoordinate &coord); 28 | /// 删除经纬点 29 | void remove(int index); 30 | void removeEnd(); 31 | /// 设置多边形顶点 32 | void setPoints(const QVector &coords); 33 | /// 获取多边形顶点 34 | const QVector &points() const; 35 | int count(); 36 | /// 获取某个点的位置 37 | const QGeoCoordinate &at(int i) const; 38 | 39 | public: 40 | /// 获取所有的实例 41 | static const QSet &items(); 42 | 43 | signals: 44 | void added(const int index, const QGeoCoordinate &coord); 45 | void removed(const int index, const QGeoCoordinate &coord); 46 | void updated(const int &index, const QGeoCoordinate &coord); 47 | void changed(); 48 | void doubleClicked(); 49 | void editableChanged(bool editable); 50 | 51 | protected: 52 | virtual bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override; 53 | /// 被添加到场景后,为控制点添加事件过滤器 54 | virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; 55 | virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; 56 | 57 | private: 58 | void updatePolygon(); ///< 通过场景坐标更新图形 59 | void updateEditable(); 60 | 61 | private: 62 | static QSet m_items; ///< 所有实例 63 | 64 | private: 65 | bool m_editable; ///< 鼠标是否可交互编辑 66 | bool m_sceneAdded; ///< 是否已被添加到场景 67 | // 68 | QVector m_coords; ///< 经纬点列表 69 | QVector m_points; ///< 场景坐标点列表 70 | // 71 | QVector m_ctrlPoints; ///< 控制点(圆点) 72 | }; 73 | 74 | #endif // MAPPOLYGONITEM_H 75 | -------------------------------------------------------------------------------- /maprangeringitem.cpp: -------------------------------------------------------------------------------- 1 | #include "maprangeringitem.h" 2 | #include "graphicsmap.h" 3 | #include "mapobjectitem.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void qt_graphicsItem_highlightSelected(QGraphicsItem *item, QPainter *painter, const QStyleOptionGraphicsItem *option); 11 | 12 | QSet MapRangeRingItem::m_items; 13 | 14 | MapRangeRingItem::MapRangeRingItem() : 15 | m_cross(true), 16 | m_rotation(0), 17 | m_attachObj(nullptr) 18 | { 19 | m_pen.setWidth(2); 20 | m_pen.setCosmetic(true); 21 | m_pen.setColor(QColor::fromRgb(118, 215, 196)); 22 | m_font.setFamily("Microsoft YaHei"); 23 | m_font.setPointSizeF(10); 24 | setCoordinate({0, 0}); 25 | setRadius(60); 26 | // 27 | m_items.insert(this); 28 | } 29 | 30 | MapRangeRingItem::~MapRangeRingItem() 31 | { 32 | m_items.remove(this); 33 | } 34 | 35 | void MapRangeRingItem::setCrossVisible(bool visible) 36 | { 37 | m_cross = visible; 38 | } 39 | 40 | void MapRangeRingItem::setCoordinate(const QGeoCoordinate &coord) 41 | { 42 | if(m_coord == coord) 43 | return; 44 | m_coord = coord; 45 | updateBoundingRect(); 46 | setPos(GraphicsMap::toScene(m_coord)); 47 | } 48 | 49 | void MapRangeRingItem::setRotation(const qreal °ree) 50 | { 51 | if(m_rotation == degree) 52 | return; 53 | m_rotation = degree; 54 | // 保持椭圆和方位刻度不变,然后在paint函数里面更新十字型和距离文字的位置 55 | update(); 56 | } 57 | 58 | void MapRangeRingItem::setRadius(const float &km) 59 | { 60 | if(m_radius == km) 61 | return; 62 | m_radius = km; 63 | updateBoundingRect(); 64 | // 30km respond to 10 point size for font 65 | m_font.setPointSizeF(km / 30 * 10); 66 | } 67 | 68 | void MapRangeRingItem::attach(MapObjectItem *obj) 69 | { 70 | if(m_attachObj) { 71 | disconnect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapRangeRingItem::setCoordinate); 72 | disconnect(m_attachObj, &MapObjectItem::rotationChanged, this, &MapRangeRingItem::setRotation); 73 | } 74 | // 75 | if(obj) { 76 | m_attachObj = obj; 77 | connect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapRangeRingItem::setCoordinate); 78 | connect(m_attachObj, &MapObjectItem::rotationChanged, this, &MapRangeRingItem::setRotation); 79 | setCoordinate(m_attachObj->coordinate()); 80 | setRotation(m_attachObj->rotation()); 81 | } 82 | } 83 | 84 | void MapRangeRingItem::detach() 85 | { 86 | if(m_attachObj) { 87 | disconnect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapRangeRingItem::setCoordinate); 88 | disconnect(m_attachObj, &MapObjectItem::rotationChanged, this, &MapRangeRingItem::setRotation); 89 | } 90 | m_attachObj = nullptr; 91 | } 92 | 93 | void MapRangeRingItem::setPen(const QPen &pen) 94 | { 95 | if(m_pen == pen) 96 | return; 97 | m_pen = pen; 98 | updateBoundingRect(); 99 | } 100 | 101 | void MapRangeRingItem::setFont(const QFont &font) 102 | { 103 | if(m_font == font) 104 | return; 105 | m_font = font; 106 | update(); 107 | } 108 | 109 | QPen MapRangeRingItem::pen() const 110 | { 111 | return m_pen; 112 | } 113 | 114 | QFont MapRangeRingItem::font() const 115 | { 116 | return m_font; 117 | } 118 | 119 | const QSet &MapRangeRingItem::items() 120 | { 121 | return m_items; 122 | } 123 | 124 | QRectF MapRangeRingItem::boundingRect() const 125 | { 126 | return m_boundRect; 127 | } 128 | 129 | void MapRangeRingItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 130 | { 131 | Q_UNUSED(widget) 132 | 133 | // radius of ellpise 134 | auto ellpiseRadius = [&](const float &radius) { 135 | auto topCoord = m_coord.atDistanceAndAzimuth(radius * 1e3, 0); 136 | auto rightCoord = m_coord.atDistanceAndAzimuth(radius * 1e3, 90); 137 | auto top = GraphicsMap::toScene(topCoord); 138 | auto right = GraphicsMap::toScene(rightCoord); 139 | auto center = this->pos(); 140 | auto rx = right.rx() - center.rx(); 141 | auto ry = center.ry() - top.ry(); 142 | return QPointF(rx, ry); 143 | }; 144 | // 145 | painter->setPen(m_pen); 146 | painter->setFont(m_font); 147 | painter->setBrush(Qt::NoBrush); 148 | auto radius0 = ellpiseRadius(m_radius / 3); 149 | auto radius1 = ellpiseRadius(m_radius / 3 * 2); 150 | auto radius2 = ellpiseRadius(m_radius); 151 | /*--------------- fixed ratation ----------------*/ 152 | // 1.draw ellipse 153 | painter->drawEllipse({0, 0}, radius0.rx(), radius0.ry()); 154 | painter->drawEllipse({0, 0}, radius1.rx(), radius1.ry()); 155 | painter->drawEllipse({0, 0}, radius2.rx(), radius2.ry()); 156 | 157 | // 2.draw dial with azimuth 158 | painter->save(); 159 | QFontMetricsF fontMetrix(m_font); 160 | auto textRect = fontMetrix.boundingRect("N"); 161 | for(int i = 0; i < 12; ++i) { 162 | int az = i * 30; 163 | QString number; 164 | switch (az) { 165 | case 30: 166 | case 60: 167 | case 120: 168 | case 150: 169 | case 210: 170 | case 240: 171 | case 300: 172 | case 330: 173 | number = QString::number(az); 174 | break; 175 | case 0: 176 | number = "N"; 177 | break; 178 | case 90: 179 | number = "E"; 180 | break; 181 | case 180: 182 | number = "S"; 183 | break; 184 | case 270: 185 | number = "W"; 186 | break; 187 | } 188 | textRect = fontMetrix.boundingRect(number); 189 | painter->drawLine(0.0, -radius2.ry(), 0.0, -radius2.ry()*0.98); 190 | painter->drawText(-textRect.width()/2, -radius2.y() + textRect.height(), number); 191 | painter->rotate(30); 192 | } 193 | painter->restore(); 194 | 195 | /*--------------- mutable ratation ----------------*/ 196 | // 3.draw cross shape with a ligter color 197 | // Painter Begin 198 | painter->save(); 199 | painter->rotate(m_rotation); 200 | if(m_cross) { 201 | painter->save(); 202 | auto pen = painter->pen(); 203 | pen.setColor(pen.color().lighter()); 204 | pen.setStyle(Qt::DashLine); 205 | painter->setPen(pen); 206 | painter->drawLine(-radius2.rx(), 0, radius2.rx(), 0); 207 | painter->drawLine(0 ,-radius2.ry(), 0, radius2.ry()); 208 | painter->restore(); 209 | } 210 | auto pen = painter->pen(); 211 | pen.setColor(pen.color().lighter(130)); 212 | painter->setPen(pen); 213 | 214 | // 4.draw distance text 215 | painter->rotate(45); 216 | auto km = QString::number(m_radius/3)+"Km"; 217 | textRect = fontMetrix.boundingRect(km); 218 | painter->drawText(-textRect.width()/2, -radius0.y(), km); 219 | km = QString::number(m_radius/3*2)+"Km"; 220 | textRect = fontMetrix.boundingRect(km); 221 | painter->drawText(-textRect.width()/2, -radius1.y(), km); 222 | km = QString::number(m_radius)+"Km"; 223 | textRect = fontMetrix.boundingRect(km); 224 | painter->drawText(-textRect.width()/2, -radius2.y(), km); 225 | painter->rotate(-45); 226 | // Painter End 227 | painter->restore(); 228 | 229 | // copied from qt source (maybe not work for current) 230 | if (option->state & QStyle::State_Selected) 231 | qt_graphicsItem_highlightSelected(this, painter, option); 232 | } 233 | 234 | void MapRangeRingItem::updateBoundingRect() 235 | { 236 | prepareGeometryChange(); 237 | qreal halfpw = m_pen.style() == Qt::NoPen ? qreal(0) : m_pen.widthF() / 2; 238 | auto topCoord = m_coord.atDistanceAndAzimuth(m_radius * 1e3, 0); 239 | auto rightCoord = m_coord.atDistanceAndAzimuth(m_radius * 1e3, 90); 240 | auto top = GraphicsMap::toScene(topCoord); 241 | auto right = GraphicsMap::toScene(rightCoord); 242 | auto center = this->pos(); 243 | auto rx = right.rx() - center.rx(); 244 | auto ry = center.ry() - top.ry(); 245 | m_boundRect.adjust(-rx, -ry, rx, ry); 246 | m_boundRect.adjust(-halfpw, -halfpw, halfpw, halfpw); 247 | } 248 | 249 | void qt_graphicsItem_highlightSelected(QGraphicsItem *item, QPainter *painter, const QStyleOptionGraphicsItem *option) 250 | { 251 | const QRectF murect = painter->transform().mapRect(QRectF(0, 0, 1, 1)); 252 | if (qFuzzyIsNull(qMax(murect.width(), murect.height()))) 253 | return; 254 | 255 | const QRectF mbrect = painter->transform().mapRect(item->boundingRect()); 256 | if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0)) 257 | return; 258 | 259 | qreal itemPenWidth = 1; 260 | const qreal pad = itemPenWidth / 2; 261 | 262 | const qreal penWidth = 0; // cosmetic pen 263 | 264 | const QColor fgcolor = option->palette.windowText().color(); 265 | const QColor bgcolor( // ensure good contrast against fgcolor 266 | fgcolor.red() > 127 ? 0 : 255, 267 | fgcolor.green() > 127 ? 0 : 255, 268 | fgcolor.blue() > 127 ? 0 : 255); 269 | 270 | painter->setPen(QPen(bgcolor, penWidth, Qt::SolidLine)); 271 | painter->setBrush(Qt::NoBrush); 272 | painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); 273 | 274 | painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine)); 275 | painter->setBrush(Qt::NoBrush); 276 | painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad)); 277 | } 278 | -------------------------------------------------------------------------------- /maprangeringitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPRANGERINGITEM_H 2 | #define MAPRANGERINGITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class MapObjectItem; 12 | 13 | /*! 14 | * \brief 距离环 15 | * \details 显示三个地理等距椭圆环,如果需要实现屏幕恒等大小的效果,需要外部手动调用setRadius接口来实现 16 | * 该图形通常显示为椭圆,仅在水平方向和垂直方向的距离较为准确,其他角度通常都有误差,在低纬度上误差较小对结果影响不大 17 | * \warning 该元素对渲染效率影响较大,会占用过多CPU,暂未找到好的解决方案 18 | */ 19 | class GRAPHICSMAPLIB_EXPORT MapRangeRingItem : public QObject, public QGraphicsItem 20 | { 21 | Q_OBJECT 22 | public: 23 | explicit MapRangeRingItem(); 24 | ~MapRangeRingItem(); 25 | /// 设置是否显示十字架 26 | void setCrossVisible(bool visible); 27 | /// 设置位置 28 | void setCoordinate(const QGeoCoordinate &coord); 29 | /// 覆盖基类的同名函数,提供信号槽机制 30 | void setRotation(const qreal °ree); 31 | /// 设置半径 32 | void setRadius(const float &km); 33 | /// 依附到地图对象,将会自动更新位置 34 | void attach(MapObjectItem *obj); 35 | /// 取消依附地图对象,后续手动更新位置 36 | void detach(); 37 | /// 设置画笔 38 | void setPen(const QPen &pen); 39 | /// 设置字体 40 | void setFont(const QFont &font); 41 | /// 获取画笔 42 | QPen pen() const; 43 | /// 获取字体 44 | QFont font() const; 45 | 46 | public: 47 | /// 获取所有的实例 48 | static const QSet &items(); 49 | 50 | public: 51 | virtual QRectF boundingRect() const override; 52 | virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; 53 | 54 | private: 55 | void updateBoundingRect(); 56 | 57 | private: 58 | static QSet m_items; ///< 所有实例 59 | private: 60 | QPen m_pen; 61 | QFont m_font; 62 | bool m_cross; 63 | // 64 | QGeoCoordinate m_coord; ///< 位置 65 | float m_radius; ///< 半径 66 | qreal m_rotation; ///< 旋转 67 | // 68 | QRectF m_boundRect; 69 | // 70 | MapObjectItem *m_attachObj; 71 | }; 72 | 73 | #endif // MAPRANGERINGITEM_H 74 | -------------------------------------------------------------------------------- /maprectitem.cpp: -------------------------------------------------------------------------------- 1 | #include "maprectitem.h" 2 | #include "graphicsmap.h" 3 | #include 4 | #include 5 | #include 6 | 7 | QSet MapRectItem::m_items; 8 | 9 | MapRectItem::MapRectItem(): 10 | m_editable(false), 11 | m_center(0,0), 12 | m_size(1e3, 1e3) 13 | { 14 | // keep the outline width of 1-pixel when item scales 15 | auto pen = this->pen(); 16 | pen.setWidth(1); 17 | pen.setCosmetic(true); 18 | this->setPen(pen); 19 | // 20 | m_firstCtrl.setRect(-4, -4, 8, 8); 21 | m_secondCtrl.setRect(-4, -4, 8, 8); 22 | m_rectCtrl.setParentItem(this); 23 | m_firstCtrl.setParentItem(this); 24 | m_secondCtrl.setParentItem(this); 25 | // 26 | pen.setColor(Qt::lightGray); 27 | pen.setStyle(Qt::DashLine); 28 | m_rectCtrl.setPen(pen); 29 | m_firstCtrl.setAcceptHoverEvents(true); 30 | m_firstCtrl.setPen(QPen(Qt::gray)); 31 | m_firstCtrl.setBrush(Qt::lightGray); 32 | m_firstCtrl.setCursor(Qt::DragMoveCursor); 33 | m_firstCtrl.setFlag(QGraphicsItem::ItemIgnoresTransformations); 34 | m_firstCtrl.setFlag(QGraphicsItem::ItemIsMovable); 35 | m_firstCtrl.setCursor(Qt::DragMoveCursor); 36 | m_secondCtrl.setAcceptHoverEvents(true); 37 | m_secondCtrl.setPen(QPen(Qt::gray)); 38 | m_secondCtrl.setBrush(Qt::lightGray); 39 | m_secondCtrl.setCursor(Qt::DragMoveCursor); 40 | m_secondCtrl.setFlag(QGraphicsItem::ItemIgnoresTransformations); 41 | m_secondCtrl.setFlag(QGraphicsItem::ItemIsMovable); 42 | m_secondCtrl.setCursor(Qt::DragMoveCursor); 43 | // 44 | m_items.insert(this); 45 | updateEditable(); 46 | } 47 | 48 | MapRectItem::~MapRectItem() 49 | { 50 | m_items.remove(this); 51 | } 52 | 53 | void MapRectItem::setEditable(const bool &editable) 54 | { 55 | if(m_editable == editable) 56 | return; 57 | 58 | m_editable = editable; 59 | emit editableChanged(editable); 60 | updateEditable(); 61 | } 62 | 63 | bool MapRectItem::isEditable() const 64 | { 65 | return m_editable; 66 | } 67 | 68 | void MapRectItem::toggleEditable() 69 | { 70 | setEditable(!m_editable); 71 | } 72 | 73 | void MapRectItem::setCenter(const QGeoCoordinate ¢er) 74 | { 75 | if(center == m_center) 76 | return; 77 | m_center = center; 78 | // We should to compute topleft and botoom right coordinate to keep previous size unchanged 79 | auto leftCoord = m_center.atDistanceAndAzimuth(m_size.width()/2, -90); 80 | auto rightCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 90); 81 | auto topCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 0); 82 | auto bottomCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 180); 83 | m_topLeftCoord = {topCoord.latitude(), leftCoord.longitude()}; 84 | m_bottomRightCoord = {bottomCoord.latitude(), rightCoord.longitude()}; 85 | updateRect(); 86 | // 87 | emit centerChanged(center); 88 | } 89 | 90 | void MapRectItem::setSize(const QSizeF &size) 91 | { 92 | if(m_size == size) 93 | return; 94 | m_size = size; 95 | // We should to compute topleft and botoom right coordinate from new size 96 | auto leftCoord = m_center.atDistanceAndAzimuth(m_size.width()/2, -90); 97 | auto rightCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 90); 98 | auto topCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 0); 99 | auto bottomCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 180); 100 | m_topLeftCoord = {topCoord.latitude(), leftCoord.longitude()}; 101 | m_bottomRightCoord = {bottomCoord.latitude(), rightCoord.longitude()}; 102 | updateRect(); 103 | // 104 | emit sizeChanged(size); 105 | } 106 | 107 | void MapRectItem::setRect(const QGeoCoordinate &first, const QGeoCoordinate &second) 108 | { 109 | // compute the left right top bottom, which help up to compute the center and the width&height 110 | double left, right, top, bottom; 111 | if(first.longitude() <= second.longitude()) { 112 | left = first.longitude(); 113 | right = second.longitude(); 114 | } else { 115 | left = second.longitude(); 116 | right = first.longitude(); 117 | } 118 | if(first.latitude() <= second.latitude()) { 119 | bottom = first.latitude(); 120 | top = second.latitude(); 121 | } else { 122 | bottom = second.latitude(); 123 | top = first.latitude(); 124 | } 125 | // ignore setter requeset if shape and position has no differece 126 | const QGeoCoordinate tlCoord(top, left); 127 | const QGeoCoordinate brCoord(bottom, right); 128 | if(tlCoord == m_topLeftCoord && brCoord == m_bottomRightCoord) 129 | return; 130 | m_topLeftCoord = tlCoord; 131 | m_bottomRightCoord = brCoord; 132 | 133 | // compute new center and size 134 | m_center = {(top+right)/2, (left+right)/2}; 135 | auto width = QGeoCoordinate(m_center.latitude(), left).distanceTo(QGeoCoordinate(m_center.latitude(), right)); 136 | auto height = QGeoCoordinate(bottom, m_center.longitude()).distanceTo(QGeoCoordinate(top, m_center.longitude())); 137 | m_size = {width, height}; 138 | 139 | // rebuild rect shape 140 | updateRect(); 141 | 142 | // 143 | emit centerChanged(m_center); 144 | emit sizeChanged(m_size); 145 | } 146 | 147 | const QGeoCoordinate &MapRectItem::center() const 148 | { 149 | return m_center; 150 | } 151 | 152 | const QSizeF &MapRectItem::size() const 153 | { 154 | return m_size; 155 | } 156 | 157 | QVector MapRectItem::points() const 158 | { 159 | QVector points; 160 | auto leftCoord = m_center.atDistanceAndAzimuth(m_size.width()/2, -90); 161 | auto rightCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 90); 162 | auto topCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 0); 163 | auto bottomCoord = m_center.atDistanceAndAzimuth(m_size.height()/2, 180); 164 | points.append({topCoord.latitude(), leftCoord.longitude(), 0}); 165 | points.append({topCoord.latitude(), rightCoord.longitude(), 0}); 166 | points.append({bottomCoord.latitude(), rightCoord.longitude(), 0}); 167 | points.append({bottomCoord.latitude(), leftCoord.longitude(), 0}); 168 | return points; 169 | } 170 | 171 | const QSet &MapRectItem::items() 172 | { 173 | return m_items; 174 | } 175 | 176 | bool MapRectItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event) 177 | { 178 | if(!m_editable) 179 | return false; 180 | auto ctrlPoint = watched == &m_firstCtrl ? &m_firstCtrl : &m_secondCtrl; 181 | switch (event->type()) { 182 | case QEvent::GraphicsSceneMouseMove: 183 | case QEvent::GraphicsSceneMouseRelease: 184 | { 185 | // We should to compute center and size 186 | // and then update rect item rect 187 | auto firstCtrl = watched == &m_firstCtrl ? &m_firstCtrl : &m_secondCtrl; 188 | auto secondCtrl = firstCtrl == &m_firstCtrl ? &m_secondCtrl : &m_firstCtrl; 189 | // compute center 190 | auto centerPoint = (firstCtrl->pos() + secondCtrl->pos()) / 2; 191 | m_center = GraphicsMap::toCoordinate(centerPoint); 192 | // compute left right top bottom 193 | auto first = firstCtrl->pos(); 194 | auto second = secondCtrl->pos(); 195 | double left, right, top, bottom; 196 | if(first.x() <= second.x()) { 197 | left = first.x(); 198 | right = second.x(); 199 | } else { 200 | left = second.x(); 201 | right = first.x(); 202 | } 203 | if(first.y() <= second.y()) { 204 | bottom = first.y(); 205 | top = second.y(); 206 | } else { 207 | bottom = second.y(); 208 | top = first.y(); 209 | } 210 | 211 | QPointF leftPoint(left, centerPoint.y()); 212 | QPointF rightPoint(right, centerPoint.y()); 213 | QPointF topPoint(centerPoint.x(), top); 214 | QPointF bottomPoint(centerPoint.x(), bottom); 215 | m_size.setWidth(GraphicsMap::toCoordinate(leftPoint).distanceTo(GraphicsMap::toCoordinate(rightPoint))); 216 | m_size.setHeight(GraphicsMap::toCoordinate(topPoint).distanceTo(GraphicsMap::toCoordinate(bottomPoint))); 217 | auto topLeftPoint = QPointF(left, top); 218 | auto bottomRightPoint = QPointF(right, bottom); 219 | m_rectCtrl.setRect({topLeftPoint, bottomRightPoint}); 220 | QGraphicsRectItem::setRect({topLeftPoint, bottomRightPoint}); 221 | // 222 | emit centerChanged(m_center); 223 | emit sizeChanged(m_size); 224 | // compute 225 | break; 226 | } 227 | case QEvent::GraphicsSceneHoverEnter: 228 | { 229 | ctrlPoint->setBrush(Qt::white); 230 | ctrlPoint->setScale(1.2); 231 | break; 232 | } 233 | case QEvent::GraphicsSceneHoverLeave: 234 | { 235 | ctrlPoint->setBrush(Qt::lightGray); 236 | ctrlPoint->setScale(1); 237 | break; 238 | } 239 | default: 240 | break; 241 | } 242 | 243 | return false; 244 | } 245 | 246 | QVariant MapRectItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) 247 | { 248 | if(change != ItemSceneHasChanged) 249 | return QGraphicsRectItem::itemChange(change, value); 250 | 251 | m_firstCtrl.installSceneEventFilter(this); 252 | m_secondCtrl.installSceneEventFilter(this); 253 | return QGraphicsRectItem::itemChange(change, value); 254 | } 255 | 256 | void MapRectItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) 257 | { 258 | QGraphicsRectItem::mouseDoubleClickEvent(event); 259 | emit doubleClicked(); 260 | } 261 | 262 | /*! 263 | * \brief MapRectItem::updateRect 264 | * \details 更新矩形实际是根据左上和右下两个点进行重建,所以在其他Setter函数必须提前将两个计算好 265 | */ 266 | void MapRectItem::updateRect() 267 | { 268 | // compute topleft point and bottomright point in scene 269 | auto topLeftPoint = GraphicsMap::toScene(m_topLeftCoord); 270 | auto bottomRightPoint = GraphicsMap::toScene(m_bottomRightCoord); 271 | // update rect outlook 272 | 273 | QGraphicsRectItem::setRect({topLeftPoint, bottomRightPoint}); 274 | 275 | // update rect's contorl points 276 | m_firstCtrl.setPos(topLeftPoint); 277 | m_secondCtrl.setPos(bottomRightPoint); 278 | m_rectCtrl.setRect({topLeftPoint, bottomRightPoint}); 279 | } 280 | 281 | void MapRectItem::updateEditable() 282 | { 283 | auto pen = this->pen(); 284 | pen.setWidth(0); 285 | pen.setColor(m_editable ? Qt::white : Qt::lightGray); 286 | setPen(pen); 287 | 288 | m_rectCtrl.setVisible(m_editable); 289 | m_firstCtrl.setVisible(m_editable); 290 | m_secondCtrl.setVisible(m_editable); 291 | } 292 | -------------------------------------------------------------------------------- /maprectitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPRECTITEM_H 2 | #define MAPRECTITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | 8 | /*! 9 | * \brief 矩形 10 | * \note 暂缺少图形拖动的实现 11 | */ 12 | class GRAPHICSMAPLIB_EXPORT MapRectItem : public QObject, public QGraphicsRectItem 13 | { 14 | Q_OBJECT 15 | public: 16 | MapRectItem(); 17 | ~MapRectItem(); 18 | /// 控制可编辑性 19 | void setEditable(const bool &editable); 20 | bool isEditable() const; 21 | void toggleEditable(); 22 | /// 设置中心 23 | void setCenter(const QGeoCoordinate ¢er); 24 | /// 设置尺寸 米 25 | void setSize(const QSizeF &size); 26 | /// 设置包围矩形两个对角顶点来自动生成圆形 27 | void setRect(const QGeoCoordinate &first, const QGeoCoordinate &second); 28 | /// 获取中心 29 | const QGeoCoordinate ¢er() const; 30 | /// 获取尺寸 31 | const QSizeF &size() const; 32 | /// 获取四个点的坐标,顺时针方向,左上角为第一个点 33 | QVector points() const; 34 | 35 | public: 36 | /// 获取所有的实例 37 | static const QSet &items(); 38 | 39 | signals: 40 | void centerChanged(const QGeoCoordinate ¢er); 41 | void sizeChanged(const QSizeF ¢er); 42 | void doubleClicked(); 43 | void editableChanged(bool editable); 44 | 45 | protected: 46 | virtual bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override; 47 | /// 被添加到场景后,为控制点添加事件过滤器 48 | virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; 49 | virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; 50 | 51 | private: 52 | void updateRect(); 53 | void updateEditable(); 54 | 55 | private: 56 | static QSet m_items; ///< 所有实例 57 | 58 | private: 59 | bool m_editable; ///< 鼠标是否可交互编辑 60 | // 61 | QGeoCoordinate m_center; ///< 中心 62 | QSizeF m_size; ///< 宽高 63 | // 64 | QGeoCoordinate m_topLeftCoord; ///< 左上经纬度 65 | QGeoCoordinate m_bottomRightCoord; ///< 右下经纬度 66 | // 67 | QGraphicsRectItem m_rectCtrl; ///< 矩形 68 | QGraphicsEllipseItem m_firstCtrl; ///< 对角控制点1 69 | QGraphicsEllipseItem m_secondCtrl; ///< 对角控制点2 70 | }; 71 | 72 | #endif // MAPRECTITEM_H 73 | -------------------------------------------------------------------------------- /maprouteitem.cpp: -------------------------------------------------------------------------------- 1 | #include "maprouteitem.h" 2 | #include "graphicsmap.h" 3 | #include "mapobjectitem.h" 4 | 5 | QSet MapRouteItem::m_items; 6 | 7 | MapRouteItem::MapRouteItem() : 8 | m_moveable(false), 9 | m_checkable(false), 10 | m_exclusive(true) 11 | { 12 | // 13 | m_normalPen = this->pen(); 14 | m_normalPen.setWidth(2); 15 | m_normalPen.setCosmetic(true); // it will be always 2 pixmap whatever scale transform 16 | m_normalPen.setCapStyle(Qt::RoundCap); 17 | m_normalPen.setJoinStyle(Qt::RoundJoin); 18 | m_normalPen.setColor(QColor::fromRgb(241, 196, 15)); 19 | m_moveablePen = m_normalPen; 20 | m_moveablePen.setColor(m_normalPen.color().lighter(170)); 21 | this->setPen(m_normalPen); 22 | // 23 | m_items.insert(this); 24 | } 25 | 26 | MapRouteItem::~MapRouteItem() 27 | { 28 | m_items.remove(this); 29 | } 30 | 31 | void MapRouteItem::setMoveable(bool movable) 32 | { 33 | if(m_moveable == movable) 34 | return; 35 | m_moveable = movable; 36 | for(auto point : m_points) { 37 | point->setMoveable(m_moveable); 38 | } 39 | this->setPen(m_moveable ? m_moveablePen : m_normalPen); 40 | } 41 | 42 | void MapRouteItem::setCheckable(bool checkable) 43 | { 44 | // make sure call setChecked before operator= 45 | if(m_checkable == checkable) 46 | return; 47 | m_checkable = checkable; 48 | for(auto point : m_points) { 49 | point->setAllowMouseEvent(m_checkable); 50 | point->setCheckable(m_checkable); 51 | } 52 | } 53 | 54 | void MapRouteItem::setChecked(int index, bool checked) 55 | { 56 | if(!m_checkable) 57 | return; 58 | if(m_exclusive && checked) { 59 | for(auto point : m_points) { 60 | point->setChecked(false); 61 | } 62 | } 63 | m_points.at(index)->setChecked(checked); 64 | } 65 | 66 | void MapRouteItem::setChecked(MapObjectItem *point, bool checked) 67 | { 68 | auto index = m_points.indexOf(point); 69 | setChecked(index, checked); 70 | } 71 | 72 | void MapRouteItem::toggle(int index) 73 | { 74 | auto state = m_points.at(index)->isChecked(); 75 | setChecked(index, !state); 76 | } 77 | 78 | void MapRouteItem::toggle(MapObjectItem *point) 79 | { 80 | auto index = m_points.indexOf(point); 81 | toggle(index); 82 | } 83 | 84 | void MapRouteItem::setExclusive(bool exclusive) 85 | { 86 | if(m_exclusive == exclusive) 87 | return; 88 | m_exclusive = exclusive; 89 | if(m_exclusive) { 90 | for(auto point : m_points) { 91 | point->setChecked(false); 92 | } 93 | } 94 | } 95 | 96 | MapObjectItem* MapRouteItem::append(MapObjectItem *point) 97 | { 98 | bindPoint(point); 99 | m_points.append(point); 100 | // update point item pos 101 | // 102 | updatePolyline(); 103 | // 104 | emit added(m_points.size()-1, point); 105 | return point; 106 | } 107 | 108 | MapObjectItem *MapRouteItem::append(const QGeoCoordinate &coord) 109 | { 110 | auto point = new MapObjectItem(coord); 111 | append(point); 112 | return point; 113 | } 114 | 115 | MapObjectItem *MapRouteItem::insert(const int &index, MapObjectItem *point) 116 | { 117 | bindPoint(point); 118 | m_points.insert(index, point); 119 | // 120 | updatePolyline(); 121 | // 122 | emit added(index, point); 123 | return point; 124 | } 125 | 126 | MapObjectItem *MapRouteItem::insert(const int &index, const QGeoCoordinate &coord) 127 | { 128 | auto point = new MapObjectItem(coord); 129 | insert(index, point); 130 | return point; 131 | } 132 | 133 | MapObjectItem *MapRouteItem::replace(const int &index, MapObjectItem *point) 134 | { 135 | if(m_points.value(index) == point) 136 | return m_points.value(index); 137 | 138 | // 139 | delete m_points.value(index); 140 | bindPoint(point); 141 | m_points.replace(index, point); 142 | updatePolyline(); 143 | // 144 | emit updated(index, point); 145 | return point; 146 | } 147 | 148 | MapObjectItem *MapRouteItem::replace(const int &index, const QGeoCoordinate &coord) 149 | { 150 | auto point = new MapObjectItem(coord); 151 | replace(index, point); 152 | return point; 153 | } 154 | 155 | void MapRouteItem::remove(int index) 156 | { 157 | if(index <0 || index >= m_points.size()) 158 | return; 159 | 160 | delete m_points.takeAt(index); 161 | updatePolyline(); 162 | // 163 | emit removed(index); 164 | } 165 | 166 | void MapRouteItem::remove(MapObjectItem *point) 167 | { 168 | auto index = m_points.indexOf(point); 169 | remove(index); 170 | } 171 | 172 | const QVector &MapRouteItem::setPoints(const QVector &points) 173 | { 174 | if(points == m_points) 175 | return m_points; 176 | 177 | // delete previous 178 | qDeleteAll(m_points); 179 | m_points.clear(); 180 | 181 | // make up the newly 182 | m_points = points; 183 | updatePolyline(); 184 | // 185 | emit changed(); 186 | return m_points; 187 | } 188 | 189 | const QVector &MapRouteItem::points() const 190 | { 191 | return m_points; 192 | } 193 | 194 | QVector MapRouteItem::checked() const 195 | { 196 | QVector checked; 197 | for(auto point : m_points) { 198 | if(point->isChecked()) 199 | checked.append(point); 200 | } 201 | return checked; 202 | } 203 | 204 | QVector MapRouteItem::checkedIndex() const 205 | { 206 | QVector checked; 207 | for(int i = 0; i < m_points.size(); ++i) { 208 | auto point = m_points.at(i); 209 | if(!point->isChecked()) 210 | continue; 211 | checked.append(i); 212 | } 213 | return checked; 214 | } 215 | 216 | int MapRouteItem::indexOf(MapObjectItem *point) 217 | { 218 | return m_points.indexOf(point); 219 | } 220 | 221 | const QSet &MapRouteItem::items() 222 | { 223 | return m_items; 224 | } 225 | 226 | /// 更新QPainterPath的路径 227 | /// 更新从beginIndex和endIndex之间多个航点的文字 228 | void MapRouteItem::updatePolyline() 229 | { 230 | // update path 231 | if(m_points.isEmpty()) { 232 | setPath(QPainterPath()); 233 | return; 234 | } 235 | QPainterPath path(GraphicsMap::toScene(m_points.first()->coordinate())); 236 | for(int i = 1; i < m_points.size(); ++i) { 237 | path.lineTo(GraphicsMap::toScene(m_points.at(i)->coordinate())); 238 | } 239 | 240 | for(int nIndex = 0; nIndex < m_points.size(); ++nIndex) { 241 | m_points.at(nIndex)->setText(QString::number(nIndex)); 242 | } 243 | setPath(path); 244 | } 245 | 246 | void MapRouteItem::updatePointMoved() 247 | { 248 | auto ctrlItem = dynamic_cast(sender()); 249 | auto index = m_points.indexOf(ctrlItem); 250 | auto point = m_points.at(index); 251 | // update the polyline 252 | updatePolyline(); 253 | emit updated(index, point); 254 | } 255 | 256 | void MapRouteItem::updatePointPressed() 257 | { 258 | m_lastPointIsChecked = dynamic_cast(sender())->isChecked(); 259 | } 260 | 261 | void MapRouteItem::updatePointReleased() 262 | { 263 | auto point = dynamic_cast(sender()); 264 | bool isChecekd = point->isChecked(); 265 | if(m_lastPointIsChecked != isChecekd && isChecekd) 266 | setChecked(point, true); 267 | } 268 | 269 | void MapRouteItem::bindPoint(MapObjectItem *point) 270 | { 271 | point->setParentItem(this); 272 | point->setMoveable(m_moveable); 273 | point->setCheckable(m_checkable); 274 | point->setAllowMouseEvent(true); 275 | connect(point, &MapObjectItem::pressed, this, &MapRouteItem::updatePointPressed); 276 | connect(point, &MapObjectItem::released, this, &MapRouteItem::updatePointReleased); 277 | connect(point, &MapObjectItem::coordinateDragged, this, &MapRouteItem::updatePointMoved); 278 | } 279 | -------------------------------------------------------------------------------- /maprouteitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPROUTEITEM_H 2 | #define MAPROUTEITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | #include 8 | 9 | class MapObjectItem; 10 | 11 | /*! 12 | * \brief 航路 13 | * \note 航路类将负责航点的生命周期 14 | */ 15 | class GRAPHICSMAPLIB_EXPORT MapRouteItem : public QObject, public QGraphicsPathItem 16 | { 17 | Q_OBJECT 18 | public: 19 | using QGraphicsPathItem::setPen; 20 | using QGraphicsPathItem::pen; 21 | 22 | MapRouteItem(); 23 | ~MapRouteItem(); 24 | /// 设置鼠标可移动航点 25 | void setMoveable(bool movable); 26 | /// 设置航点可选中 27 | void setCheckable(bool checkable); 28 | /// 设置航点被选中 29 | void setChecked(int index, bool checked = true); 30 | void setChecked(MapObjectItem *point, bool checked = true); 31 | /// 切换航点选中状态 32 | void toggle(int index); 33 | void toggle(MapObjectItem *point); 34 | /// 设置航点选中互斥性 35 | void setExclusive(bool exclusive); 36 | /// 设置编辑状态和非编辑状态下两种画笔 37 | void setPen(bool editable, const QPen &pen); 38 | /// 获取画笔 39 | QPen pen(bool editable) const; 40 | /// 添加航点 41 | MapObjectItem *append(MapObjectItem *point); 42 | MapObjectItem *append(const QGeoCoordinate &coord); 43 | /// 插入航点 44 | MapObjectItem *insert(const int &index, MapObjectItem *point); 45 | MapObjectItem *insert(const int &index, const QGeoCoordinate &coord); 46 | /// 替换航点 47 | MapObjectItem *replace(const int &index, MapObjectItem *point); 48 | MapObjectItem *replace(const int &index, const QGeoCoordinate &coord); 49 | /// 删除航点 50 | void remove(int index); 51 | void remove(MapObjectItem *point); 52 | /// 设置航点 53 | const QVector &setPoints(const QVector &points); 54 | /// 获取航点列表 55 | const QVector &points() const; 56 | /// 获取所有选中的航点 57 | QVector checked() const; 58 | /// 获取所有选中的航点下标 59 | QVector checkedIndex() const; 60 | /// 获取下标 61 | int indexOf(MapObjectItem *point); 62 | 63 | public: 64 | /// 获取所有的实例 65 | static const QSet &items(); 66 | 67 | signals: 68 | void added(const int &index, const MapObjectItem *point); 69 | void removed(const int &index); 70 | void updated(const int &index, const MapObjectItem *point); 71 | void changed(); 72 | 73 | private: 74 | static QSet m_items; ///< 所有实例 75 | 76 | private: 77 | void updatePolyline(); 78 | void updatePointMoved(); 79 | void updatePointPressed(); 80 | void updatePointReleased(); 81 | void bindPoint(MapObjectItem *point); 82 | 83 | private: 84 | QPen m_normalPen; ///< 非编辑状态画笔 85 | QPen m_moveablePen; ///< 编辑状态画笔 86 | bool m_moveable; ///< 航点可移动 87 | bool m_checkable; ///< 航点可选中性 88 | bool m_exclusive; ///< 航点选中互斥性 89 | // 90 | QVector m_points; ///< 航点元素 91 | bool m_lastPointIsChecked; ///< 上次触发按钮的选中状态 92 | }; 93 | 94 | #endif // MAPROUTEITEM_H 95 | -------------------------------------------------------------------------------- /mapscutcheonitem.cpp: -------------------------------------------------------------------------------- 1 | #include "mapscutcheonitem.h" 2 | #include "graphicsmap.h" 3 | #include "mapobjectitem.h" 4 | #include "maptableitem.h" 5 | #include 6 | #include 7 | #include 8 | #define M_PI 3.14159265358979323846 9 | 10 | 11 | void MapSuctcheonItem::MapScutcheonLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 12 | { 13 | painter->save(); 14 | painter->setRenderHint(QPainter::Antialiasing, true); 15 | painter->setPen(pen()); 16 | painter->drawLine(line()); 17 | painter->restore(); 18 | } 19 | 20 | MapSuctcheonItem::MapSuctcheonItem(MapObjectItem *parent) 21 | :QGraphicsItem(parent) 22 | { 23 | m_JoinPen = QPen(QColor(128, 255, 255, 200)); 24 | m_JoinPen.setWidthF(1); 25 | m_pJoinLine = new MapScutcheonLine(this); 26 | if(m_pJoinLine){ 27 | m_pJoinLine->setFlag(QGraphicsItem::ItemIgnoresTransformations); 28 | m_pJoinLine->setPen(m_JoinPen); 29 | } 30 | 31 | m_pTablet = new MapTableItem({0, 0, 0}, m_pJoinLine); 32 | m_pTablet->setFixDirect(true, 45); 33 | setOffset(100, 45); 34 | 35 | connect(m_pTablet, &MapTableItem::coordinateDragged, this, &MapSuctcheonItem::onCoordinateDragged); 36 | 37 | m_border.setPen(QPen(Qt::lightGray)); 38 | m_border.setVisible(false); 39 | m_border.setParentItem(this); 40 | } 41 | 42 | MapSuctcheonItem::~MapSuctcheonItem() 43 | { 44 | 45 | } 46 | 47 | void MapSuctcheonItem::setMoveable(bool movable) 48 | { 49 | this->setFlag(QGraphicsItem::ItemIsMovable, movable); 50 | this->setAcceptHoverEvents(movable); 51 | } 52 | 53 | void MapSuctcheonItem::setCheckable(bool checkable) 54 | { 55 | // be sure that setChecked is called first 56 | // and then do operator= 57 | if(checkable == false) 58 | setChecked(false); 59 | m_checkable = checkable; 60 | } 61 | 62 | void MapSuctcheonItem::setChecked(bool checked) 63 | { 64 | if(!m_checkable) 65 | return; 66 | if(m_checked == checked) 67 | return; 68 | m_checked = checked; 69 | if(checked) { 70 | m_border.setRect(this->boundingRect()); 71 | m_border.setVisible(true); 72 | } 73 | else { 74 | m_border.setVisible(false); 75 | } 76 | } 77 | 78 | void MapSuctcheonItem::toggle() 79 | { 80 | setChecked(!m_checked); 81 | } 82 | 83 | bool MapSuctcheonItem::isChecked() const 84 | { 85 | return m_checked; 86 | } 87 | 88 | void MapSuctcheonItem::setMargins(int left, int top, int right, int bottom) 89 | { 90 | m_pTablet->setMargins(left, top, right, bottom); 91 | } 92 | 93 | void MapSuctcheonItem::setSpacing(int space) 94 | { 95 | m_pTablet->setSpacing(space); 96 | } 97 | 98 | void MapSuctcheonItem::setOffset(qreal joinLineLength, int fixedAngle) 99 | { 100 | QPointF endPos; 101 | auto sinAngle = sin(fixedAngle * M_PI / 180); 102 | auto cosAngle = cos(fixedAngle * M_PI / 180); 103 | 104 | endPos = {sinAngle * joinLineLength, -cosAngle * joinLineLength}; 105 | m_pJoinLine->setLine({QPointF(0 ,0), endPos}); 106 | m_pTablet->setPos(m_pJoinLine->line().p2()); 107 | } 108 | 109 | void MapSuctcheonItem::setOffset(QPointF endPos) 110 | { 111 | m_pJoinLine->setLine({QPointF(0 ,0), endPos}); 112 | } 113 | 114 | void MapSuctcheonItem::addField(const QString &field, bool bVolatile) 115 | { 116 | m_pTablet->addField(field, bVolatile); 117 | } 118 | 119 | void MapSuctcheonItem::inrFiled(int pos, const QString &field, bool bVolatile) 120 | { 121 | m_pTablet->inrField(pos, field, bVolatile); 122 | } 123 | 124 | void MapSuctcheonItem::delField(const QString &field) 125 | { 126 | m_pTablet->delField(field); 127 | } 128 | 129 | void MapSuctcheonItem::setFieldFont(const QString &field, const QFont &font) 130 | { 131 | m_pTablet->setFieldFont(field, font); 132 | } 133 | 134 | void MapSuctcheonItem::setValueFont(const QString &field, const QFont &font) 135 | { 136 | m_pTablet->setValueFont(field, font); 137 | } 138 | 139 | void MapSuctcheonItem::setFieldPen(const QString &field, const QPen &pen) 140 | { 141 | m_pTablet->setFieldPen(field, pen); 142 | } 143 | 144 | void MapSuctcheonItem::setValuePen(const QString &field, const QPen &pen) 145 | { 146 | m_pTablet->setValuePen(field, pen); 147 | } 148 | 149 | void MapSuctcheonItem::setValue(const QString &field, const QString &value) 150 | { 151 | m_pTablet->setValue(field, value); 152 | } 153 | 154 | void MapSuctcheonItem::setFixedDirect(bool bFiexdDirect, qreal fixedangle) 155 | { 156 | m_pTablet->setFixDirect(bFiexdDirect, fixedangle); 157 | } 158 | 159 | void MapSuctcheonItem::setBorderPen(const QPen &borderPen) 160 | { 161 | m_pTablet->setBorderPen(borderPen); 162 | } 163 | 164 | void MapSuctcheonItem::setBackBrush(const QBrush &brush) 165 | { 166 | m_pTablet->setBackBrush(brush); 167 | } 168 | 169 | void MapSuctcheonItem::setJoinPen(const QPen &joinPen) 170 | { 171 | m_JoinPen = joinPen; 172 | } 173 | 174 | void MapSuctcheonItem::updateTableSize() 175 | { 176 | m_pTablet->updateTableSize(); 177 | } 178 | 179 | QPainterPath MapSuctcheonItem::shape() const 180 | { 181 | auto startPos = m_pJoinLine->line().p1(); 182 | auto endPos = m_pJoinLine->line().p2(); 183 | QPainterPath path(startPos); 184 | path.lineTo(endPos); 185 | 186 | QRectF rect = m_pTablet->tabletRect(); 187 | QPointF topleft = rect.topLeft() + endPos; 188 | QPointF btmright = rect.bottomRight() + endPos; 189 | QRectF curRect(topleft, btmright); 190 | path.addRect(curRect); 191 | 192 | return path; 193 | } 194 | 195 | QRectF MapSuctcheonItem::boundingRect() const 196 | { 197 | auto line = m_pJoinLine->line(); 198 | auto table = m_pTablet->tabletRect(); 199 | 200 | auto width = abs(line.p2().x()) + table.width(); 201 | auto height = abs(line.p2().y()) + table.height(); 202 | return QRectF(-width, -height, width, height); 203 | } 204 | 205 | void MapSuctcheonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 206 | { 207 | if (m_pJoinLine && m_pTablet) 208 | { 209 | auto line = m_pJoinLine->line(); 210 | // qDebug()<parentItem(); 214 | // auto sceenPos = item->pos(); 215 | // auto viewPos = this->scene()->views().at(0)->mapFromScene(sceenPos); 216 | 217 | // QPoint sceenOffsetPos = viewPos + QPoint(line.x2(), line.y2()); 218 | // ((QGraphicsItem*)m_pTablet)->setPos(this->scene()->views().at(0)->mapToScene(sceenOffsetPos)); 219 | 220 | //m_pTablet->setPos(line.p2()); 221 | } 222 | } 223 | 224 | void MapSuctcheonItem::onCoordinateDragged(const QPointF& point) 225 | { 226 | if(m_pJoinLine){ 227 | // 俩种亦可 第一种拿当前俩个的场景坐标转到屏幕坐标算线段的差值 赋给线段长度 第一种需要关联到事件悬停 Item scale. 228 | // 第二种直接拿图表的位置赋给线段长度 229 | auto startPos = m_pJoinLine->line().p1(); 230 | /* first. Map element stretching is not considered here 231 | auto viewPos = this->scene()->views().at(0)->mapFromScene(point); 232 | auto lineViewPos = this->scene()->views().at(0)->mapFromScene(m_pJoinLine->scenePos()); 233 | m_pJoinLine->setLine({startPos, viewPos - lineViewPos}); 234 | */ 235 | 236 | auto tableTopLeft = m_pTablet->pos(); 237 | m_pJoinLine->setLine({startPos, tableTopLeft}); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /mapscutcheonitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPSUCTCHEONITEM_H 2 | #define MAPSUCTCHEONITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class MapObjectItem; 12 | class MapTableItem; 13 | 14 | class GRAPHICSMAPLIB_EXPORT MapSuctcheonItem : public QObject, public QGraphicsItem 15 | { 16 | /*line of Scutcheon.*/ 17 | class MapScutcheonLine :public QGraphicsLineItem 18 | { 19 | public: 20 | explicit MapScutcheonLine(QGraphicsItem *parent = Q_NULLPTR) 21 | : QGraphicsLineItem(parent){} 22 | ~MapScutcheonLine(){} 23 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) Q_DECL_OVERRIDE; 24 | }; 25 | Q_OBJECT 26 | public: 27 | /// 继承父对象将同步一切事件,但旋转会导致子对象一起旋转。 28 | MapSuctcheonItem(MapObjectItem * parent = nullptr); 29 | ~MapSuctcheonItem(); 30 | /// 设置鼠标可拖拽 31 | void setMoveable(bool movable); 32 | /// 设置选中性 33 | void setCheckable(bool checkable); 34 | /// 设置选中状态(不会触发信号) 35 | void setChecked(bool checked); 36 | /// 切换选中状态(不会触发信号) 37 | void toggle(); 38 | /// 是否选中 39 | bool isChecked() const; 40 | /// 设置边距和间距 41 | void setMargins(int left, int top, int right, int bottom); 42 | void setSpacing(int space); 43 | /// 设置连线图表的角度和长度 params: 这里长度为屏幕坐标,由于图表为ItemIgnoresTransformations 所以只需计算屏幕的终点位置 44 | void setOffset(qreal joinLineLength, int fixedAngle); 45 | void setOffset(QPointF endPos); 46 | /// 添加字段. params: 1.field 字段名. 2.bVolatile 标记字段值是否为易变性. 47 | /// 注:bVolatile=true表示字段值经常改变,此时会在paint中绘制. Volatile=false表示字段值比较固定,绘制更改时绘制一次,不在paint中实时绘制. 48 | void addField(const QString &field, bool bVolatile); 49 | void inrFiled(int pos, const QString &field, bool bVolatile); 50 | void delField(const QString &field); 51 | /// 设置字段名字体. params: 1.field 字段名. 2.font 字段名字体. 3.pen 字段名画笔 52 | void setFieldFont(const QString &field, const QFont &font); 53 | void setValueFont(const QString &field, const QFont &font); 54 | void setFieldPen(const QString &field, const QPen &pen); 55 | void setValuePen(const QString &field, const QPen &pen); 56 | /// 设置字段值. params: 1.field 字段名. 2.value 字段值. 57 | void setValue(const QString &field, const QString &value); 58 | /// 设置启用固定方向. 添加作为子项时,会跟随父项转动;此时若想固定方向,启用固定方向,并调用SetHeading将angle设置为父转动角度负值. 59 | /// params: 1.bFiexdDirect 标识是否启用固定方向. 2.fixedangle 固定角度值. 60 | void setFixedDirect(bool bFiexdDirect, qreal fixedangle = 0.0); 61 | /// 设置边框画笔. 62 | void setBorderPen(const QPen &borderPen); 63 | /// 设置背景画刷. 64 | void setBackBrush(const QBrush &brush); 65 | /// 设置连线画笔. 66 | void setJoinPen(const QPen &joinPen); 67 | /// 手动更新图表 68 | void updateTableSize(); 69 | 70 | MapTableItem * getTabel() { return m_pTablet; } 71 | protected: 72 | QPainterPath shape() const override; 73 | QRectF boundingRect() const override; 74 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 75 | signals: 76 | void pressed(); 77 | void released(); 78 | void coordinateDragged(const QGeoCoordinate &coord); 79 | void doubleClicked(); 80 | private: 81 | void onCoordinateDragged(const QPointF& point); 82 | private: 83 | QGraphicsRectItem m_border; /// 边框 84 | bool m_checkable = false; 85 | bool m_checked = false; 86 | bool m_bFixedDirect; /// 固定方向. 87 | QPen m_JoinPen; /// 连线画笔. 88 | MapScutcheonLine *m_pJoinLine; /// 连线对象. 89 | MapTableItem *m_pTablet; /// 标牌匾对象. 90 | }; 91 | 92 | 93 | #endif // MAPSUCTCHEONITEM_H 94 | -------------------------------------------------------------------------------- /maptableitem.cpp: -------------------------------------------------------------------------------- 1 | #include "maptableitem.h" 2 | #include "graphicsmap.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define M_PI 3.14159265358979323846 8 | 9 | 10 | MapTableItem::MapTableItem(const QGeoCoordinate &coord, QGraphicsItem * parent) 11 | :QGraphicsItem(parent) 12 | { 13 | m_FixedAngle = 0; 14 | memset(m_Margin, 0x00, sizeof(int)* _upper); 15 | 16 | m_nMaxWide = 0; 17 | m_nCurHigh = 0; 18 | m_BackWide = 0; 19 | m_RoundRadius = 0; 20 | m_bFixedDirect = 0; 21 | 22 | m_ScutSize = QSize(6.0, 6.0); 23 | 24 | m_BorderPen = QPen(QColor(128, 255, 255, 200)); 25 | m_BackBrush = QBrush(QColor(128, 255, 255, 50)); 26 | m_backPixmap = QPixmap(":/Resources/logo.png"); 27 | 28 | m_AnchorPos = AP_TOPLEFT; 29 | 30 | QPen pen(Qt::DotLine); 31 | pen.setColor(Qt::lightGray); 32 | m_border.setPen(pen); 33 | m_border.setVisible(false); 34 | m_border.setParentItem(this); 35 | 36 | setMargins(5, 5, 5, 5); 37 | setSpacing(5); 38 | 39 | this->setMoveable(true); 40 | this->setAllowMouseEvent(true); 41 | this->setCheckable(true); 42 | 43 | this->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); 44 | this->setPos(GraphicsMap::toScene(coord)); 45 | } 46 | 47 | MapTableItem::~MapTableItem() 48 | { 49 | 50 | } 51 | 52 | void MapTableItem::setAnchorPosition(AnchorPosition anchorPos) 53 | { 54 | m_AnchorPos = anchorPos; 55 | } 56 | 57 | void MapTableItem::setMargins(int left, int top, int right, int bottom) 58 | { 59 | m_Margin[_left] = left; 60 | m_Margin[_top] = top; 61 | m_Margin[_right] = right; 62 | m_Margin[_bottom] = bottom; 63 | } 64 | 65 | void MapTableItem::setSpacing(int space) 66 | { 67 | m_Margin[_space] = space; 68 | } 69 | 70 | void MapTableItem::addField(const QString &field, bool bVolatile) 71 | { 72 | if(m_Infoes.contains(field)){ 73 | m_Infoes[field]->bVolatile = bVolatile; 74 | return; 75 | } 76 | 77 | m_Fields.push_back(field); 78 | 79 | FieldInfo *info = new FieldInfo(bVolatile); 80 | m_Infoes.insert(field, info); 81 | } 82 | 83 | void MapTableItem::inrField(int pos, const QString &field, bool bVolatile) 84 | { 85 | if(pos >= m_Fields.count()) 86 | m_Fields.push_back(field); 87 | else 88 | m_Fields.insert(pos, field); 89 | 90 | FieldInfo *info = new FieldInfo(bVolatile); 91 | m_Infoes.insert(field, info); 92 | } 93 | 94 | void MapTableItem::delField(const QString &field) 95 | { 96 | m_Fields.takeAt(m_Fields.indexOf(field)); 97 | auto item = m_Infoes.find(field); 98 | m_Infoes.erase(item); 99 | } 100 | 101 | void MapTableItem::setFieldFont(const QString &field, const QFont &font) 102 | { 103 | if(m_Infoes.contains(field)){ 104 | m_Infoes[field]->fieldFont = font; 105 | } 106 | } 107 | 108 | void MapTableItem::setValueFont(const QString &field, const QFont &font) 109 | { 110 | if(m_Infoes.contains(field)){ 111 | m_Infoes[field]->valueFont = font; 112 | } 113 | } 114 | 115 | void MapTableItem::setFieldPen(const QString &field, const QPen &pen) 116 | { 117 | if(m_Infoes.contains(field)){ 118 | m_Infoes[field]->fieldPen = pen; 119 | } 120 | } 121 | 122 | void MapTableItem::setValuePen(const QString &field, const QPen &pen) 123 | { 124 | if(m_Infoes.contains(field)){ 125 | m_Infoes[field]->valuePen = pen; 126 | } 127 | } 128 | 129 | void MapTableItem::setValue(const QString &field, const QString &value) 130 | { 131 | if(m_Infoes.contains(field)){ 132 | m_Infoes[field]->value = value; 133 | } 134 | } 135 | 136 | void MapTableItem::setFixDirect(bool bFixDirect, double fixedangle) 137 | { 138 | m_bFixedDirect = bFixDirect; 139 | } 140 | 141 | void MapTableItem::setRoundedRadius(int roundRadius) 142 | { 143 | m_RoundRadius = roundRadius; 144 | } 145 | 146 | void MapTableItem::setBorderPen(const QPen &borderPen) 147 | { 148 | m_BorderPen = borderPen; 149 | } 150 | 151 | void MapTableItem::setBackBrush(const QBrush &brush) 152 | { 153 | m_BackBrush = brush; 154 | } 155 | 156 | void MapTableItem::setBackPixmap(const QPixmap &pixmap) 157 | { 158 | m_backPixmap = pixmap; 159 | } 160 | 161 | QRectF MapTableItem::tabletRect() 162 | { 163 | auto rect = boundingRect(); 164 | return rect; 165 | } 166 | 167 | QString MapTableItem::getValue(const QString &field) 168 | { 169 | if(m_Infoes.contains(field)){ 170 | return m_Infoes[field]->value; 171 | } 172 | return QString(); 173 | } 174 | 175 | void MapTableItem::updateTableSize() 176 | { 177 | UpdateInfo(); 178 | } 179 | 180 | void MapTableItem::setAllowMouseEvent(bool enable) 181 | { 182 | m_enableMouse = enable; 183 | } 184 | 185 | void MapTableItem::setMoveable(bool movable) 186 | { 187 | this->setFlag(QGraphicsItem::ItemIsMovable, movable); 188 | this->setAcceptHoverEvents(movable); 189 | } 190 | 191 | void MapTableItem::setCheckable(bool checkable) 192 | { 193 | if(checkable == false) 194 | setChecked(false); 195 | m_checkable = checkable; 196 | } 197 | 198 | void MapTableItem::setChecked(bool checked) 199 | { 200 | if(!m_checkable) 201 | return; 202 | if(m_checked == checked) 203 | return; 204 | m_checked = checked; 205 | if(checked) { 206 | auto rect = this->boundingRect(); 207 | auto leftTop = rect.topLeft(); 208 | auto rightBottom = rect.bottomRight(); 209 | 210 | leftTop += QPoint(-5, -5); 211 | rightBottom += QPoint(5, 5); 212 | m_border.setRect(QRectF(leftTop, rightBottom)); 213 | m_border.setVisible(true); 214 | } 215 | else { 216 | m_border.setVisible(false); 217 | } 218 | } 219 | 220 | void MapTableItem::toggle() 221 | { 222 | setChecked(!m_checked); 223 | } 224 | 225 | bool MapTableItem::isChecked() const 226 | { 227 | return m_checked; 228 | } 229 | 230 | void MapTableItem::UpdateInfo() 231 | { 232 | int nWide = m_Margin[_left] + m_Margin[_right] + m_Margin[_space]; 233 | int nHigh = m_Margin[_top] + m_Margin[_bottom] + m_Margin[_space] * (m_Fields.size() - 1); 234 | 235 | static int maxFieldWidth = 0; 236 | static int maxValueWidth = 0; 237 | 238 | for (auto &field : qAsConst(m_Fields)) 239 | { 240 | auto curFieldFont = m_Infoes[field]->fieldFont; 241 | auto curValueFont = m_Infoes[field]->valueFont; 242 | auto curValue = m_Infoes[field]->value; 243 | 244 | QFontMetrics fieldMet(curFieldFont); 245 | QFontMetrics valueMet(curValueFont); 246 | 247 | QRect fieldRect = fieldMet.boundingRect(field); 248 | QRect valueRect = valueMet.boundingRect(curValue); 249 | 250 | int nFieldWide = fieldRect.width(); 251 | int nValueWide = valueRect.width(); 252 | int nFieldHigh = fieldRect.height(); 253 | int nValueHigh = valueRect.height(); 254 | 255 | maxFieldWidth = maxFieldWidth > nFieldWide ? maxFieldWidth : nFieldWide; 256 | maxValueWidth = maxValueWidth > nValueWide ? maxValueWidth : nValueWide; 257 | 258 | nHigh += nFieldHigh > nValueHigh ? nFieldHigh : nValueHigh; 259 | m_Margin[_maxField] = m_Margin[_maxField] > nFieldWide ? m_Margin[_maxField] : nFieldWide; 260 | } 261 | m_nMaxWide = maxFieldWidth + maxValueWidth; 262 | m_ScutSize.setWidth(nWide + m_nMaxWide); 263 | m_ScutSize.setHeight(nHigh); 264 | } 265 | 266 | QRectF MapTableItem::boundingRect() const 267 | { 268 | int nScutWide = m_ScutSize.width(); 269 | int nScutHigh = m_ScutSize.height(); 270 | QRectF rect(QPointF(0.0, 0.0), m_ScutSize); 271 | switch (m_AnchorPos) 272 | { 273 | case AP_TOPLEFT: break; 274 | case AP_TOPRIGHT: rect.setTopLeft(QPointF(-nScutWide, 0.0)); break; 275 | case AP_BOTTOMLEFT: rect.setTopLeft(QPointF(0.0, -nScutHigh)); break; 276 | case AP_BOTTOMRIGHT:rect.setTopLeft(QPointF(-nScutWide, -nScutHigh));break; 277 | case AP_MIDDLELEFT: rect.setTopLeft(QPointF(0.0, -nScutHigh / 2)); break; 278 | case AP_MIDDLERIGHT:rect.setTopLeft(QPointF(-nScutWide , -nScutHigh / 2)); break; 279 | case AP_MIDDLETOP: rect.setTopLeft(QPointF(-nScutWide / 2, 0.0)); break; 280 | case AP_MIDDLEBTTOM:rect.setTopLeft(QPointF(-nScutWide / 2, -nScutHigh ));break; 281 | case AP_CENTER: rect.setTopLeft(QPointF(-nScutWide / 2, -nScutHigh / 2));break; 282 | default: break; 283 | } 284 | 285 | return rect; 286 | } 287 | 288 | QPainterPath MapTableItem::shape() const 289 | { 290 | QPainterPath path; 291 | QRectF rect = boundingRect(); 292 | QPointF topleft = rect.topLeft(); 293 | QPointF btmright = rect.bottomRight(); 294 | QRectF curRect(topleft, btmright); 295 | path.addRect(curRect); 296 | 297 | return path; 298 | } 299 | 300 | void MapTableItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 301 | { 302 | QRectF rect = boundingRect(); 303 | QPointF otopleft = rect.topLeft(); 304 | QPointF obtmrigt = rect.bottomRight(); 305 | QRectF curRect(otopleft, obtmrigt); 306 | 307 | QPointF topleft = curRect.topLeft(); 308 | QPointF topright = curRect.topRight(); 309 | QPointF btmleft = curRect.bottomLeft(); 310 | QPointF btmright = curRect.bottomRight(); 311 | 312 | painter->save(); 313 | painter->rotate(rotation()); 314 | 315 | painter->drawImage(rect, m_backPixmap.toImage()); 316 | switch (m_AnchorPos) 317 | { 318 | case AP_TOPLEFT: 319 | case AP_TOPRIGHT: 320 | case AP_BOTTOMLEFT: 321 | case AP_BOTTOMRIGHT: 322 | case AP_CENTER: 323 | { 324 | QPen curPen(m_BorderPen); 325 | curPen.setWidthF(m_BorderPen.widthF()); 326 | painter->setPen(curPen); 327 | painter->setBrush(m_BackBrush); 328 | painter->drawRect(curRect); 329 | break; 330 | } 331 | case AP_MIDDLELEFT: 332 | { 333 | painter->setPen(Qt::NoPen); 334 | painter->setBrush(m_BackBrush); 335 | 336 | QPen curPen(m_BorderPen); 337 | curPen.setWidthF(m_BorderPen.widthF()); 338 | 339 | QPainterPath path; 340 | path.moveTo(QPointF(0.0, 0.0)); 341 | path.lineTo(btmleft); 342 | path.lineTo(btmright); 343 | path.lineTo(topright); 344 | path.lineTo(topleft); 345 | path.lineTo(QPointF(0.0, 0.0)); 346 | painter->setPen(curPen); 347 | painter->drawPath(path); 348 | break; 349 | } 350 | case AP_MIDDLERIGHT: 351 | { 352 | painter->setPen(Qt::NoPen); 353 | painter->setBrush(m_BackBrush); 354 | 355 | QPen curPen(m_BorderPen); 356 | curPen.setWidthF(m_BorderPen.widthF()); 357 | 358 | QPainterPath path; 359 | path.moveTo(QPointF(0.0, 0.0)); 360 | path.lineTo(btmright); 361 | path.lineTo(btmleft); 362 | path.lineTo(topleft); 363 | path.lineTo(topright); 364 | path.lineTo(QPointF(0.0, 0.0)); 365 | painter->setPen(curPen); 366 | painter->drawPath(path); 367 | break; 368 | } 369 | case AP_MIDDLETOP: 370 | { 371 | painter->setPen(Qt::NoPen); 372 | painter->setBrush(m_BackBrush); 373 | 374 | QPen curPen(m_BorderPen); 375 | curPen.setWidthF(m_BorderPen.widthF()); 376 | 377 | QPainterPath path; 378 | path.moveTo(QPointF(0.0, 0.0)); 379 | path.lineTo(topleft); 380 | path.lineTo(btmleft); 381 | path.lineTo(btmright); 382 | path.lineTo(topright); 383 | path.lineTo(QPointF(0.0, 0.0)); 384 | painter->setPen(curPen); 385 | painter->drawPath(path); 386 | break; 387 | } 388 | case AP_MIDDLEBTTOM: 389 | { 390 | painter->setPen(Qt::NoPen); 391 | painter->setBrush(m_BackBrush); 392 | 393 | QPen curPen(m_BorderPen); 394 | curPen.setWidthF(m_BorderPen.widthF()); 395 | 396 | QPainterPath path; 397 | path.moveTo(QPointF(0.0, 0.0)); 398 | path.lineTo(btmright); 399 | path.lineTo(topright); 400 | path.lineTo(topleft); 401 | path.lineTo(btmleft); 402 | path.lineTo(QPointF(0.0, 0.0)); 403 | painter->setPen(curPen); 404 | painter->drawPath(path); 405 | break; 406 | } 407 | default: 408 | break; 409 | } 410 | 411 | int nHighOffset = m_Margin[_top] - 1.0; 412 | for(auto &field : qAsConst(m_Fields)) 413 | { 414 | QFont fieldFont = m_Infoes[field]->fieldFont; 415 | QPen fieldPen = m_Infoes[field]->fieldPen; 416 | QFontMetrics fieldMet(fieldFont); 417 | 418 | QFont curFont = m_Infoes[field]->valueFont; 419 | QString curValue = m_Infoes[field]->value; 420 | QPen curPen = m_Infoes[field]->valuePen; 421 | 422 | QFontMetrics valueMet(curFont); 423 | QRect fieldRect = fieldMet.boundingRect(field); 424 | QRect valueRect = valueMet.boundingRect(curValue); 425 | 426 | int nFieldHigh = fieldRect.height(); 427 | int nValueHigh = valueRect.height(); 428 | int nMortValue = nFieldHigh > nValueHigh ? nFieldHigh : nValueHigh; 429 | int yOffsetVal = nHighOffset + nMortValue; 430 | 431 | QPointF fieldPos(m_Margin[_left], yOffsetVal); 432 | QPointF valuePos(m_Margin[_left] + m_Margin[_space] + m_Margin[_maxField] , yOffsetVal); 433 | fieldPos = rect.topLeft() + fieldPos; 434 | valuePos = rect.topLeft() + valuePos; 435 | 436 | painter->setRenderHint(QPainter::TextAntialiasing, true); 437 | 438 | painter->setFont(fieldFont); 439 | painter->setPen(fieldPen); 440 | painter->drawText(fieldPos, field); 441 | painter->setFont(curFont); 442 | painter->setPen(curPen); 443 | painter->drawText(valuePos, curValue); 444 | nHighOffset += (nMortValue + m_Margin[_space] - 1.0); 445 | } 446 | 447 | painter->restore(); 448 | } 449 | 450 | QVariant MapTableItem::itemChange(GraphicsItemChange change, const QVariant &value) 451 | { 452 | return QGraphicsItem::itemChange(change, value); 453 | } 454 | 455 | void MapTableItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) 456 | { 457 | QGraphicsItem::hoverEnterEvent(event); 458 | if(this->flags() & QGraphicsItem::ItemIsMovable) { 459 | this->setScale(1.2); 460 | this->setCursor(Qt::DragMoveCursor); 461 | } 462 | } 463 | 464 | void MapTableItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) 465 | { 466 | QGraphicsItem::hoverLeaveEvent(event); 467 | if(this->flags() & QGraphicsItem::ItemIsMovable) { 468 | this->setScale(1.1); 469 | this->setCursor(Qt::ArrowCursor); 470 | } 471 | } 472 | 473 | void MapTableItem::mousePressEvent(QGraphicsSceneMouseEvent *event) 474 | { 475 | QGraphicsItem::mousePressEvent(event); 476 | 477 | if(m_enableMouse) 478 | event->accept(); 479 | else 480 | event->ignore(); 481 | m_pressPos = event->screenPos(); 482 | } 483 | 484 | void MapTableItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) 485 | { 486 | QGraphicsItem::mouseMoveEvent(event); 487 | 488 | /// 由于该Item 可能继承其他 Item 所以在此传递其Item 在场景里的坐标 489 | emit coordinateDragged(this->scenePos()); 490 | } 491 | 492 | void MapTableItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) 493 | { 494 | QGraphicsItem::mouseReleaseEvent(event); 495 | 496 | if(!m_enableMouse) 497 | return; 498 | // if moved some distance, we ignore switch-check 499 | if(m_checkable && ((m_pressPos-event->screenPos()).manhattanLength() < 3) 500 | && this->contains(event->pos())) { 501 | setChecked(!m_checked); 502 | } 503 | } 504 | 505 | void MapTableItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) 506 | { 507 | QGraphicsItem::mouseDoubleClickEvent(event); 508 | emit doubleClicked(); 509 | } 510 | 511 | MapTableItem::FieldInfo::FieldInfo() 512 | { 513 | fieldHigh = 6.0; 514 | bVolatile = false; 515 | fieldFont = QFont("Microsoft YaHei", 10); 516 | valueFont = QFont("Microsoft YaHei", 10); 517 | fieldPen = QPen(QColor(128, 255, 255, 200)); 518 | valuePen = QPen(QColor(128, 255, 255, 200)); 519 | valuePos = {0, 0}; 520 | value = " "; 521 | } 522 | 523 | MapTableItem::FieldInfo::FieldInfo(bool _bVolatile) 524 | { 525 | fieldHigh = 6.0; 526 | bVolatile = _bVolatile; 527 | fieldFont = QFont("Microsoft YaHei", 10); 528 | valueFont = QFont("Microsoft YaHei", 10); 529 | fieldPen = QPen(QColor(128, 255, 255, 200)); 530 | valuePen = QPen(QColor(128, 255, 255, 200)); 531 | valuePos = {0, 0}; 532 | value = " "; 533 | } 534 | 535 | MapTableItem::FieldInfo::FieldInfo(const FieldInfo &ot) 536 | { 537 | fieldHigh = ot.fieldHigh; 538 | bVolatile = ot.bVolatile; 539 | fieldFont = ot.fieldFont; 540 | valueFont = ot.valueFont; 541 | fieldPen = ot.fieldPen; 542 | valuePen = ot.valuePen; 543 | valuePos = ot.valuePos; 544 | value = ot.value; 545 | } 546 | -------------------------------------------------------------------------------- /maptableitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPTABLEITEM_H 2 | #define MAPTABLEITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /*! 11 | * \brief 图表 12 | * \details 显示字段名和字段值,由于其显示的过程更新值较多,需手动调用updateTableSize() 提高其计算图表大小 13 | * 提升计算效率,其绘画效率 14 | * \warning 测试后得出 存在同一viewport的图元 GrapihcsView的更新模式,会存在图元较少,为提高更新效率, 15 | * 选用的视口更新模式为SmartViewportUpdate 更新其视口所有图元。 16 | */ 17 | class GRAPHICSMAPLIB_EXPORT MapTableItem : public QObject, public QGraphicsItem 18 | { 19 | Q_OBJECT 20 | public: 21 | enum AnchorPosition{ 22 | AP_TOPLEFT = 0X00, 23 | AP_TOPRIGHT, 24 | AP_BOTTOMLEFT, 25 | AP_BOTTOMRIGHT, 26 | AP_MIDDLELEFT, 27 | AP_MIDDLERIGHT, 28 | AP_MIDDLETOP, 29 | AP_MIDDLEBTTOM, 30 | AP_CENTER 31 | }; 32 | MapTableItem(const QGeoCoordinate &coord = {0, 0, 0}, QGraphicsItem * parent = nullptr); 33 | ~MapTableItem(); 34 | 35 | /// 设置描点位置边距和间距 36 | void setAnchorPosition(AnchorPosition anchorPos); 37 | void setMargins(int left, int top, int right, int bottom); 38 | void setSpacing(int space); 39 | /// 添加字段 40 | void addField(const QString &field, bool bVolatile); 41 | void inrField(int pos, const QString &field, bool bVolatile); 42 | void delField(const QString &field); 43 | /// 设置字段名字体 44 | void setFieldFont(const QString &field, const QFont &font); 45 | void setValueFont(const QString &field, const QFont &font); 46 | void setFieldPen(const QString &field, const QPen &pen); 47 | void setValuePen(const QString &field, const QPen &pen); 48 | /// 设置字段值 49 | void setValue(const QString &field, const QString &value); 50 | /// 设置启用固定方向 51 | void setFixDirect(bool bFixDirect, double fixedangle = 0.0); 52 | /// 设置圆角半径 53 | void setRoundedRadius(int roundRadius); 54 | /// 设置边框画笔 55 | void setBorderPen(const QPen &borderPen); 56 | /// 设置背景画刷 57 | void setBackBrush(const QBrush &brush); 58 | /// 设置背景图片 59 | void setBackPixmap(const QPixmap &pixmap); 60 | /// 获取牌匾大小 61 | QRectF tabletRect(); 62 | /// 获取字段值. 63 | QString getValue(const QString &field); 64 | /// 实时修改值时, 手动调用一次计算并更新牌匾大小 65 | void updateTableSize(); 66 | 67 | /// 设置是否允许鼠标事件,影响是否能触发点击信号(但是可以收到press信号)以及切换选中状态 68 | void setAllowMouseEvent(bool enable); 69 | /// 设置鼠标可拖拽 70 | void setMoveable(bool movable); 71 | /// 设置选中性 72 | void setCheckable(bool checkable); 73 | /// 设置选中状态(不会触发信号) 74 | void setChecked(bool checked); 75 | /// 切换选中状态(不会触发信号) 76 | void toggle(); 77 | /// 是否选中 78 | bool isChecked() const; 79 | protected: 80 | /// 更新牌匾大小. 81 | void UpdateInfo(); 82 | QRectF boundingRect() const override; 83 | QPainterPath shape() const override; 84 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 85 | signals: 86 | void doubleClicked(); 87 | void coordinateChanged(const QGeoCoordinate &coord); 88 | void coordinateDragged(const QPointF &point); 89 | protected: 90 | virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; 91 | virtual void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; 92 | virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; 93 | virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; 94 | virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; 95 | virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; 96 | virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; 97 | protected: 98 | /// m_Margin字段对应下标类型 99 | enum 100 | { 101 | _left = 0x00,//左边距. 102 | _top = 0x01, //上边距. 103 | _right = 0x02,//右边距. 104 | _bottom = 0x03,//下边距. 105 | _space = 0x04, //字段名与字段值间隔值. 106 | _maxField = 0x05,//所有字段中最長像素. 107 | _upper = 0x06, //描述数组值个数. 108 | }; 109 | 110 | class FieldInfo//字段信息. 111 | { 112 | public: 113 | int fieldHigh;//字段占用高度. 114 | bool bVolatile;//标记字段值是否为经常改变性质. 115 | QFont fieldFont;//字段名字体. 116 | QFont valueFont;//字段值字体. 117 | QPen fieldPen;//字段名画笔. 118 | QPen valuePen;//字段值画笔. 119 | QPointF valuePos;//字段值绘制位置. 120 | QString value; //字段值. 121 | 122 | FieldInfo(); 123 | FieldInfo(bool _bVolatile); 124 | FieldInfo(const FieldInfo &ot); 125 | }; 126 | 127 | qreal m_FixedAngle;//固定角度值. 暂没用上 128 | int m_Margin[_upper]; 129 | int m_nMaxWide;//记录最大宽度. 130 | int m_nCurHigh;//记录当前高度. 暂没用上 131 | int m_BackWide;//背景矩形框宽度. 暂没用上 132 | int m_RoundRadius;//圆角半径. 暂没用上 133 | bool m_bFixedDirect;//固定方向. 134 | 135 | QSize m_ScutSize; // 标牌大小 136 | QPen m_BorderPen; // 边框画笔. 137 | QBrush m_BackBrush; // 背景画刷. 138 | AnchorPosition m_AnchorPos; // 锚点位置. 139 | QStringList m_Fields; //字段名列表(存在是需要有序显示). 140 | QMap m_Infoes; //字段信息. 141 | QPixmap m_backPixmap; //背景底图. 142 | 143 | QGraphicsRectItem m_border; // 外围边框 144 | // 145 | bool m_enableMouse = true; 146 | bool m_checkable = false; 147 | bool m_checked = false; 148 | // 149 | QPoint m_pressPos; 150 | QGeoCoordinate m_coord; 151 | }; 152 | 153 | 154 | #endif // MAPTABLEITEM_H 155 | -------------------------------------------------------------------------------- /maptrailitem.cpp: -------------------------------------------------------------------------------- 1 | #include "maptrailitem.h" 2 | #include "graphicsmap.h" 3 | #include "mapobjectitem.h" 4 | 5 | QSet MapTrailItem::m_items; 6 | 7 | MapTrailItem::MapTrailItem() : 8 | m_attachObj(nullptr) 9 | { 10 | auto pen = this->pen(); 11 | pen.setWidth(5); 12 | pen.setCosmetic(true); // it will be always 2 pixmap whatever scale transform 13 | pen.setCapStyle(Qt::RoundCap); 14 | pen.setJoinStyle(Qt::RoundJoin); 15 | pen.setColor({255, 255, 0, 200}); 16 | this->setPen(pen); 17 | // 18 | m_items.insert(this); 19 | } 20 | 21 | MapTrailItem::~MapTrailItem() 22 | { 23 | m_items.remove(this); 24 | } 25 | 26 | void MapTrailItem::addCoordinate(const QGeoCoordinate &coord) 27 | { 28 | auto path = this->path(); 29 | if(!m_coord.isValid()) { 30 | auto point = GraphicsMap::toScene(coord); 31 | path.moveTo(point); 32 | m_coord = coord; 33 | setPath(path); 34 | return; 35 | } 36 | // 37 | if(m_coord.distanceTo(coord) < 50) 38 | return; 39 | // 40 | m_coord = coord; 41 | auto point = GraphicsMap::toScene(coord); 42 | path.lineTo(point); 43 | setPath(path); 44 | } 45 | 46 | void MapTrailItem::clear() 47 | { 48 | setPath(QPainterPath()); 49 | m_coord = QGeoCoordinate(); 50 | } 51 | 52 | void MapTrailItem::attach(MapObjectItem *obj) 53 | { 54 | if(m_attachObj) 55 | disconnect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapTrailItem::addCoordinate); 56 | // 57 | clear(); 58 | m_attachObj = obj; 59 | if(m_attachObj) 60 | connect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapTrailItem::addCoordinate); 61 | } 62 | 63 | void MapTrailItem::detach() 64 | { 65 | if(m_attachObj) 66 | disconnect(m_attachObj, &MapObjectItem::coordinateChanged, this, &MapTrailItem::addCoordinate); 67 | m_attachObj = nullptr; 68 | } 69 | 70 | const QSet &MapTrailItem::items() 71 | { 72 | return m_items; 73 | } 74 | -------------------------------------------------------------------------------- /maptrailitem.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPTRAILITEM_H 2 | #define MAPTRAILITEM_H 3 | 4 | #include "GraphicsMapLib_global.h" 5 | #include 6 | #include 7 | 8 | class MapObjectItem; 9 | 10 | /*! 11 | * \brief 轨迹 12 | * \warning 该元素对渲染效率影响较大,会占用过多CPU,暂未找到好的解决方案 13 | */ 14 | class GRAPHICSMAPLIB_EXPORT MapTrailItem : public QObject, public QGraphicsPathItem 15 | { 16 | Q_OBJECT 17 | public: 18 | MapTrailItem(); 19 | ~MapTrailItem(); 20 | /// 添加经纬点轨迹(该函数会自动优化该点是否添加到轨迹点) 21 | void addCoordinate(const QGeoCoordinate &coord); 22 | /// 清除轨迹 23 | void clear(); 24 | /// 依附到地图对象,清除已存在的航迹,将会自动更新位置 25 | void attach(MapObjectItem *obj); 26 | /// 取消依附地图对象,后续手动更新位置,如需清除航迹,请手动清除 27 | void detach(); 28 | 29 | public: 30 | /// 获取所有的实例 31 | static const QSet &items(); 32 | 33 | private: 34 | static QSet m_items; ///< 所有实例 35 | private: 36 | QGeoCoordinate m_coord; ///< 轨迹点0 37 | // 38 | MapObjectItem *m_attachObj; 39 | }; 40 | 41 | #endif // MAPTRAILITEM_H 42 | --------------------------------------------------------------------------------