def setUpClass(cls): """Run before all tests""" # Create test layer srcpath = os.path.join(TEST_DATA_DIR, 'provider') cls.basetestfile = os.path.join(srcpath, 'delimited_wkt.csv') url = QUrl.fromLocalFile(cls.basetestfile) url.addQueryItem("crs", "epsg:4326") url.addQueryItem("type", "csv") url.addQueryItem("wktField", "wkt") url.addQueryItem("spatialIndex", "no") url.addQueryItem("subsetIndex", "no") url.addQueryItem("watchFile", "no") cls.vl = QgsVectorLayer(url.toString(), u'test', u'delimitedtext') assert cls.vl.isValid(), "{} is invalid".format(cls.basetestfile) cls.provider = cls.vl.dataProvider() cls.basetestpolyfile = os.path.join(srcpath, 'delimited_wkt_poly.csv') url = QUrl.fromLocalFile(cls.basetestpolyfile) url.addQueryItem("crs", "epsg:4326") url.addQueryItem("type", "csv") url.addQueryItem("wktField", "wkt") url.addQueryItem("spatialIndex", "no") url.addQueryItem("subsetIndex", "no") url.addQueryItem("watchFile", "no") cls.vl_poly = QgsVectorLayer(url.toString(), u'test_polygon', u'delimitedtext') assert cls.vl_poly.isValid(), "{} is invalid".format(cls.basetestpolyfile) cls.poly_provider = cls.vl_poly.dataProvider()
def test_refLayers2(self): l1 = QgsVectorLayer(QUrl.fromLocalFile(os.path.join(self.testDataDir, "delimitedtext/test.csv")).toString() + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False) self.assertEqual(l1.isValid(), True) QgsProject.instance().addMapLayer(l1) # referenced layers cannot be stored ! tmp = QUrl.fromLocalFile(os.path.join(tempfile.gettempdir(), "t.sqlite")).toString() l2 = QgsVectorLayer("%s?layer_ref=%s" % (tmp, l1.id()), "tt", "virtual", False) self.assertEqual(l2.isValid(), False) self.assertEqual("Cannot store referenced layers" in l2.dataProvider().error().message(), True)
def test_repository_url(): """Return the test repository URL on file system. :return: The test repository URL string :rtype: str """ return QUrl.fromLocalFile(test_data_path()).toString()
def onHelp(self): helpPath = Utils.getHelpPath() if helpPath == '': url = QUrl("http://www.gdal.org/" + self.helpFileName) else: url = QUrl.fromLocalFile(helpPath + '/' + self.helpFileName) QDesktopServices.openUrl(url)
def help(self): localDoc = None html = self.grass7Name + ".html" if system.isWindows(): # For MS-Windows, use the configured GRASS7 path localPath = os.path.join(Grass7Utils.grassPath(), "docs/html", html) if os.path.exists(localPath): localDoc = os.path.abspath(localPath) elif system.isMac(): # For MacOSX official package localPath = os.path.join("/Applications/GRASS-7.0.app/Contents/MacOS/docs/html", html) if os.path.exists(localPath): localDoc = os.path.abspath(localPath) else: # For GNU/Linux distributions searchPaths = ["/usr/share/doc/grass-doc/html", "/opt/grass/docs/html", "/usr/share/doc/grass/docs/html"] for path in searchPaths: localPath = os.path.join(path, html) if os.path.exists(localPath): localDoc = os.path.abspath(localPath) # Found the local documentation if localDoc: localDoc = QUrl.fromLocalFile(localDoc).toString() return False, localDoc # Return the URL if local doc is not found return False, "http://grass.osgeo.org/grass70/manuals/" + self.grass7Name + ".html"
def test_source_escaping(self): # the source contains ':' source = QUrl.fromLocalFile(os.path.join(self.testDataDir, "delimitedtext/test.csv")).toString() + "?type=csv&geomType=none&subsetIndex=no&watchFile=no" d = QgsVirtualLayerDefinition() d.addSource("t", source, "delimitedtext") l = QgsVectorLayer(d.toString(), "vtab", "virtual", False) self.assertEqual(l.isValid(), True)
def openScriptFileExtEditor(self): tabWidget = self.tabEditorWidget.currentWidget() path = tabWidget.path import subprocess try: subprocess.Popen([os.environ['EDITOR'], path]) except KeyError: QDesktopServices.openUrl(QUrl.fromLocalFile(path))
def helpUrl(self): helpPath = Grass7Utils.grassHelpPath() if helpPath == '': return None if os.path.exists(helpPath): return QUrl.fromLocalFile(os.path.join(helpPath, '{}.html'.format(self.grass7Name))).toString() else: return helpPath + '{}.html'.format(self.grass7Name)
def help(self): helpPath = GrassUtils.grassHelpPath() if helpPath == '': return False, None if os.path.exists(helpPath): return False, QUrl.fromLocalFile(os.path.join(helpPath, '{}.html'.format(self.grassName))).toString() else: return False, '{}{}.html'.format(helpPath, self.grassName)
def test_CsvNoGeometry(self): l1 = QgsVectorLayer(QUrl.fromLocalFile(os.path.join(self.testDataDir, "delimitedtext/test.csv")).toString() + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False) self.assertEqual(l1.isValid(), True) QgsProject.instance().addMapLayer(l1) l2 = QgsVectorLayer("?layer_ref=" + l1.id(), "vtab", "virtual", False) self.assertEqual(l2.isValid(), True) QgsProject.instance().removeMapLayer(l1.id())
def helpUrl(self): helpPath = GdalUtils.gdalHelpPath() if helpPath == '': return None if os.path.exists(helpPath): return QUrl.fromLocalFile(os.path.join(helpPath, '{}.html'.format(self.commandName()))).toString() else: return helpPath + '{}.html'.format(self.commandName())
def test_DynamicGeometry(self): l1 = QgsVectorLayer(QUrl.fromLocalFile(os.path.join(self.testDataDir, "delimitedtext/testextpt.txt")).toString() + "?type=csv&delimiter=%7C&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False) self.assertEqual(l1.isValid(), True) QgsProject.instance().addMapLayer(l1) query = toPercent("select *,makepoint(x,y) as geom from vtab1") l2 = QgsVectorLayer("?layer_ref=%s&query=%s&geometry=geom:point:0&uid=id" % (l1.id(), query), "vtab", "virtual", False) self.assertEqual(l2.isValid(), True) QgsProject.instance().removeMapLayer(l1)
def test_reopen2(self): source = toPercent(os.path.join(self.testDataDir, "france_parts.shp")) tmp = QUrl.fromLocalFile(os.path.join(tempfile.gettempdir(), "t.sqlite")).toString() l = QgsVectorLayer("%s?layer=ogr:%s:vtab&nogeometry" % (tmp, source), "vtab2", "virtual", False) self.assertEqual(l.isValid(), True) l2 = QgsVectorLayer(tmp, "tt", "virtual", False) self.assertEqual(l2.isValid(), True) self.assertEqual(l2.dataProvider().wkbType(), 100) self.assertEqual(l2.dataProvider().featureCount(), 4)
def test_sql(self): l1 = QgsVectorLayer(QUrl.fromLocalFile(os.path.join(self.testDataDir, "delimitedtext/test.csv")).toString() + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False) self.assertEqual(l1.isValid(), True) QgsProject.instance().addMapLayer(l1) l3 = QgsVectorLayer("?query=SELECT * FROM test", "tt", "virtual") self.assertEqual(l3.isValid(), True) s = sum(f.id() for f in l3.getFeatures()) self.assertEqual(s, 15)
def test_resolve_path(self): """Test resolving the path works correctly.""" collection_path = test_data_path('collections', 'test_collection') search_paths = [] # Test case 1: local path img_path = test_data_path( 'collections', 'test_collection', 'svg', 'blastoise.svg') fixed_path = resolve_path(img_path, collection_path, search_paths) self.assertEqual(img_path, fixed_path) # Test case 2: local url img_path = test_data_path( 'collections', 'test_collection', 'svg', 'blastoise.svg') img_url = QUrl.fromLocalFile(img_path) fixed_path = resolve_path( img_url.toString(), collection_path, search_paths) self.assertEqual(fixed_path, img_path) # Test case 3: http url img_path = 'http://qgis.org/test/image.svg' img_url = QUrl(img_path) fixed_path = resolve_path( img_url.toString(), collection_path, search_paths) self.assertEqual(fixed_path, img_path) # Test case 4: checking in the svg local collection path img_path = '/you/would/not/find/this/charizard.svg' fixed_path = resolve_path(img_path, collection_path, search_paths) expected_path = test_data_path( 'collections', 'test_collection', 'svg', 'charizard.svg') self.assertEqual(fixed_path, expected_path) # Test case 5: checking in the image local collection path img_path = '/you/would/not/find/this/pikachu.png' fixed_path = resolve_path(img_path, collection_path, search_paths) expected_path = test_data_path( 'collections', 'test_collection', 'image', 'pikachu.png') self.assertEqual(fixed_path, expected_path) # Test case 6: checking in the search paths search_paths = [ test_data_path( 'collections', 'test_collection', 'preview') ] img_path = 'prev_1.png' fixed_path = resolve_path(img_path, collection_path, search_paths) expected_path = test_data_path( 'collections', 'test_collection', 'preview', 'prev_1.png') self.assertEqual(fixed_path, expected_path) # Test case 7: not finding anywhere (return the original path) img_path = '/you/would/not/find/this/anywhere.png' fixed_path = resolve_path(img_path, collection_path, search_paths) self.assertEqual(fixed_path, img_path)
def test_refLayer(self): l1 = QgsVectorLayer(QUrl.fromLocalFile(os.path.join(self.testDataDir, "delimitedtext/test.csv")).toString() + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False) self.assertEqual(l1.isValid(), True) QgsProject.instance().addMapLayer(l1) l2 = QgsVectorLayer("?layer_ref=" + l1.id(), "vtab", "virtual", False) self.assertEqual(l2.isValid(), True) # now delete the layer QgsProject.instance().removeMapLayer(l1.id()) # check that it does not crash print((sum([f.id() for f in l2.getFeatures()])))
def delimitedTextData(self, testname, filename, requests, verbose, **params): # Retrieve the data for a delimited text url # Create a layer for the specified file and query parameters # and return the data for the layer (fields, data) filepath = os.path.join(unitTestDataPath("delimitedtext"), filename) url = QUrl.fromLocalFile(filepath) if not requests: requests = [{}] for k in params.keys(): url.addQueryItem(k, params[k]) urlstr = url.toString() log = [] with MessageLogger('DelimitedText') as logger: if verbose: print(testname) layer = QgsVectorLayer(urlstr, 'test', 'delimitedtext') uri = unicode(layer.dataProvider().dataSourceUri()) if verbose: print(uri) basename = os.path.basename(filepath) if not basename.startswith('test'): basename = 'file' uri = re.sub(r'^file\:\/\/[^\?]*', 'file://' + basename, uri) fields = [] fieldTypes = [] data = {} if layer.isValid(): for nr, r in enumerate(requests): if verbose: print("Processing request", nr + 1, repr(r)) if callable(r): r(layer) if verbose: print("Request function executed") if callable(r): continue rfields, rtypes, rdata = self.layerData(layer, r, nr * 1000) if len(rfields) > len(fields): fields = rfields fieldTypes = rtypes data.update(rdata) if not rdata: log.append("Request " + str(nr) + " did not return any data") if verbose: print("Request returned", len(rdata.keys()), "features") for msg in logger.messages(): filelogname = 'temp_file' if 'tmp' in filename.lower() else filename msg = re.sub(r'file\s+.*' + re.escape(filename), 'file ' + filelogname, msg) msg = msg.replace(filepath, filelogname) log.append(msg) return dict(fields=fields, fieldTypes=fieldTypes, data=data, log=log, uri=uri, geometryType=layer.geometryType())
def shortHelp(self): helpPath = GdalUtils.gdalHelpPath() if helpPath == '': return if os.path.exists(helpPath): url = QUrl.fromLocalFile(os.path.join(helpPath, '{}.html'.format(self.commandName()))).toString() else: url = helpPath + '{}.html'.format(self.commandName()) return self._formatHelp('''This algorithm is based on the GDAL {} module. For more info, see the <a href={}> module help</a> '''.format(self.commandName(), url))
def asHtmlEntry(self, roles): canInstall = "Green" if self.canOpen(roles) else "Orange" s = """<div class="icon"><div class="icon-container"> <img src="{image}"></div></div> <div class="description"><h2>{title}</h2><p>{description}</p> <a class="btn{available}" href="{url}">INSTALL</a> </div> """.format(image=QUrl.fromLocalFile(self.iconPath()).toString(), title=self.name, description=self.description, available=canInstall, url=self.url ) return s
def test_refLayers(self): l1 = QgsVectorLayer(QUrl.fromLocalFile(os.path.join(self.testDataDir, "delimitedtext/test.csv")).toString() + "?type=csv&geomType=none&subsetIndex=no&watchFile=no", "test", "delimitedtext", False) self.assertEqual(l1.isValid(), True) QgsProject.instance().addMapLayer(l1) # cf qgis bug #12266 for i in range(10): q = toPercent("select * from t" + str(i)) l2 = QgsVectorLayer("?layer_ref=%s:t%d&query=%s&uid=id" % (l1.id(), i, q), "vtab", "virtual", False) QgsProject.instance().addMapLayer(l2) self.assertEqual(l2.isValid(), True) s = sum([f.id() for f in l2.dataProvider().getFeatures()]) # NOQA self.assertEqual(sum([f.id() for f in l2.getFeatures()]), 21) QgsProject.instance().removeMapLayer(l2.id())
def getHtmlFromHelpFile(alg, helpFile): if not os.path.exists(helpFile): return None try: with open(helpFile) as f: descriptions = json.load(f) content = getHtmlFromDescriptionsDict(alg, descriptions) algGroup, algName = alg.id().split(':') filePath = os.path.join(system.tempHelpFolder(), "{}_{}.html".format(algGroup, algName)) with open(filePath, 'w', encoding='utf-8') as f: f.write(content) return QUrl.fromLocalFile(filePath).toString() except: return None
def test_reopen4(self): source = toPercent(os.path.join(self.testDataDir, "france_parts.shp")) tmp = QUrl.fromLocalFile(os.path.join(tempfile.gettempdir(), "t.sqlite")).toString() query = toPercent("SELECT * FROM vtab") l = QgsVectorLayer("%s?layer=ogr:%s:vtab&query=%s&uid=objectid&nogeometry" % (tmp, source, query), "vtab2", "virtual", False) self.assertEqual(l.isValid(), True) l2 = QgsVectorLayer(tmp, "tt", "virtual", False) self.assertEqual(l2.isValid(), True) self.assertEqual(l2.dataProvider().wkbType(), 100) self.assertEqual(l2.dataProvider().featureCount(), 4) sumid = sum([f.id() for f in l2.getFeatures()]) self.assertEqual(sumid, 10659) suma = sum([f.attributes()[1] for f in l2.getFeatures()]) self.assertEqual(suma, 3064.0)
def _get_cursor_columns(self, c): tf = QTemporaryFile() tf.open() tmp = tf.fileName() tf.close() q = QUrl.toPercentEncoding(c.sql) p = QgsVectorLayer("%s?query=%s" % (QUrl.fromLocalFile(tmp).toString(), q), "vv", "virtual") if not p.isValid(): return [] f = [f.name() for f in p.fields()] if p.geometryType() != Qgis.WKBNoGeometry: gn = getQueryGeometryName(tmp) if gn: f += [gn] return f
def __init__(self, db, sql, parent=None): # create a virtual layer with non-geometry results q = QUrl.toPercentEncoding(sql) t = QTime() t.start() tf = QTemporaryFile() tf.open() tmp = tf.fileName() tf.close() p = QgsVectorLayer("%s?query=%s" % (QUrl.fromLocalFile(tmp).toString(), q), "vv", "virtual") self._secs = t.elapsed() / 1000.0 if not p.isValid(): data = [] header = [] raise DbError(p.dataProvider().error().summary(), sql) else: header = [f.name() for f in p.fields()] has_geometry = False if p.geometryType() != QGis.WKBNoGeometry: gn = getQueryGeometryName(tmp) if gn: has_geometry = True header += [gn] data = [] for f in p.getFeatures(): a = f.attributes() if has_geometry: if f.geometry(): a += [f.geometry().exportToWkt()] else: a += [None] data += [a] self._secs = 0 self._affectedRows = len(data) BaseTableModel.__init__(self, header, data, parent)
def run(self): """Run method that performs all the real work""" self.dlg.statusBarProcessing.setValue(0) #show step1 button self.dlg.btnExecuteClippingTask.setVisible(True) #self.dlg.textEdit.clear() ## Load layers and add to comboboxes #layers = self.iface.legendInterface().layers() #layers = QgsMapLayerRegistry.instance().mapLayers().values() layers = QgsProject.instance().mapLayers().values() #Clear combobox self.dlg.comboBoxInputVector.clear() self.dlg.comboBoxInputRaster.clear() #layer_list = [] for layer in layers: #if layer.type() == QgsMapLayer.VectorLayer and (layer.wkbType()==QGis.WKBPolygon or layer.wkbType() == QGis.WKBMultiPolygon): if layer.type() == QgsMapLayer.VectorLayer and ( layer.wkbType() == QgsWkbTypes.Polygon or layer.wkbType() == QgsWkbTypes.MultiPolygon): self.dlg.comboBoxInputVector.addItem(layer.name(), layer) if layer.type() == QgsMapLayer.RasterLayer: self.dlg.comboBoxInputRaster.addItem(layer.name(), layer) #layer_list.append(layer.name()) #self.dlg.comboBoxInputVector.addItems(layer_list) self.dlg.textAboutThesis.clear() self.dlg.textAboutThesis.append( "<span><b>Crop Rows Generator (CRG) QGIS-PLUGIN</b> is part of: <br><b>AN AUTOMATIC CROP ROWS GENERATOR USING AERIAL HIGH-RESOLUTION IMAGES FOR PRECISION AGRICULTURE</b>,master research project.</span>" ) self.dlg.textAboutThesis.append( "<span>Developed in partial fulfillment of the requirements for the degree of:</span>" ) self.dlg.textAboutThesis.append( "<span><b>Magister en Ingeniería con Énfasis en Ingeniería de Sistemas y Computación</b></span>" ) strFromLocalFile = os.path.abspath( os.path.join(os.path.dirname(__file__), "help.html")) urlFromLocalFile = QUrl.fromLocalFile(strFromLocalFile) #self.dlg.webViewHelp.load(QUrl('http://www.google.com')) self.dlg.webViewHelp.load(urlFromLocalFile) str2FromLocalFile = os.path.abspath( os.path.join(os.path.dirname(__file__), "noprocessing.html")) url2FromLocalFile = QUrl.fromLocalFile(str2FromLocalFile) self.dlg.webViewApiStatus.load(url2FromLocalFile) #self.dlg.pushButtonOutputVectorFile.clicked.connect(self.fileNameDialog_Clicked) self.dlg.btnExecuteClippingTask.clicked.connect( self.executeCropRowsClipProcessing) self.dlg.btnExecuteProcessingFromApi.clicked.connect( self.executeCropRowsProcessingFromAPI) self.dlg.webViewProcessingStatus.loadFinished.connect( self.on_webViewProcessingStatus_loadFinished) self.dlg.radioButtonSeed1.clicked.connect( partial(self.displaySeedSelectedRadioOption, "Horizontal Pattern")) self.dlg.radioButtonSeed2.clicked.connect( partial(self.displaySeedSelectedRadioOption, "Vertical Pattern")) self.dlg.radioButtonSeed3.clicked.connect( partial(self.displaySeedSelectedRadioOption, "Counterclockwise Pattern")) self.dlg.radioButtonSeed4.clicked.connect( partial(self.displaySeedSelectedRadioOption, "Clockwise Pattern")) self.dlg.btnExecuteSaveConfig.clicked.connect( self.saveConfigXMLAndReloadPlugin) #wizard self.dlg.btnCancelWizard.clicked.connect(self.cancelWizard) #hide step2 button self.dlg.btnExecuteProcessingFromApi.setVisible(False) #hide processing tab self.dlg.mainTabs.setTabEnabled(1, False) self.dlg.statusBarProcessing.setGeometry(10, 281, 511, 31) nowTime = time.strftime("%c") QgsMessageLog.logMessage( '========================================================') QgsMessageLog.logMessage('Crop Rows GUI dialog starts:' + time.strftime('%c')) QgsMessageLog.logMessage( '========================================================') self.readConfigFileFromXML() #self.dlg.webViewProcessingStatus.loadProgress.connect(self.load_progress) # show the dialog #self.dlg.show() self.dlg.exec_()
def accept(self): """ This is called when the user has clicked on the "OK" button to create the chart.""" if self.layerComboBox.count() == 0: return self.readChartParams() uniqueCustomFields = [] if self.selectedRadialUnit == 5 or self.selectedCircleUnit == 5: if self.customFieldCol == -1: self.iface.messageBar().pushMessage( "", "Custom Category Filed cannot be selected because none is selected", level=Qgis.Warning, duration=3) return uniqueCustomFields = self.selectedLayer.uniqueValues( self.customFieldCol) if len(uniqueCustomFields) > 40: # We have too many categories self.iface.messageBar().pushMessage( "", "There are too many custom categories for a chart", level=Qgis.Warning, duration=3) return folder = askForFolder(self) if not folder: return # Set aside some space to accumulate the statistics data = AutoDict() rvlist = AutoDict() cvlist = AutoDict() request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) isdt = self.dtRadioButton.isChecked() attributes = [] if isdt: attributes.append(self.selectedDateTimeCol) else: attributes.extend([self.selectedDateCol, self.selectedTimeCol]) if self.customFieldCol != -1: attributes.append(self.customFieldCol) request.setSubsetOfAttributes(attributes) if isdt: col1 = self.selectedDateTimeCol col2 = self.selectedDateTimeCol else: col1 = self.selectedDateCol col2 = self.selectedTimeCol # Iterate through each feature and parse and process the date/time values iter = self.selectedLayer.getFeatures(request) for f in iter: try: if self.selectedRadialUnit == 5: rv = f[self.customFieldCol] if rv == None: continue else: rv = self.parseDateTimeValues(self.selectedRadialUnit, f[col1], f[col2]) if self.selectedCircleUnit == 5: cv = f[self.customFieldCol] if cv == None: continue else: cv = self.parseDateTimeValues(self.selectedCircleUnit, f[col1], f[col2]) except: continue rvlist[rv] += 1 cvlist[cv] += 1 data[rv][cv] += 1 if not any(cvlist) or not any(rvlist): self.iface.messageBar().pushMessage("", "Valid dates were not found", level=Qgis.Warning, duration=3) return rvrange, segCnt, rvunits = self.getUnitStr(rvlist, self.selectedRadialUnit) cvrange, bandCnt, cvunits = self.getUnitStr(cvlist, self.selectedCircleUnit) if rvunits is None or cvunits is None: self.iface.messageBar().pushMessage( "", "There is too large of a year range to create chart", level=Qgis.Warning, duration=3) return # Create the web page with all the JavaScript variables datastr = self.formatData(data, rvrange, cvrange) chartSize = self.chartInnerRadius * 2 + ( bandCnt + 1) * 2 * self.chartBandHeight + 10 # 10 is additional margin style = (".legendContainer {{\n" " height: {};\n" "}}\n" ".legend svg {{\n" " width: {}px;\n" " height: {}px;\n" "}}\n" "#chart svg {{\n" " height: {}px;\n" " width: {}px;\n" "}}\n").format(chartSize, self.legendBoxWidth, self.legendHeight, chartSize, chartSize) script = [] script.append('var segHeight={};'.format(self.chartBandHeight)) script.append('var segCnt={};'.format(segCnt)) script.append('var bandCnt={};'.format(bandCnt)) script.append('var segLabels={};'.format(rvunits)) script.append('var bandLabels={};'.format(cvunits)) script.append('var innerRadius={};'.format(self.chartInnerRadius)) script.append('var edata=[{}];'.format(datastr)) script.append('var startColor="{}";'.format(self.beginningColor)) script.append('var endColor="{}";'.format(self.endingColor)) script.append('var noDataColor="{}";'.format(self.noDataColor)) rl = '' if self.showRadialLabels: rl = '\n\t.segmentLabels(segLabels)' bl = '' if self.showBandLabels: bl = '\n\t.radialLabels(bandLabels)' ll = '' if self.showLegend: ll = '\n\t.legDivId("legend")\n\t.legendSettings({{width: {}, height: {}, legBlockWidth: {}}})\n\t.data(edata)\n'.format( self.legendBoxWidth, self.legendHeight, self.legendWidth) script.append( 'var chart = circularHeatChart()\n\t.range([startColor,endColor])\n\t.nullColor(noDataColor)\n\t.segmentHeight(segHeight)\n\t.innerRadius(innerRadius)\n\t.numSegments(segCnt){}{}{};' .format(rl, bl, ll)) script.append( 'd3.select(\'#chart\')\n\t.selectAll(\'svg\')\n\t.data([edata])\n\t.enter()\n\t.append(\'svg\')\n\t.call(chart);' ) if self.showDataValues: script.append( 'd3.selectAll("#chart path").on(\'mouseover\', function() {\n\tvar d = d3.select(this).data();\n\td3.select("#info").text(\'' + self.dataValueLabel + ' \' + d);\n});') if self.showLegend: script.append('d3.select("#legendTitle").html("' + str(self.legendEdit.text()).replace('"', '\\"') + '");\n') values = { "@TITLE@": self.chartTitle, "@STYLE@": style, "@SCRIPT@": '\n'.join(script) } template = os.path.join(os.path.dirname(__file__), "templates", "index.html") html = replaceInTemplate(template, values) filename = os.path.join(folder, "index.html") try: fout = open(filename, 'w') except: self.iface.messageBar().pushMessage("", "Error opening output file", level=Qgis.Critical, duration=3) return fout.write(html) fout.close() #Copy over the d3 libraries copyfile(os.path.join(os.path.dirname(__file__), "d3", "d3.min.js"), os.path.join(folder, "d3.min.js")) copyfile( os.path.join(os.path.dirname(__file__), "d3", "circularHeatChart.js"), os.path.join(folder, "circularHeatChart.js")) # open the web browser url = QUrl.fromLocalFile(filename).toString() webbrowser.open(url, new=2) # new=2 is a new tab if possible QMessageBox().information(self, "Date Time Heatmap", "Chart has been created")
import os from string import Template from qgis.PyQt.QtCore import QUrl from jinja2 import Environment, FileSystemLoader path = os.path.join(os.path.dirname(__file__)) env = Environment(loader=FileSystemLoader(path)) def render_tample(name, **data): template = env.get_template('{}.html'.format(name)) return template.render(**data) def get_template(name): """ Return a string Template for the given html file :param name: The name of the template to return :return: a string Template """ html = os.path.join(os.path.dirname(__file__), "{}.html".format(name)) with open(html) as f: return Template(f.read()) base = os.path.dirname(os.path.abspath(__file__)) baseurl = QUrl.fromLocalFile(base + '/')
def loadFile(self, htmlfile): self.webView.load(QUrl.fromLocalFile(htmlfile))
def exportAsPdf(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName(self, self.tr('Save Model As PDF'), '', self.tr('PDF files (*.pdf *.PDF)')) if not filename: return if not filename.lower().endswith('.pdf'): filename += '.pdf' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) printerRect = QRectF(0, 0, totalRect.width(), totalRect.height()) printer = QPrinter() printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(filename) printer.setPaperSize(QSizeF(printerRect.width(), printerRect.height()), QPrinter.DevicePixel) printer.setFullPage(True) painter = QPainter(printer) self.scene.render(painter, printerRect, totalRect) painter.end() self.bar.pushMessage("", self.tr("Successfully exported model as PDF to <a href=\"{}\">{}</a>").format(QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) self.repaintModel(controls=True)
def __init__(self, settingsdict, num_data_cols, rowheader_colwidth_percent, empty_row_between_tables, page_break_between_tables, from_active_layer, sql_table): #show the user this may take a long time... utils.start_waiting_cursor() self.nr_header_rows = 3 reportfolder = os.path.join(QDir.tempPath(), 'midvatten_reports') if not os.path.exists(reportfolder): os.makedirs(reportfolder) reportpath = os.path.join(reportfolder, "w_qual_report.html") f = codecs.open(reportpath, "wb", "utf-8") #write some initiating html rpt = r"""<head><title>%s</title></head>""" % ru( QCoreApplication.translate( 'Wqualreport', 'water quality report from Midvatten plugin for QGIS')) rpt += r""" <meta http-equiv="content-type" content="text/html; charset=utf-8" />""" #NOTE, all report data must be in 'utf-8' rpt += "<html><body>" f.write(rpt) if from_active_layer: utils.pop_up_info( ru( QCoreApplication.translate( 'CompactWqualReport', 'Check that exported number of rows are identical to expected number of rows!\nFeatures in layers from sql queries can be invalid and then excluded from the report!' )), 'Warning!') w_qual_lab_layer = qgis.utils.iface.activeLayer() if w_qual_lab_layer is None: raise utils.UsageError( ru( QCoreApplication.translate('CompactWqualReport', 'Must select a layer!'))) if not w_qual_lab_layer.selectedFeatureCount(): w_qual_lab_layer.selectAll() data = self.get_data_from_qgislayer(w_qual_lab_layer) else: data = self.get_data_from_sql(sql_table, utils.getselectedobjectnames()) report_data, num_data = self.data_to_printlist(data) utils.MessagebarAndLog.info(bar_msg=ru( QCoreApplication.translate( 'CompactWqualReport', 'Created report from %s number of rows.')) % str(num_data)) for startcol in range(1, len(report_data[0]), num_data_cols): printlist = [[row[0]] for row in report_data] for rownr, row in enumerate(report_data): printlist[rownr].extend( row[startcol:min(startcol + num_data_cols, len(row))]) filtered = [row for row in printlist if any(row[1:])] self.htmlcols = len(filtered[0]) self.WriteHTMLReport( filtered, f, rowheader_colwidth_percent, empty_row_between_tables=empty_row_between_tables, page_break_between_tables=page_break_between_tables) # write some finishing html and close the file f.write("\n</body></html>") f.close() utils.stop_waiting_cursor( ) # now this long process is done and the cursor is back as normal if report_data: QDesktopServices.openUrl(QUrl.fromLocalFile(reportpath))
def showHelp(self): """Display application help to the user.""" help_file = os.path.join( os.path.split(os.path.dirname(__file__))[0], 'help', 'index.html') QDesktopServices.openUrl(QUrl.fromLocalFile(help_file))
def exportAsImage(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName(self, self.tr('Save Model As Image'), '', self.tr('PNG files (*.png *.PNG)')) if not filename: return if not filename.lower().endswith('.png'): filename += '.png' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) imgRect = QRectF(0, 0, totalRect.width(), totalRect.height()) img = QImage(totalRect.width(), totalRect.height(), QImage.Format_ARGB32_Premultiplied) img.fill(Qt.white) painter = QPainter() painter.setRenderHint(QPainter.Antialiasing) painter.begin(img) self.scene.render(painter, imgRect, totalRect) painter.end() img.save(filename) self.bar.pushMessage("", self.tr("Successfully exported model as image to <a href=\"{}\">{}</a>").format(QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) self.repaintModel(controls=True)
def help(self): QDesktopServices.openUrl(QUrl.fromLocalFile( self.plugin_dir + "/help/html/index.html"))
def saveModel(self, saveAs): if not self.can_save(): return self.model.setName(str(self.textName.text())) self.model.setGroup(str(self.textGroup.text())) if self.model.sourceFilePath() and not saveAs: filename = self.model.sourceFilePath() else: filename, filter = QFileDialog.getSaveFileName(self, self.tr('Save Model'), ModelerUtils.modelsFolders()[0], self.tr('Processing models (*.model3 *.MODEL3)')) if filename: if not filename.endswith('.model3'): filename += '.model3' self.model.setSourceFilePath(filename) if filename: if not self.model.toFile(filename): if saveAs: QMessageBox.warning(self, self.tr('I/O error'), self.tr('Unable to save edits. Reason:\n {0}').format(str(sys.exc_info()[1]))) else: QMessageBox.warning(self, self.tr("Can't save model"), QCoreApplication.translate('QgsPluginInstallerInstallingDialog', ( "This model can't be saved in its original location (probably you do not " "have permission to do it). Please, use the 'Save as…' option.")) ) return self.update_model.emit() if saveAs: self.bar.pushMessage("", self.tr("Model was correctly saved to <a href=\"{}\">{}</a>").format(QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) else: self.bar.pushMessage("", self.tr("Model was correctly saved"), level=Qgis.Success, duration=5) self.hasChanged = False
def exportAsPython(self): filename, filter = QFileDialog.getSaveFileName(self, self.tr('Save Model As Python Script'), '', self.tr('Processing scripts (*.py *.PY)')) if not filename: return if not filename.lower().endswith('.py'): filename += '.py' text = self.model.asPythonCode() with codecs.open(filename, 'w', encoding='utf-8') as fout: fout.write(text) self.bar.pushMessage("", self.tr("Successfully exported model as python script to <a href=\"{}\">{}</a>").format(QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5)
def exportAsSvg(self): self.repaintModel(controls=False) filename, fileFilter = QFileDialog.getSaveFileName(self, self.tr('Save Model As SVG'), '', self.tr('SVG files (*.svg *.SVG)')) if not filename: return if not filename.lower().endswith('.svg'): filename += '.svg' totalRect = self.scene.itemsBoundingRect() totalRect.adjust(-10, -10, 10, 10) svgRect = QRectF(0, 0, totalRect.width(), totalRect.height()) svg = QSvgGenerator() svg.setFileName(filename) svg.setSize(QSize(totalRect.width(), totalRect.height())) svg.setViewBox(svgRect) svg.setTitle(self.model.displayName()) painter = QPainter(svg) self.scene.render(painter, svgRect, totalRect) painter.end() self.bar.pushMessage("", self.tr("Successfully exported model as SVG to <a href=\"{}\">{}</a>").format(QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) self.repaintModel(controls=True)
def help(self): # Display html help url = QUrl.fromLocalFile(self.plugin_dir + "/help" + self.translate_str("/index_en.html")).toString() webbrowser.open(url, new=2)
def open_help(self): QDesktopServices.openUrl( QUrl.fromLocalFile( os.path.join(os.path.dirname(__file__), "doc", "build", "html", "index.html")))
def setHelpPage(self, helppath): self.webView.load(QUrl.fromLocalFile(helppath))
def load_content(self): self.html_loaded = False base_url = QUrl.fromLocalFile(self.layout().project().absoluteFilePath()) self.web_page.setViewportSize(QSize(self.rect().width() * self.html_units_to_layout_units, self.rect().height() * self.html_units_to_layout_units)) self.web_page.mainFrame().setHtml(self.create_plot(), base_url)
def setHTML(self, file_path): self.html_file = file_path self.WebV.load(QUrl.fromLocalFile(self.html_file))
def __init__(self, settingsdict, num_data_cols, rowheader_colwidth_percent, empty_row_between_tables, page_break_between_tables, from_active_layer, sql_table, sort_parameters_alphabetically, sort_by_obsid, date_time_as_columns, date_time_format, method, data_column): #show the user this may take a long time... reportfolder = os.path.join(QDir.tempPath(), 'midvatten_reports') if not os.path.exists(reportfolder): os.makedirs(reportfolder) reportpath = os.path.join(reportfolder, "w_qual_report.html") f = codecs.open(reportpath, "wb", "utf-8") #write some initiating html rpt = r"""<head><title>%s</title></head>""" % ru( QCoreApplication.translate( 'Wqualreport', 'water quality report from Midvatten plugin for QGIS')) rpt += r""" <meta http-equiv="content-type" content="text/html; charset=utf-8" />""" #NOTE, all report data must be in 'utf-8' rpt += "<html><body>" f.write(rpt) if date_time_as_columns: data_columns = [ 'obsid', 'date_time', 'report', 'parameter', 'unit', data_column ] else: data_columns = [ 'obsid', 'date_time', 'parameter', 'unit', data_column ] if from_active_layer: w_qual_lab_layer = qgis.utils.iface.activeLayer() if w_qual_lab_layer is None: raise utils.UsageError( ru( QCoreApplication.translate('CompactWqualReport', 'Must select a layer!'))) if not w_qual_lab_layer.selectedFeatureCount(): w_qual_lab_layer.selectAll() df = self.get_data_from_qgislayer(w_qual_lab_layer, data_columns) else: df = self.get_data_from_sql(sql_table, utils.getselectedobjectnames(), data_columns) if date_time_as_columns: columns = ['obsid', 'date_time', 'report'] rows = ['parunit'] values = [data_column] report_data = self.data_to_printlist( df, list(columns), list(rows), values, sort_parameters_alphabetically, sort_by_obsid, method, date_time_format) else: columns = ['obsid'] rows = ['parunit', 'date_time'] values = [data_column] report_data = self.data_to_printlist( df, list(columns), list(rows), values, sort_parameters_alphabetically, sort_by_obsid, method, date_time_format) # Split the data into separate tables with the specified number of columns for startcol in range(len(rows), len(report_data[0]), num_data_cols): printlist = [row[:len(rows)] for row in report_data] for rownr, row in enumerate(report_data): printlist[rownr].extend( row[startcol:min(startcol + num_data_cols, len(row))]) filtered = [row for row in printlist if any(row[len(rows):])] self.htmlcols = len(filtered[0]) self.write_html_table( filtered, f, rowheader_colwidth_percent, empty_row_between_tables=empty_row_between_tables, page_break_between_tables=page_break_between_tables, nr_header_rows=len(columns), nr_row_header_columns=len(rows)) # write some finishing html and close the file f.write("\n</body></html>") f.close() if report_data: QDesktopServices.openUrl(QUrl.fromLocalFile(reportpath))
def open_collection(self): """Slot called when the user clicks the 'Open' button.""" collection_path = local_collection_path(self._sel_coll_id) directory_url = QUrl.fromLocalFile(str(collection_path)) QDesktopServices.openUrl(directory_url)
def updateDescription(self, current, previous): if isinstance(current, TreeResultItem): html = '<b>Algorithm</b>: {}<br><b>File path</b>: <a href="{}">{}</a>'.format(current.algorithm, QUrl.fromLocalFile(current.filename).toString(), QDir.toNativeSeparators(current.filename)) self.txtDescription.setHtml(html)
def openResult(self, item, column): QDesktopServices.openUrl(QUrl.fromLocalFile(item.filename))
def saveModel(self, saveAs) -> bool: if not self.validateSave(QgsModelDesignerDialog.SaveAction.SaveAsFile): return False model_name_matched_file_name = self.model().modelNameMatchesFilePath() if self.model().sourceFilePath() and not saveAs: filename = self.model().sourceFilePath() else: if self.model().sourceFilePath(): initial_path = Path(self.model().sourceFilePath()) elif self.model().name(): initial_path = Path(ModelerUtils.modelsFolders()[0]) / ( self.model().name() + '.model3') else: initial_path = Path(ModelerUtils.modelsFolders()[0]) filename, _ = QFileDialog.getSaveFileName( self, self.tr('Save Model'), initial_path.as_posix(), self.tr('Processing models (*.model3 *.MODEL3)')) if not filename: return False filename = QgsFileUtils.ensureFileNameHasExtension( filename, ['model3']) self.model().setSourceFilePath(filename) if not self.model().name() or self.model().name() == self.tr( 'model'): self.setModelName(Path(filename).stem) elif saveAs and model_name_matched_file_name: # if saving as, and the model name used to match the filename, then automatically update the # model name to match the new file name self.setModelName(Path(filename).stem) if not self.model().toFile(filename): if saveAs: QMessageBox.warning( self, self.tr('I/O error'), self.tr('Unable to save edits. Reason:\n {0}').format( str(sys.exc_info()[1]))) else: QMessageBox.warning( self, self.tr("Can't save model"), self. tr("This model can't be saved in its original location (probably you do not " "have permission to do it). Please, use the 'Save as…' option." )) return False self.update_model.emit() if saveAs: self.messageBar().pushMessage( "", self.tr("Model was saved to <a href=\"{}\">{}</a>").format( QUrl.fromLocalFile(filename).toString(), QDir.toNativeSeparators(filename)), level=Qgis.Success, duration=5) self.setDirty(False) return True
def openfolder(folder): """ Open a folder using the OS :param folder: The path to the folder to open. """ QDesktopServices.openUrl(QUrl.fromLocalFile(folder))
class PlotFactory(QObject): # pylint:disable=too-many-instance-attributes """ Plot factory which creates Plotly Plot objects Console usage: .. code-block:: python # create (and customize) plot settings, where # plot_type (string): 'scatter' # plot_properties (dictionary): {'x':[1,2,3], 'marker_width': 10} # layout_properties (dictionary): {'legend'; True, 'title': 'Plot Title'} settings = PlotSettings(plot_type, plot_properties, layout_properties) # create the factory, which will create plots using the specified settings factory = PlotFactory(settings) # Use the factory to build a plot output_file_path = factory.build_figure() """ # create fixed class variables as paths for local javascript files POLY_FILL_PATH = QUrl.fromLocalFile( os.path.realpath( os.path.join(os.path.dirname(__file__), '..', 'jsscripts/polyfill.min.js'))).toString() PLOTLY_PATH = QUrl.fromLocalFile( os.path.realpath( os.path.join(os.path.dirname(__file__), '..', 'jsscripts/plotly-1.52.2.min.js'))).toString() PLOT_TYPES = {t.type_name(): t for t in PlotType.__subclasses__()} plot_built = pyqtSignal() # Add function to QDate and QDateTime classes that the PlotlyJSONEncoder expects from date objects if not hasattr(QDate, 'isoformat'): QDate.isoformat = lambda d: d.toString(Qt.ISODate) if not hasattr(QDateTime, 'isoformat'): QDateTime.isoformat = lambda d: d.toString(Qt.ISODate) def __init__(self, settings: PlotSettings = None, context_generator: QgsExpressionContextGenerator = None, visible_region: QgsReferencedRectangle = None, polygon_filter: FilterRegion = None): super().__init__() if settings is None: settings = PlotSettings('scatter') self.settings = settings self.context_generator = context_generator self.raw_plot = None self.plot_path = None self.selected_features_only = self.settings.properties[ 'selected_features_only'] self.visible_region = visible_region self.polygon_filter = polygon_filter self.trace = None self.layout = None self.source_layer = QgsProject.instance().mapLayer( self.settings.source_layer_id ) if self.settings.source_layer_id else None self.rebuild() if self.source_layer: self.source_layer.layerModified.connect(self.rebuild) if self.selected_features_only: self.source_layer.selectionChanged.connect(self.rebuild) def fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() # add a new scope corresponding to the source layer -- this will potentially overwrite any other # layer scopes which may be present in the context (e.g. from atlas layers), but we need to ensure # that source layer fields and attributes are present in the context context.appendScope( self.source_layer.createExpressionContextScope()) self.settings.data_defined_properties.prepare(context) self.fetch_layout_properties(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.settings.properties.get( 'visible_features_only', False) and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.settings.properties.get( 'visible_features_only', False) and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) # Some plot types don't draw individual glyphs for each feature, but aggregate them instead. # In that case it doesn't make sense to evaluate expressions for settings like marker size or color for each # feature. Instead, the evaluation should be executed only once for these settings. aggregating = self.settings.plot_type in ['box', 'histogram'] executed = False xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['in_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead colors.append(default_value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['out_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color stroke_colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_STROKE_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] stroke_colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead stroke_colors.append(default_value.name()) executed = True self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths def fetch_layout_properties(self, context): # pylint: disable=too-many-statements """ (Re)fetches layout properties. """ title = '' if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_TITLE): default_value = self.settings.layout['title'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsString( PlotSettings.PROPERTY_TITLE, context, default_value) title = value legend_title = '' if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_LEGEND_TITLE): default_value = self.settings.layout['legend_title'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsString( PlotSettings.PROPERTY_LEGEND_TITLE, context, default_value) legend_title = value x_title = '' if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_X_TITLE): default_value = self.settings.layout['x_title'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsString( PlotSettings.PROPERTY_X_TITLE, context, default_value) x_title = value y_title = '' if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_Y_TITLE): default_value = self.settings.layout['y_title'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsString( PlotSettings.PROPERTY_Y_TITLE, context, default_value) y_title = value z_title = '' if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_Z_TITLE): default_value = self.settings.layout['z_title'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsString( PlotSettings.PROPERTY_Z_TITLE, context, default_value) z_title = value x_min = None if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_X_MIN): default_value = self.settings.layout['x_min'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_X_MIN, context, default_value) x_min = value x_max = None if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_X_MAX): default_value = self.settings.layout['x_max'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_X_MAX, context, default_value) x_max = value y_min = None if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_Y_MIN): default_value = self.settings.layout['y_min'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_Y_MIN, context, default_value) y_min = value y_max = None if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_Y_MAX): default_value = self.settings.layout['y_max'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_Y_MAX, context, default_value) y_max = value self.settings.data_defined_title = title self.settings.data_defined_legend_title = legend_title self.settings.data_defined_x_title = x_title self.settings.data_defined_y_title = y_title self.settings.data_defined_z_title = z_title self.settings.data_defined_x_min = x_min self.settings.data_defined_x_max = x_max self.settings.data_defined_y_min = y_min self.settings.data_defined_y_max = y_max def set_visible_region(self, region: QgsReferencedRectangle): """ Sets the visible region associated with the factory, possibly triggering a rebuild of a filtered plot """ if self.settings.properties.get('visible_features_only', False): self.visible_region = region self.rebuild() def rebuild(self): """ Rebuilds the plot, re-fetching current values from the layer """ if self.source_layer: self.fetch_values_from_layer() self.trace = self._build_trace() self.layout = self._build_layout() self.plot_built.emit() def _build_trace(self): """ Builds the final trace calling the go.xxx plotly method this method here is the one performing the real job From the initial object created (e.g. p = Plot(plot_type, plot_properties, layout_properties)) this methods checks the plot_type and elaborates the plot_properties dictionary passed :return: the final Plot Trace (final Plot object, AKA go.xxx plot type) """ assert self.settings.plot_type in PlotFactory.PLOT_TYPES return PlotFactory.PLOT_TYPES[self.settings.plot_type].create_trace( self.settings) def _build_layout(self): """ Builds the final layout calling the go.Layout plotly method From the initial object created (e.g. p = Plot(plot_type, plot_properties, layout_properties)) this methods checks the plot_type and elaborates the layout_properties dictionary passed :return: the final Plot Layout (final Layout object, AKA go.Layout) """ assert self.settings.plot_type in PlotFactory.PLOT_TYPES return PlotFactory.PLOT_TYPES[self.settings.plot_type].create_layout( self.settings) @staticmethod def js_callback(_): """ Returns a string that is added to the end of the plot. This string is necessary for the interaction between plot and map objects WARNING! The string ReplaceTheDiv is a default string that will be replaced in a second moment """ js_str = ''' <script> // additional js function to select and click on the data // returns the ids of the selected/clicked feature var plotly_div = document.getElementById('ReplaceTheDiv') var plotly_data = plotly_div.data // selecting function plotly_div.on('plotly_selected', function(data){ var dds = {}; dds["mode"] = 'selection' dds["type"] = data.points[0].data.type featureIds = []; featureIdsTernary = []; data.points.forEach(function(pt){ featureIds.push(parseInt(pt.id)) featureIdsTernary.push(parseInt(pt.pointNumber)) dds["id"] = featureIds dds["tid"] = featureIdsTernary }) //console.log(dds) window.status = JSON.stringify(dds) }) // clicking function plotly_div.on('plotly_click', function(data){ var featureIds = []; var dd = {}; dd["fidd"] = data.points[0].id dd["mode"] = 'clicking' // loop and create dictionary depending on plot type for(var i=0; i < data.points.length; i++){ // scatter plot if(data.points[i].data.type == 'scatter'){ dd["uid"] = data.points[i].data.uid dd["type"] = data.points[i].data.type data.points.forEach(function(pt){ dd["fid"] = pt.id }) } // pie else if(data.points[i].data.type == 'pie'){ dd["type"] = data.points[i].data.type dd["label"] = data.points[i].label dd["field"] = data.points[i].data.name console.log(data.points[i].label) console.log(data.points[i]) } // histogram else if(data.points[i].data.type == 'histogram'){ dd["type"] = data.points[i].data.type dd["uid"] = data.points[i].data.uid dd["field"] = data.points[i].data.name // correct axis orientation if(data.points[i].data.orientation == 'v'){ dd["id"] = data.points[i].x dd["bin_step"] = data.points[i].fullData.xbins.size } else { dd["id"] = data.points[i].y dd["bin_step"] = data.points[i].fullData.ybins.size } } // box plot else if(data.points[i].data.type == 'box'){ dd["uid"] = data.points[i].data.uid dd["type"] = data.points[i].data.type dd["field"] = data.points[i].data.customdata[0] // correct axis orientation if(data.points[i].data.orientation == 'v'){ dd["id"] = data.points[i].x } else { dd["id"] = data.points[i].y } } // violin plot else if(data.points[i].data.type == 'violin'){ dd["uid"] = data.points[i].data.uid dd["type"] = data.points[i].data.type dd["field"] = data.points[i].data.customdata[0] // correct axis orientation (for violin is viceversa) if(data.points[i].data.orientation == 'v'){ dd["id"] = data.points[i].x } else { dd["id"] = data.points[i].y } } // bar plot else if(data.points[i].data.type == 'bar'){ dd["uid"] = data.points[i].data.uid dd["type"] = data.points[i].data.type dd["field"] = data.points[i].data.customdata[0] // correct axis orientation if(data.points[i].data.orientation == 'v'){ dd["id"] = data.points[i].x } else { dd["id"] = data.points[i].y } } // ternary else if(data.points[i].data.type == 'scatterternary'){ dd["uid"] = data.points[i].data.uid dd["type"] = data.points[i].data.type dd["field"] = data.points[i].data.customdata dd["fid"] = data.points[i].pointNumber } } window.status = JSON.stringify(dd) }); </script>''' return js_str def build_html(self, config) -> str: """ Creates the HTML for the plot Calls the go.Figure plotly method and builds the figure object adjust the html file and add some line (including the js_string for the interaction) save the html plot file in a temporary directory and return the path that can be loaded in the QWebView This method is directly usable after the plot object has been created and the 2 methods (buildTrace and buildLayout) have been called params: config (dict): config = {'scrollZoom': True, 'editable': False} config argument is necessary to specify which buttons should appear in the plotly toolbar, if the user can edit the plot inline, etc. With this parameter is possible to hide the toolbar only in print layouts and not in the normal plot canvas. :return: the final html content representing the plot Console usage: .. code-block:: python # create the initial object settings = PlotSettings(plot_type, plot_properties, layout_properties) factory = PlotFactory(settings) # finally create the Figure html_content = factory.build_html() """ fig = go.Figure(data=self.trace, layout=self.layout) # first lines of additional html with the link to the local javascript raw_plot = '<head><meta charset="utf-8" /><script src="{}">' \ '</script><script src="{}"></script></head>'.format( self.POLY_FILL_PATH, self.PLOTLY_PATH) # set some configurations # call the plot method without all the javascript code raw_plot += plotly.offline.plot(fig, output_type='div', include_plotlyjs=False, show_link=False, config=config) # insert callback for javascript events raw_plot += self.js_callback(raw_plot) # use regex to replace the string ReplaceTheDiv with the correct plot id generated by plotly match = re.search(r'Plotly.newPlot\(\s*[\'"](.+?)[\'"]', raw_plot) substr = match.group(1) raw_plot = raw_plot.replace('ReplaceTheDiv', substr) return raw_plot def build_figure(self) -> str: """ Creates the final plot (single plot) Calls the go.Figure plotly method and builds the figure object adjust the html file and add some line (including the js_string for the interaction) save the html plot file in a temporary directory and return the path that can be loaded in the QWebView This method is directly usable after the plot object has been created and the 2 methods (buildTrace and buildLayout) have been called :return: the final html path containing the plot Console usage: .. code-block:: python # create the initial object settings = PlotSettings(plot_type, plot_properties, layout_properties) factory = PlotFactory(settings) # finally create the Figure path_to_output = factory.build_figure() """ self.plot_path = os.path.join(tempfile.gettempdir(), 'temp_plot_name.html') config = { 'scrollZoom': True, 'editable': True, 'modeBarButtonsToRemove': ['toImage', 'sendDataToCloud', 'editInChartStudio'] } with open(self.plot_path, "w", encoding="utf8") as f: f.write(self.build_html(config)) return self.plot_path def build_figures(self, plot_type, ptrace, config=None) -> str: """ Overlaps plots on the same map canvas params: plot_type (string): 'scatter' ptrace (list of Plot Traces): list of all the different Plot Traces plot_type argument in necessary for Bar and Histogram plots when the options stack is chosen. In this case the layouts of the firsts plot are deleted and only the last one is taken into account (so to have the stack option). self.layout is DELETED, so the final layout is taken from the LAST plot configuration added :return: the final html path containing the plot with the js_string for the interaction Console usage: .. code-block:: python # create the initial object settings = PlotSettings(plot_type, plot_properties, layout_properties) factory = PlotFactory(settings) # finally create the Figures path_to_output = factory.build_figures(plot_type, ptrace) """ # assign the variables from the kwargs arguments # plot_type = kwargs['plot_type'] # ptrace = kwargs['pl'] # check if the plot type and render the correct figure if plot_type in ('bar', 'histogram'): del self.layout self.layout = PlotFactory.PLOT_TYPES[plot_type].create_layout( self.settings) figures = go.Figure(data=ptrace, layout=self.layout) else: figures = go.Figure(data=ptrace, layout=self.layout) # set some configurations if config is None: config = {'scrollZoom': True, 'editable': True} # first lines of additional html with the link to the local javascript self.raw_plot = '<head><meta charset="utf-8" /><script src="{}">' \ '</script><script src="{}"></script></head>'.format( self.POLY_FILL_PATH, self.PLOTLY_PATH) # call the plot method without all the javascript code self.raw_plot += plotly.offline.plot(figures, output_type='div', include_plotlyjs=False, show_link=False, config=config) # insert callback for javascript events self.raw_plot += self.js_callback(self.raw_plot) # use regex to replace the string ReplaceTheDiv with the correct plot id generated by plotly match = re.search(r'Plotly.newPlot\(\s*[\'"](.+?)[\'"]', self.raw_plot) substr = match.group(1) self.raw_plot = self.raw_plot.replace('ReplaceTheDiv', substr) self.plot_path = os.path.join(tempfile.gettempdir(), 'temp_plot_name.html') with open(self.plot_path, "w", encoding="utf8") as f: f.write(self.raw_plot) return self.plot_path def build_sub_plots(self, grid, row, column, ptrace): # pylint:disable=too-many-arguments """ Draws plot in different plot canvases (not overlapping) params: grid (string): 'row' or 'col'. Plot are created in rows or columns row (int): number of rows (if row is selected) column (int): number of columns (if column is selected) ptrace (list of Plot Traces): list of all the different Plot Traces :return: the final html path containing the plot with the js_string for the interaction Console usage: .. code-block:: python # create the initial object settings = PlotSettings(plot_type, plot_properties, layout_properties) factory = PlotFactory(settings) # finally create the Figures path_to_output = factory.build_sub_plots('row', 1, gr, pl, tt) """ if grid == 'row': fig = subplots.make_subplots(rows=row, cols=column) for i, itm in enumerate(ptrace): fig.add_trace(itm, row, i + 1) elif grid == 'col': fig = subplots.make_subplots(rows=row, cols=column) for i, itm in enumerate(ptrace): fig.add_trace(itm, i + 1, column) # set some configurations config = {'scrollZoom': True, 'editable': True} # first lines of additional html with the link to the local javascript self.raw_plot = '<head><meta charset="utf-8" /><script src="{}"></script>' \ '<script src="{}"></script></head>'.format( self.POLY_FILL_PATH, self.PLOTLY_PATH) # call the plot method without all the javascript code self.raw_plot += plotly.offline.plot(fig, output_type='div', include_plotlyjs=False, show_link=False, config=config) # insert callback for javascript events self.raw_plot += self.js_callback(self.raw_plot) # use regex to replace the string ReplaceTheDiv with the correct plot id generated by plotly match = re.search(r'Plotly.newPlot\(\s*[\'"](.+?)[\'"]', self.raw_plot) substr = match.group(1) self.raw_plot = self.raw_plot.replace('ReplaceTheDiv', substr) self.plot_path = os.path.join(tempfile.gettempdir(), 'temp_plot_name.html') with open(self.plot_path, "w", encoding="utf8") as f: f.write(self.raw_plot) return self.plot_path def build_plot_dict(self) -> dict: """ Returns a python dictionary of the whole Figure object. This method is not used in the plugin itself, but it is used in the testing suite to avoid finding the Figure parameters with weird regex from the html :return: dictionary of the Figure object """ fig = go.Figure(data=self.trace, layout=self.layout) return fig.to_dict()
def fromLocalFile(cls, filename): return cls(QUrl.fromLocalFile(filename))
def help(self): '''Display a help page''' url = QUrl.fromLocalFile(os.path.dirname(__file__) + "/index.html").toString() webbrowser.open(url, new=2)
def __init__(self, iface, parent=None): super(MainDialog, self).__init__(parent) QDialog.__init__(self) self.setupUi(self) self.iface = iface self.previewUrl = None self.layer_search_combo = None self.exporter_combo = None self.feedback = FeedbackDialog(self) self.feedback.setModal(True) stgs = QSettings() self.restoreGeometry( stgs.value("qgis2web/MainDialogGeometry", QByteArray(), type=QByteArray)) self.verticalLayout_2.addStretch() self.horizontalLayout_6.addStretch() if stgs.value("qgis2web/previewOnStartup", Qt.Checked) == Qt.Checked: self.previewOnStartup.setCheckState(Qt.Checked) else: self.previewOnStartup.setCheckState(Qt.Unchecked) if stgs.value("qgis2web/closeFeedbackOnSuccess", Qt.Checked) == Qt.Checked: self.closeFeedbackOnSuccess.setCheckState(Qt.Checked) else: self.closeFeedbackOnSuccess.setCheckState(Qt.Unchecked) self.previewFeatureLimit.setText( stgs.value("qgis2web/previewFeatureLimit", "1000")) self.appearanceParams.setSelectionMode( QAbstractItemView.SingleSelection) self.preview = None if webkit_available: widget = QWebView() self.preview = widget try: # if os.environ["TRAVIS"]: self.preview.setPage(WebPage()) except Exception: print("Failed to set custom webpage") webview = self.preview.page() webview.setNetworkAccessManager(QgsNetworkAccessManager.instance()) self.preview.settings().setAttribute( QWebSettings.DeveloperExtrasEnabled, True) self.preview.settings().setAttribute( QWebSettings.DnsPrefetchEnabled, True) else: widget = QTextBrowser() widget.setText( self.tr('Preview is not available since QtWebKit ' 'dependency is missing on your system')) self.right_layout.insertWidget(0, widget) self.populateConfigParams(self) self.populate_layers_and_groups(self) self.populateLayerSearch() writer = WRITER_REGISTRY.createWriterFromProject() self.setStateToWriter(writer) self.exporter = EXPORTER_REGISTRY.createFromProject() self.exporter_combo.setCurrentIndex( self.exporter_combo.findText(self.exporter.name())) self.exporter_combo.currentIndexChanged.connect( self.exporterTypeChanged) self.toggleOptions() if webkit_available: if self.previewOnStartup.checkState() == Qt.Checked: self.autoUpdatePreview() self.buttonPreview.clicked.connect(self.previewMap) else: self.buttonPreview.setDisabled(True) QgsProject.instance().cleared.connect(self.reject) self.layersTree.model().dataChanged.connect(self.populateLayerSearch) self.ol3.clicked.connect(self.changeFormat) self.leaflet.clicked.connect(self.changeFormat) self.buttonExport.clicked.connect(self.saveMap) helpText = os.path.join(os.path.dirname(os.path.realpath(__file__)), "helpFile.md") self.helpField.setSource(QUrl.fromLocalFile(helpText)) if webkit_available: self.devConsole = QWebInspector(self.preview) self.devConsole.setFixedHeight(0) self.devConsole.setObjectName("devConsole") self.devConsole.setPage(self.preview.page()) self.devConsole.hide() self.right_layout.insertWidget(1, self.devConsole) self.filter = devToggleFilter() self.filter.devToggle.connect(self.showHideDevConsole) self.installEventFilter(self.filter) self.setModal(False)
def helpUrl(self): file = os.path.dirname(__file__) + '/index.html' if not os.path.exists(file): return '' return QUrl.fromLocalFile(file).toString(QUrl.FullyEncoded)
def qgis_composer_html_renderer(impact_report, component): """HTML to PDF renderer using QGIS Composer. Render using qgis composer for a given impact_report data and component context for html input. :param impact_report: ImpactReport contains data about the report that is going to be generated. :type impact_report: safe.report.impact_report.ImpactReport :param component: Contains the component metadata and context for rendering the output. :type component: safe.report.report_metadata.QgisComposerComponentsMetadata :return: Whatever type of output the component should be. .. versionadded:: 4.0 """ context = component.context # QGIS3: not used # qgis_composition_context = impact_report.qgis_composition_context # create new layout with A4 portrait page layout = QgsPrintLayout(QgsProject.instance()) page = QgsLayoutItemPage(layout) page.setPageSize('A4', orientation=QgsLayoutItemPage.Portrait) layout.pageCollection().addPage(page) if not context.html_frame_elements: # if no html frame elements at all, do not generate empty report. component.output = '' return component.output # Add HTML Frame for html_el in context.html_frame_elements: mode = html_el.get('mode') html_element = QgsLayoutItemHtml(layout) margin_left = html_el.get('margin_left', 10) margin_top = html_el.get('margin_top', 10) width = html_el.get('width', component.page_width - 2 * margin_left) height = html_el.get('height', component.page_height - 2 * margin_top) html_frame = QgsLayoutFrame(layout, html_element) html_frame.attemptSetSceneRect( QRectF(margin_left, margin_top, width, height)) html_element.addFrame(html_frame) if html_element: if mode == 'text': text = html_el.get('text') text = text if text else '' html_element.setContentMode(QgsLayoutItemHtml.ManualHtml) html_element.setResizeMode( QgsLayoutItemHtml.RepeatUntilFinished) html_element.setHtml(text) html_element.loadHtml() elif mode == 'url': url = html_el.get('url') html_element.setContentMode(QgsLayoutItemHtml.Url) html_element.setResizeMode( QgsLayoutItemHtml.RepeatUntilFinished) qurl = QUrl.fromLocalFile(url) html_element.setUrl(qurl) # Attempt on removing blank page. Notes: We assume that the blank page # will always appears in the last x page(s), not in the middle. pc = layout.pageCollection() index = pc.pageCount() while pc.pageIsEmpty(index): pc.deletePage(index) index -= 1 # process to output # in case output folder not specified if impact_report.output_folder is None: impact_report.output_folder = mkdtemp(dir=temp_dir()) component_output_path = impact_report.component_absolute_output_path( component.key) component_output = None output_format = component.output_format doc_format = QgisComposerComponentsMetadata.OutputFormat.DOC_OUTPUT template_format = QgisComposerComponentsMetadata.OutputFormat.QPT if isinstance(output_format, list): component_output = [] for i in range(len(output_format)): each_format = output_format[i] each_path = component_output_path[i] if each_format in doc_format: result_path = create_qgis_pdf_output( impact_report, each_path, layout, each_format, component) component_output.append(result_path) elif each_format == template_format: result_path = create_qgis_template_output( each_path, layout) component_output.append(result_path) elif isinstance(output_format, dict): component_output = {} for key, each_format in list(output_format.items()): each_path = component_output_path[key] if each_format in doc_format: result_path = create_qgis_pdf_output( impact_report, each_path, layout, each_format, component) component_output[key] = result_path elif each_format == template_format: result_path = create_qgis_template_output( each_path, layout) component_output[key] = result_path elif (output_format in QgisComposerComponentsMetadata.OutputFormat.SUPPORTED_OUTPUT): component_output = None if output_format in doc_format: result_path = create_qgis_pdf_output( impact_report, component_output_path, layout, output_format, component) component_output = result_path elif output_format == template_format: result_path = create_qgis_template_output( component_output_path, layout) component_output = result_path component.output = component_output return component.output
def qgis_composer_renderer(impact_report, component): """Default Map Report Renderer using QGIS Composer. Render using qgis composer for a given impact_report data and component context. :param impact_report: ImpactReport contains data about the report that is going to be generated. :type impact_report: safe.report.impact_report.ImpactReport :param component: Contains the component metadata and context for rendering the output. :type component: safe.report.report_metadata.QgisComposerComponentsMetadata :return: Whatever type of output the component should be. .. versionadded:: 4.0 """ context = component.context qgis_composition_context = impact_report.qgis_composition_context # load composition object layout = QgsPrintLayout(QgsProject.instance()) # load template main_template_folder = impact_report.metadata.template_folder # we do this condition in case custom template was found if component.template.startswith('../qgis-composer-templates/'): template_path = os.path.join(main_template_folder, component.template) else: template_path = component.template with open(template_path) as template_file: template_content = template_file.read() document = QtXml.QDomDocument() # Replace for k, v in context.substitution_map.items(): template_content = template_content.replace('[{}]'.format(k), v) document.setContent(template_content) rwcontext = QgsReadWriteContext() load_status = layout.loadFromTemplate( document, rwcontext) if not load_status: raise TemplateLoadingError( tr('Error loading template: %s') % template_path) # replace image path for img in context.image_elements: item_id = img.get('id') path = img.get('path') image = layout_item(layout, item_id, QgsLayoutItemPicture) if image and path: image.setPicturePath(path) # replace html frame for html_el in context.html_frame_elements: item_id = html_el.get('id') mode = html_el.get('mode') html_element = layout_item(layout, item_id, QgsLayoutItemHtml) if html_element: if mode == 'text': text = html_el.get('text') text = text if text else '' html_element.setContentMode(QgsLayoutItemHtml.ManualHtml) html_element.setHtml(text) html_element.loadHtml() elif mode == 'url': url = html_el.get('url') html_element.setContentMode(QgsLayoutItemHtml.Url) qurl = QUrl.fromLocalFile(url) html_element.setUrl(qurl) original_crs = impact_report.impact_function.crs destination_crs = qgis_composition_context.map_settings.destinationCrs() coord_transform = QgsCoordinateTransform(original_crs, destination_crs, QgsProject.instance()) # resize map extent for map_el in context.map_elements: item_id = map_el.get('id') split_count = map_el.get('grid_split_count') layers = [ _layer for _layer in map_el.get('layers') if isinstance( _layer, QgsMapLayer) ] map_extent_option = map_el.get('extent') composer_map = layout_item(layout, item_id, QgsLayoutItemMap) for index, _layer in enumerate(layers): # we need to check whether the layer is registered or not registered_layer = ( QgsProject.instance().mapLayer(_layer.id())) if registered_layer: if not registered_layer == _layer: layers[index] = registered_layer else: QgsProject.instance().addMapLayer(_layer) """:type: qgis.core.QgsLayoutItemMap""" if composer_map: # Search for specified map extent in the template. min_x = composer_map.extent().xMinimum() if ( impact_report.use_template_extent) else None min_y = composer_map.extent().yMinimum() if ( impact_report.use_template_extent) else None max_x = composer_map.extent().xMaximum() if ( impact_report.use_template_extent) else None max_y = composer_map.extent().yMaximum() if ( impact_report.use_template_extent) else None composer_map.setKeepLayerSet(True) layer_set = [_layer for _layer in layers if isinstance( _layer, QgsMapLayer)] composer_map.setLayers(layer_set) map_overview_extent = None if map_extent_option and isinstance( map_extent_option, QgsRectangle): # use provided map extent extent = coord_transform.transform(map_extent_option) for layer in layer_set: layer_extent = coord_transform.transform(layer.extent()) if layer.name() == map_overview['id']: map_overview_extent = layer_extent else: # if map extent not provided, try to calculate extent # from list of given layers. Combine it so all layers were # shown properly extent = QgsRectangle() extent.setMinimal() for layer in layer_set: # combine extent if different layer is provided. layer_extent = coord_transform.transform(layer.extent()) extent.combineExtentWith(layer_extent) if layer.name() == map_overview['id']: map_overview_extent = layer_extent width = extent.width() height = extent.height() longest_width = width if width > height else height half_length = longest_width / 2 margin = half_length / 5 center = extent.center() min_x = min_x or (center.x() - half_length - margin) max_x = max_x or (center.x() + half_length + margin) min_y = min_y or (center.y() - half_length - margin) max_y = max_y or (center.y() + half_length + margin) # noinspection PyCallingNonCallable square_extent = QgsRectangle(min_x, min_y, max_x, max_y) if component.key == 'population-infographic' and ( map_overview_extent): square_extent = map_overview_extent composer_map.zoomToExtent(square_extent) composer_map.invalidateCache() actual_extent = composer_map.extent() # calculate intervals for grid x_interval = actual_extent.width() / split_count composer_map.grid().setIntervalX(x_interval) y_interval = actual_extent.height() / split_count composer_map.grid().setIntervalY(y_interval) # calculate legend element for leg_el in context.map_legends: item_id = leg_el.get('id') title = leg_el.get('title') layers = [ _layer for _layer in leg_el.get('layers') if isinstance( _layer, QgsMapLayer) ] symbol_count = leg_el.get('symbol_count') column_count = leg_el.get('column_count') legend = layout_item(layout, item_id, QgsLayoutItemLegend) """:type: qgis.core.QgsLayoutItemLegend""" if legend: # set column count if column_count: legend.setColumnCount(column_count) elif symbol_count <= 7: legend.setColumnCount(1) else: legend.setColumnCount(symbol_count / 7 + 1) # set legend title if title is not None and not impact_report.legend_layers: legend.setTitle(title) # set legend root_group = legend.model().rootGroup() for _layer in layers: # we need to check whether the layer is registered or not registered_layer = ( QgsProject.instance().mapLayer(_layer.id())) if registered_layer: if not registered_layer == _layer: _layer = registered_layer else: QgsProject.instance().addMapLayer(_layer) # used for customizations tree_layer = root_group.addLayer(_layer) if impact_report.legend_layers or ( not impact_report.multi_exposure_impact_function): QgsLegendRenderer.setNodeLegendStyle( tree_layer, QgsLegendStyle.Hidden) legend.adjustBoxSize() legend.updateFilterByMap(False) # process to output # in case output folder not specified if impact_report.output_folder is None: impact_report.output_folder = mkdtemp(dir=temp_dir()) output_format = component.output_format component_output_path = impact_report.component_absolute_output_path( component.key) component_output = None doc_format = QgisComposerComponentsMetadata.OutputFormat.DOC_OUTPUT template_format = QgisComposerComponentsMetadata.OutputFormat.QPT if isinstance(output_format, list): component_output = [] for i in range(len(output_format)): each_format = output_format[i] each_path = component_output_path[i] if each_format in doc_format: result_path = create_qgis_pdf_output( impact_report, each_path, layout, each_format, component) component_output.append(result_path) elif each_format == template_format: result_path = create_qgis_template_output( each_path, layout) component_output.append(result_path) elif isinstance(output_format, dict): component_output = {} for key, each_format in list(output_format.items()): each_path = component_output_path[key] if each_format in doc_format: result_path = create_qgis_pdf_output( impact_report, each_path, layout, each_format, component) component_output[key] = result_path elif each_format == template_format: result_path = create_qgis_template_output( each_path, layout) component_output[key] = result_path elif (output_format in QgisComposerComponentsMetadata.OutputFormat.SUPPORTED_OUTPUT): component_output = None if output_format in doc_format: result_path = create_qgis_pdf_output( impact_report, component_output_path, layout, output_format, component) component_output = result_path elif output_format == template_format: result_path = create_qgis_template_output( component_output_path, layout) component_output = result_path component.output = component_output return component.output