1# -*- coding: utf-8 -*- 2"""QGIS Unit tests for QgsLayoutPicture. 3 4.. note:: This program is free software; you can redistribute it and/or modify 5it under the terms of the GNU General Public License as published by 6the Free Software Foundation; either version 2 of the License, or 7(at your option) any later version. 8""" 9__author__ = '(C) 2017 by Nyall Dawson' 10__date__ = '23/10/2017' 11__copyright__ = 'Copyright 2017, The QGIS Project' 12 13import qgis # NOQA 14 15import os 16import socketserver 17import threading 18import http.server 19from qgis.PyQt.QtCore import QRectF, QDir 20from qgis.PyQt.QtTest import QSignalSpy 21from qgis.PyQt.QtXml import QDomDocument 22from qgis.core import (QgsLayoutItemPicture, 23 QgsLayout, 24 QgsLayoutItemMap, 25 QgsRectangle, 26 QgsCoordinateReferenceSystem, 27 QgsProject, 28 QgsReadWriteContext 29 ) 30from qgis.testing import start_app, unittest 31from utilities import unitTestDataPath 32from qgslayoutchecker import QgsLayoutChecker 33from test_qgslayoutitem import LayoutItemTestCase 34 35start_app() 36TEST_DATA_DIR = unitTestDataPath() 37 38 39class TestQgsLayoutPicture(unittest.TestCase, LayoutItemTestCase): 40 41 @classmethod 42 def setUpClass(cls): 43 cls.item_class = QgsLayoutItemPicture 44 45 # Bring up a simple HTTP server, for remote picture tests 46 os.chdir(unitTestDataPath() + '') 47 handler = http.server.SimpleHTTPRequestHandler 48 49 cls.httpd = socketserver.TCPServer(('localhost', 0), handler) 50 cls.port = cls.httpd.server_address[1] 51 52 cls.httpd_thread = threading.Thread(target=cls.httpd.serve_forever) 53 cls.httpd_thread.setDaemon(True) 54 cls.httpd_thread.start() 55 56 def __init__(self, methodName): 57 """Run once on class initialization.""" 58 unittest.TestCase.__init__(self, methodName) 59 60 TEST_DATA_DIR = unitTestDataPath() 61 self.pngImage = TEST_DATA_DIR + "/sample_image.png" 62 self.svgImage = TEST_DATA_DIR + "/sample_svg.svg" 63 64 # create composition 65 self.layout = QgsLayout(QgsProject.instance()) 66 self.layout.initializeDefaults() 67 68 self.picture = QgsLayoutItemPicture(self.layout) 69 self.picture.setPicturePath(self.pngImage) 70 self.picture.attemptSetSceneRect(QRectF(70, 70, 100, 100)) 71 self.picture.setFrameEnabled(True) 72 self.layout.addLayoutItem(self.picture) 73 74 def setUp(self): 75 self.report = "<h1>Python QgsLayoutItemPicture Tests</h1>\n" 76 77 def tearDown(self): 78 report_file_path = "%s/qgistest.html" % QDir.tempPath() 79 with open(report_file_path, 'a') as report_file: 80 report_file.write(self.report) 81 82 def testMode(self): 83 pic = QgsLayoutItemPicture(self.layout) 84 # should default to unknown 85 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatUnknown) 86 spy = QSignalSpy(pic.changed) 87 pic.setMode(QgsLayoutItemPicture.FormatRaster) 88 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatRaster) 89 self.assertEqual(len(spy), 1) 90 pic.setMode(QgsLayoutItemPicture.FormatRaster) 91 self.assertEqual(len(spy), 1) 92 pic.setMode(QgsLayoutItemPicture.FormatSVG) 93 self.assertEqual(len(spy), 3) # ideally only 2! 94 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatSVG) 95 96 # set picture path without explicit format 97 pic.setPicturePath(self.pngImage) 98 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatRaster) 99 pic.setPicturePath(self.svgImage) 100 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatSVG) 101 # forced format 102 pic.setPicturePath(self.pngImage, QgsLayoutItemPicture.FormatSVG) 103 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatSVG) 104 pic.setPicturePath(self.pngImage, QgsLayoutItemPicture.FormatRaster) 105 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatRaster) 106 pic.setPicturePath(self.svgImage, QgsLayoutItemPicture.FormatSVG) 107 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatSVG) 108 pic.setPicturePath(self.svgImage, QgsLayoutItemPicture.FormatRaster) 109 self.assertEqual(pic.mode(), QgsLayoutItemPicture.FormatRaster) 110 111 def testReadWriteXml(self): 112 pr = QgsProject() 113 l = QgsLayout(pr) 114 115 pic = QgsLayoutItemPicture(l) 116 # mode should be saved/restored 117 pic.setMode(QgsLayoutItemPicture.FormatRaster) 118 119 # save original item to xml 120 doc = QDomDocument("testdoc") 121 elem = doc.createElement("test") 122 self.assertTrue(pic.writeXml(elem, doc, QgsReadWriteContext())) 123 124 pic2 = QgsLayoutItemPicture(l) 125 self.assertTrue(pic2.readXml(elem.firstChildElement(), doc, QgsReadWriteContext())) 126 self.assertEqual(pic2.mode(), QgsLayoutItemPicture.FormatRaster) 127 128 pic.setMode(QgsLayoutItemPicture.FormatSVG) 129 elem = doc.createElement("test2") 130 self.assertTrue(pic.writeXml(elem, doc, QgsReadWriteContext())) 131 pic3 = QgsLayoutItemPicture(l) 132 self.assertTrue(pic3.readXml(elem.firstChildElement(), doc, QgsReadWriteContext())) 133 self.assertEqual(pic3.mode(), QgsLayoutItemPicture.FormatSVG) 134 135 def testResizeZoom(self): 136 """Test picture resize zoom mode.""" 137 self.picture.setResizeMode(QgsLayoutItemPicture.Zoom) 138 139 checker = QgsLayoutChecker('composerpicture_resize_zoom', self.layout) 140 checker.setControlPathPrefix("composer_picture") 141 testResult, message = checker.testLayout() 142 self.report += checker.report() 143 144 assert testResult, message 145 146 def testRemoteImage(self): 147 """Test fetching remote picture.""" 148 self.picture.setPicturePath( 149 'http://localhost:' + str(TestQgsLayoutPicture.port) + '/qgis_local_server/logo.png') 150 151 checker = QgsLayoutChecker('composerpicture_remote', self.layout) 152 checker.setControlPathPrefix("composer_picture") 153 testResult, message = checker.testLayout() 154 self.report += checker.report() 155 156 self.picture.setPicturePath(self.pngImage) 157 assert testResult, message 158 159 def testNorthArrowWithMapItemRotation(self): 160 """Test picture rotation when map item is also rotated""" 161 162 layout = QgsLayout(QgsProject.instance()) 163 164 map = QgsLayoutItemMap(layout) 165 map.setExtent(QgsRectangle(0, -256, 256, 0)) 166 layout.addLayoutItem(map) 167 168 picture = QgsLayoutItemPicture(layout) 169 layout.addLayoutItem(picture) 170 171 picture.setLinkedMap(map) 172 self.assertEqual(picture.linkedMap(), map) 173 174 picture.setNorthMode(QgsLayoutItemPicture.GridNorth) 175 map.setItemRotation(45) 176 self.assertEqual(picture.pictureRotation(), 45) 177 map.setMapRotation(-34) 178 self.assertEqual(picture.pictureRotation(), 11) 179 180 # add an offset 181 picture.setNorthOffset(-10) 182 self.assertEqual(picture.pictureRotation(), 1) 183 184 map.setItemRotation(55) 185 self.assertEqual(picture.pictureRotation(), 11) 186 187 def testGridNorth(self): 188 """Test syncing picture to grid north""" 189 190 layout = QgsLayout(QgsProject.instance()) 191 192 map = QgsLayoutItemMap(layout) 193 map.setExtent(QgsRectangle(0, -256, 256, 0)) 194 layout.addLayoutItem(map) 195 196 picture = QgsLayoutItemPicture(layout) 197 layout.addLayoutItem(picture) 198 199 picture.setLinkedMap(map) 200 self.assertEqual(picture.linkedMap(), map) 201 202 picture.setNorthMode(QgsLayoutItemPicture.GridNorth) 203 map.setMapRotation(45) 204 self.assertEqual(picture.pictureRotation(), 45) 205 206 # add an offset 207 picture.setNorthOffset(-10) 208 self.assertEqual(picture.pictureRotation(), 35) 209 210 def testTrueNorth(self): 211 """Test syncing picture to true north""" 212 213 layout = QgsLayout(QgsProject.instance()) 214 215 map = QgsLayoutItemMap(layout) 216 map.attemptSetSceneRect(QRectF(0, 0, 10, 10)) 217 map.setCrs(QgsCoordinateReferenceSystem.fromEpsgId(3575)) 218 map.setExtent(QgsRectangle(-2126029.962, -2200807.749, -119078.102, -757031.156)) 219 layout.addLayoutItem(map) 220 221 picture = QgsLayoutItemPicture(layout) 222 layout.addLayoutItem(picture) 223 224 picture.setLinkedMap(map) 225 self.assertEqual(picture.linkedMap(), map) 226 227 picture.setNorthMode(QgsLayoutItemPicture.TrueNorth) 228 self.assertAlmostEqual(picture.pictureRotation(), 37.20, 1) 229 230 # shift map 231 map.setExtent(QgsRectangle(2120672.293, -3056394.691, 2481640.226, -2796718.780)) 232 self.assertAlmostEqual(picture.pictureRotation(), -38.18, 1) 233 234 # rotate map 235 map.setMapRotation(45) 236 self.assertAlmostEqual(picture.pictureRotation(), -38.18 + 45, 1) 237 238 # add an offset 239 picture.setNorthOffset(-10) 240 self.assertAlmostEqual(picture.pictureRotation(), -38.18 + 35, 1) 241 242 def testMissingImage(self): 243 layout = QgsLayout(QgsProject.instance()) 244 245 picture = QgsLayoutItemPicture(layout) 246 247 # SVG 248 picture.setPicturePath("invalid_path", QgsLayoutItemPicture.FormatSVG) 249 self.assertEqual(picture.isMissingImage(), True) 250 self.assertEqual(picture.mode(), QgsLayoutItemPicture.FormatSVG) 251 252 # Raster 253 picture.setPicturePath("invalid_path", QgsLayoutItemPicture.FormatRaster) 254 self.assertEqual(picture.isMissingImage(), True) 255 self.assertEqual(picture.mode(), QgsLayoutItemPicture.FormatRaster) 256 257 258if __name__ == '__main__': 259 unittest.main() 260