#!/usr/bin/env python # -*- coding: utf-8 -*- """ Created on 2017年12月17日 @author: Irony."[讽刺] @site: https://pyqt5.com , https://github.com/892768447 @email: 892768447@qq.com @file: WorldMap @description: """ import json import math from PyQt5.QtCore import Qt, QPointF, QRectF from PyQt5.QtGui import QColor, QPainter, QPolygonF, QPen, QBrush from PyQt5.QtOpenGL import QGLFormat from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPolygonItem __Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com" __Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]" __Version__ = "Version 1.0" class GraphicsView(QGraphicsView): # 背景区域颜色 backgroundColor = QColor(31, 31, 47) # 边框颜色 borderColor = QColor(58, 58, 90) def __init__(self, *args, **kwargs): super(GraphicsView, self).__init__(*args, **kwargs) self.resize(800, 600) # 设置背景颜色 self.setBackgroundBrush(self.backgroundColor) ''' #参考 http://doc.qt.io/qt-5/qgraphicsview.html#CacheModeFlag-enum CacheNone 不使用缓存 CacheBackground 缓存背景 ''' self.setCacheMode(self.CacheBackground) ''' #参考 http://doc.qt.io/qt-5/qgraphicsview.html#DragMode-enum NoDrag 什么都没发生; 鼠标事件被忽略。 ScrollHandDrag 光标变成指针,拖动鼠标将滚动滚动条。 该模式可以在交互式和非交互式模式下工作。 RubberBandDrag 拖动鼠标将设置橡皮筋几何形状,并选择橡皮筋所覆盖的所有项目。 对于非交互式视图,此模式被禁用。 ''' self.setDragMode(self.ScrollHandDrag) ''' #参考 http://doc.qt.io/qt-5/qgraphicsview.html#OptimizationFlag-enum DontClipPainter 已过时 DontSavePainterState 渲染时,QGraphicsView在渲染背景或前景时以及渲染每个项目时保护painter状态(请参阅QPainter.save())。 这允许你离开painter处于改变状态(即你可以调用QPainter.setPen()或QPainter.setBrush(),而不需要在绘制之后恢复状态)。 但是,如果项目一致地恢复状态,则应启用此标志以防止QGraphicsView执行相同的操作。 DontAdjustForAntialiasing 禁用QGraphicsView的抗锯齿自动调整曝光区域。 在QGraphicsItem.boundingRect()的边界上渲染反锯齿线的项目可能会导致渲染部分线外。 为了防止渲染失真,QGraphicsView在所有方向上将所有曝光区域扩展2个像素。 如果启用此标志,QGraphicsView将不再执行这些调整,最大限度地减少需要重绘的区域,从而提高性能。 一个常见的副作用是,使用抗锯齿功能进行绘制的项目可能会在移动时在画面上留下绘画痕迹。 IndirectPainting 从Qt 4.6开始,恢复调用QGraphicsView.drawItems()和QGraphicsScene.drawItems()的旧绘画算法。 仅用于与旧代码的兼容性。 ''' self.setOptimizationFlag(self.DontSavePainterState) ''' #参考 http://doc.qt.io/qt-5/qpainter.html#RenderHint-enum Antialiasing 抗锯齿 TextAntialiasing 文本抗锯齿 SmoothPixmapTransform 平滑像素变换算法 HighQualityAntialiasing 请改用Antialiasing NonCosmeticDefaultPen 已过时 Qt4CompatiblePainting 从Qt4移植到Qt5可能有用 ''' self.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform) if QGLFormat.hasOpenGL(): self.setRenderHint(QPainter.HighQualityAntialiasing) ''' #当视图被调整大小时,视图如何定位场景。使用这个属性来决定当视口控件的大小改变时,如何在视口中定位场景。 缺省行为NoAnchor在调整大小的过程中不改变场景的位置; 视图的左上角将显示为在调整大小时被锚定。请注意,只有场景的一部分可见(即有滚动条时),此属性的效果才明显。 否则,如果整个场景适合视图,QGraphicsScene使用视图对齐将视景中的场景定位。 #参考 http://doc.qt.io/qt-5/qgraphicsview.html#ViewportAnchor-enum NoAnchor 视图保持场景的位置不变 AnchorViewCenter 视图中心被用作锚点。 AnchorUnderMouse 鼠标当前位置被用作锚点 ''' self.setResizeAnchor(self.AnchorUnderMouse) ''' Rubber选择模式 #参考 http://doc.qt.io/qt-5/qt.html#ItemSelectionMode-enum ContainsItemShape 输出列表仅包含形状完全包含在选择区域内的项目。 不包括与区域轮廓相交的项目。 IntersectsItemShape 默认,输出列表包含其形状完全包含在选择区域内的项目以及与区域轮廓相交的项目。 ContainsItemBoundingRect 输出列表仅包含边界矩形完全包含在选择区域内的项目。 不包括与区域轮廓相交的项目。 IntersectsItemBoundingRect 输出列表包含边界矩形完全包含在选择区域内的项目以及与区域轮廓相交的项目。 这种方法通常用于确定需要重绘的区域。 ''' self.setRubberBandSelectionMode(Qt.IntersectsItemShape) ''' #在转换过程中如何定位视图。QGraphicsView使用这个属性决定当变换矩阵改变时如何在视口中定位场景,并且视图的坐标系被变换。 默认行为AnchorViewCenter确保在视图中心的场景点在变换过程中保持不变(例如,在旋转时,场景将围绕视图的中心旋转)。请注意,只有场景的一部分可见(即有滚动条时),此属性的效果才明显。 否则,如果整个场景适合视图,QGraphicsScene使用视图对齐将视景中的场景定位。 #参考 http://doc.qt.io/qt-5/qgraphicsview.html#ViewportAnchor-enum NoAnchor 视图保持场景的位置不变 AnchorViewCenter 视图中心被用作锚点。 AnchorUnderMouse 鼠标当前位置被用作锚点 ''' self.setTransformationAnchor(self.AnchorUnderMouse) # if QGLFormat.hasOpenGL(): # 如果开启了OpenGL则使用OpenGL Widget # self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) ''' #参考 http://doc.qt.io/qt-5/qgraphicsview.html#ViewportUpdateMode-enum FullViewportUpdate 当场景的任何可见部分改变或重新显示时,QGraphicsView将更新整个视口。 当QGraphicsView花费更多的时间来计算绘制的内容(比如重复更新很多小项目)时,这种方法是最快的。 这是不支持部分更新(如QGLWidget)的视口以及需要禁用滚动优化的视口的首选更新模式。 MinimalViewportUpdate QGraphicsView将确定需要重绘的最小视口区域,通过避免重绘未改变的区域来最小化绘图时间。 这是QGraphicsView的默认模式。 虽然这种方法提供了一般的最佳性能,但如果场景中有很多小的可见变化,QGraphicsView最终可能花费更多的时间来寻找最小化的方法。 SmartViewportUpdate QGraphicsView将尝试通过分析需要重绘的区域来找到最佳的更新模式。 BoundingRectViewportUpdate 视口中所有更改的边界矩形将被重绘。 这种模式的优点是,QGraphicsView只搜索一个区域的变化,最大限度地减少了花在确定需要重绘的时间。 缺点是还没有改变的地方也需要重新绘制。 NoViewportUpdate 当场景改变时,QGraphicsView将永远不会更新它的视口。 预计用户将控制所有更新。 此模式禁用QGraphicsView中的所有(可能较慢)项目可见性测试,适用于要求固定帧速率或视口以其他方式在外部进行更新的场景。 ''' self.setViewportUpdateMode(self.SmartViewportUpdate) # 设置场景(根据地图的经纬度,并让原点显示在屏幕中间) self._scene = QGraphicsScene(-180, -90, 360, 180, self) self.setScene(self._scene) # 初始化地图 self.initMap() def wheelEvent(self, event): # 滑轮事件 if event.modifiers() & Qt.ControlModifier: self.scaleView(math.pow(2.0, -event.angleDelta().y() / 240.0)) return event.accept() super(GraphicsView, self).wheelEvent(event) def scaleView(self, scaleFactor): factor = self.transform().scale( scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width() if factor < 0.07 or factor > 100: return self.scale(scaleFactor, scaleFactor) def initMap(self): features = json.load( open("Data/world.json", encoding="utf8")).get("features") for feature in features: geometry = feature.get("geometry") if not geometry: continue _type = geometry.get("type") coordinates = geometry.get("coordinates") for coordinate in coordinates: if _type == "Polygon": polygon = QPolygonF( [QPointF(latitude, -longitude) for latitude, longitude in coordinate]) item = QGraphicsPolygonItem(polygon) item.setPen(QPen(self.borderColor, 0)) item.setBrush(QBrush(self.backgroundColor)) item.setPos(0, 0) self._scene.addItem(item) elif _type == "MultiPolygon": for _coordinate in coordinate: polygon = QPolygonF( [QPointF(latitude, -longitude) for latitude, longitude in _coordinate]) item = QGraphicsPolygonItem(polygon) item.setPen(QPen(self.borderColor, 0)) item.setBrush(QBrush(self.backgroundColor)) item.setPos(0, 0) self._scene.addItem(item) if __name__ == "__main__": import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) print("OpenGL Status:", QGLFormat.hasOpenGL()) view = GraphicsView() view.setWindowTitle("世界地图") view.show() sys.exit(app.exec_())