def updatecanvas(self, canvas): """ Update the canvas object for the legend background. """ if self._lastextent == canvas.extent(): return self._lastextent = canvas.extent() if QGis.QGIS_VERSION_INT > 20200: from qgis.core import QgsMapRendererParallelJob, QgsMapSettings settings = canvas.mapSettings() extent = settings.extent() settings.setOutputSize(self.size()) settings.setExtent(extent) #settings.setFlags(QgsMapSettings.Antialiasing | QgsMapSettings.DrawLabeling ) self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self._renderimage) self.renderjob.start() else: if canvas.isDrawing(): return pixmap = QPixmap(self.size()) pixmap.fill(canvas.canvasColor()) painter = QPainter(pixmap) painter.setRenderHints(QPainter.Antialiasing) renderer = canvas.mapRenderer() renderer.render(painter) del painter self.canvasimage = pixmap.toImage() self.update()
class LegendWidget(Ui_legendsWidget, QWidget): showmap = pyqtSignal() def __init__(self, parent=None): super(LegendWidget, self).__init__(parent) self.setupUi(self) self.previewImage.mousePressEvent = self.previewImagePressEvent self.previewImage.resizeEvent = self.update self.btnExpand.pressed.connect(self.layerTree.expandAllNodes) self.btnCollapse.pressed.connect(self.layerTree.collapseAllNodes) self.canvas = None def init(self, canvas): self.canvas = canvas self.canvas.extentsChanged.connect(self.update_legend_map_data) def update_legend_map_data(self): if not self.layerTree.layerTreeModel(): return self.layerTree.layerTreeModel().setLegendMapViewData( self.canvas.mapUnitsPerPixel(), self.canvas.mapSettings().outputDpi(), self.canvas.scale()) def previewImagePressEvent(self, event): self.showmap.emit() def showEvent(self, showevent): self.canvas.renderStarting.connect(self.update) self.update() def hideEvent(self, hideevent): self.canvas.renderStarting.disconnect(self.update) def setRoot(self, root): model = QgsLayerTreeModel(root, self) model.setFlag(QgsLayerTreeModel.AllowNodeChangeVisibility) model.setFlag(QgsLayerTreeModel.ShowLegendAsTree) font = QFont() font.setPointSize(20) model.setLayerTreeNodeFont(QgsLayerTreeNode.NodeLayer, font) model.setLayerTreeNodeFont(QgsLayerTreeNode.NodeGroup, font) self.layerTree.setModel(model) for layer_node in model.rootGroup().findLayers(): layer = layer_node.layer() if layer.type() == QgsMapLayer.VectorLayer: if layer.geometryType() == QgsWkbTypes.NullGeometry: parent = layer_node.parent() parent.removeLayer(layer) def _renderimage(self): image = self.renderjob.renderedImage() self.previewImage.setPixmap(QPixmap.fromImage(image)) def update(self, *__args): settings = self.canvas.mapSettings() settings.setOutputSize(self.previewImage.size()) self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self._renderimage) self.renderjob.start()
def getmap(self): if self.canvas: settings = self.canvas.mapSettings() layers = settings.layers() if GPS.isConnected: try: gpslayer = QgsMapLayerRegistry.instance().mapLayersByName( "__gps_layer")[0] except IndexError: gpslayer = QgsVectorLayer("Point", "__gps_layer", "memory") symbol = QgsMarkerSymbolV2.createSimple({ 'name': 'circle', 'color': 'blue', "size": '5' }) gpslayer.rendererV2().setSymbol(symbol) QgsMapLayerRegistry.instance().addMapLayer(gpslayer, False) layers.append(gpslayer.id()) settings.setLayers(layers) map_pos = QgsPoint(GPS.gpsinfo("longitude"), GPS.gpsinfo("latitude")) # map_pos = QgsPoint(115.72589,-32.29597) geom = QgsGeometry.fromPoint(map_pos) feature = QgsFeature() feature.setGeometry(geom) gpslayer.startEditing() gpslayer.addFeature(feature) # gpslayer.commitChanges() self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self.rendermap) self.renderjob.start()
def createImageParams(): def finished(): def addParams(): def getJsonImages(): vmap = {} for l in self.paramProcess['layerImages']: vmap[ l.name() ] = l.source() return json.dumps( vmap ) e = self.paramProcess['canvas'].extent() imgWidth, imgHeight = image.width(), image.height() resX, resY = e.width() / imgWidth, e.height() / imgHeight self.paramProcess['json_images'] = getJsonImages() self.paramProcess['crs_map'] = self.paramProcess['canvas'].mapSettings().destinationCrs().authid() self.paramProcess['extent_map'] = e.asWktCoordinates() # xMin, yMin, xMax, yMax self.paramProcess['res'] = { 'X': resX, 'Y': resY } image = job.renderedImage() if bool( self.paramProcess['canvas'].property('retro') ): image = image.scaled( image.width() / 3, image.height() / 3 ) image = image.convertToFormat( QImage.Format_Indexed8, Qt.OrderedDither | Qt.OrderedAlphaDither ) image.save( self.paramProcess['pathfileImage'], "TIFF", 100 ) # 100: Uncompressed addParams() settings = QgsMapSettings( self.paramProcess['canvas'].mapSettings() ) settings.setBackgroundColor( QColor( Qt.transparent ) ) layers = self.paramProcess['layerImages'] if 'layerPolygon' in self.paramProcess: layers = [ self.paramProcess['layerPolygon'] ] + layers settings.setLayers( layers ) job = QgsMapRendererParallelJob( settings ) job.start() job.finished.connect( finished) job.waitForFinished()
def setMap(self): def finished(): super(SwipeMap, self).setContent( job.renderedImage(), self.canvas.extent() ) settings = QgsMapSettings( self.canvas.mapSettings() ) settings.setLayers( self.layers ) job = QgsMapRendererParallelJob( settings) job.start() job.finished.connect( finished) job.waitForFinished()
def export_data(layer: QgsVectorLayer, timezone_ids: List[str], timezone_colors: List[str], path: Path, image_height: int) -> None: """Saves the image and mapping file Args: layer (QgsVectorLayer): The layer to save timezone_ids (List[str]): A list of all timezone ids timezone_colors (List[str]): A list of all timezone colors path (Path): The folder to save the data to image_height (int): The height of the image to save """ path.mkdir(parents=True, exist_ok=True) # We write the JSON dataset by hand, so that the order of all key -> value mappings inside the # file is consistent. Using JSON functions, the dictionaray would be written to the file in a # random order, making content versioning hard as the file would completely change each time # is generated. json_file = (path / "timezones.json").resolve() print(f"Saving mappings JSON file to: {json_file.absolute()}") with open(json_file, "w") as f: f.write("{\n") last = len(timezone_ids) for i in range(0, last): f.write("\"{}\": \"{}\"".format(timezone_colors[i], timezone_ids[i])) if i < last - 1: f.write(",") f.write("\n") f.write("}\n") png_file = (path / "timezones.png").resolve() print(f"Saving PNG map to: {png_file.absolute()}") settings = QgsMapSettings() settings.setLayers([layer]) settings.setBackgroundColor(QColor(255, 255, 255)) settings.setOutputSize(QSize(image_height * 2, image_height)) settings.setExtent(layer.extent()) # Turn antialiasing off settings.setFlag(1, False) def finished() -> None: """Function to save the rendered map once it is done rendering""" img = render.renderedImage() img.save(str(png_file), "png") render = QgsMapRendererParallelJob(settings) render.finished.connect(finished) render.start() # This ensures that the program doesn't exit before the image is saved loop = QEventLoop() render.finished.connect(loop.quit) loop.exec_()
class LegendWidget(Ui_legendsWidget, QWidget): showmap = pyqtSignal() def __init__(self, parent=None): super(LegendWidget, self).__init__(parent) self.setupUi(self) self.canvasimage = QImage() self.items = {} self.framerect = QRect() self.legendareabrush = QBrush(QColor(255, 255, 255, 200)) self.legendareapen = QPen(QColor(255, 255, 255, 20)) self.legendareapen.setWidth(0.5) self.previewImage.mousePressEvent = self.previewImagePressEvent self.previewImage.resizeEvent = self.update self.btnExpand.pressed.connect(self.layerTree.expandAllNodes) self.btnCollapse.pressed.connect(self.layerTree.collapseAllNodes) self.canvas = None def init(self, canvas): self.canvas = canvas def previewImagePressEvent(self, event): self.showmap.emit() def showEvent(self, showevent): self.canvas.renderStarting.connect(self.update) self.update() def hideEvent(self, hideevent): self.canvas.renderStarting.disconnect(self.update) def setRoot(self, root): model = QgsLayerTreeModel(root, self) model.setFlag(QgsLayerTreeModel.AllowNodeChangeVisibility) model.setFlag(QgsLayerTreeModel.ShowLegendAsTree) font = QFont() font.setPointSize(20) model.setLayerTreeNodeFont(QgsLayerTreeNode.NodeLayer, font) model.setLayerTreeNodeFont(QgsLayerTreeNode.NodeGroup, font) self.layerTree.setModel(model) def _renderimage(self): image = self.renderjob.renderedImage() self.previewImage.setPixmap(QPixmap.fromImage(image)) def update(self, *__args): settings = self.canvas.mapSettings() settings.setOutputSize(self.previewImage.size()) self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self._renderimage) self.renderjob.start()
def setupEnviroment(rasterTileLayer): bb = rasterTileLayer.extent() canvas.setExtent(bb) canvas.refresh() canvas.zoomToFullExtent() canvas.refreshAllLayers() vlayer = rasterTileLayer settings = QgsMapSettings() settings.setLayers([vlayer]) settings.setBackgroundColor(QColor(255, 255, 255)) settings.setOutputSize(QSize(256, 256)) settings.setExtent(vlayer.extent()) render = QgsMapRendererParallelJob(settings) render.start() return canvas
def getmap(self): if self.canvas: if QGis.QGIS_VERSION_INT > 20200: from qgis.core import QgsMapRendererParallelJob self.renderjob = QgsMapRendererParallelJob(self.canvas.mapSettings()) self.renderjob.finished.connect(self.rendermap) self.renderjob.start() else: pixmap = QPixmap(self.canvas.size()) pixmap.fill(self.canvas.canvasColor()) painter = QPainter(pixmap) renderer = self.canvas.mapRenderer() renderer.render(painter) del painter self.scribbleArea.addMap(pixmap)
def getmap(self): if self.canvas: settings = self.canvas.mapSettings() layers = settings.layers() if GPS.isConnected: try: gpslayer = QgsMapLayerRegistry.instance().mapLayersByName("__gps_layer")[0] except IndexError: gpslayer = QgsVectorLayer("Point", "__gps_layer", "memory") symbol = QgsMarkerSymbolV2.createSimple({'name': 'circle', 'color': 'blue', "size": '5'}) gpslayer.rendererV2().setSymbol(symbol) QgsMapLayerRegistry.instance().addMapLayer(gpslayer, False) layers.append(gpslayer.id()) settings.setLayers(layers) map_pos = QgsPoint(GPS.gpsinfo("longitude"), GPS.gpsinfo("latitude")) # map_pos = QgsPoint(115.72589,-32.29597) geom = QgsGeometry.fromPoint(map_pos) feature = QgsFeature() feature.setGeometry(geom) gpslayer.startEditing() gpslayer.addFeature(feature) # gpslayer.commitChanges() self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self.rendermap) self.renderjob.start()
def render_layer(): projectpath = Path('./data/france_parts.qgs') prj = QgsProject() prj.read(str(projectpath.absolute())) layers = layers = prj.mapLayersByName('france_parts') xt = layers[0].extent() width = 1200 height = int(width * xt.height() / xt.width()) options = QgsMapSettings() options.setLayers(layers) options.setBackgroundColor(QColor(255, 255, 255)) options.setOutputSize(QSize(width, height)) options.setExtent(xt) render = QgsMapRendererParallelJob(options) render.start() render.waitForFinished() image = render.renderedImage() return image
def render(name, settings): settings.setOutputSize(IMAGE_SIZE) job = QgsMapRendererParallelJob(settings) #job = QgsMapRendererSequentialJob(settings) job.start() job.waitForFinished() image = job.renderedImage() if not os.path.exists(image_path): os.mkdir(image_path) image.save(os.path.join(image_path, name + '.png')) return job.renderingTime()
def test_render_via_job(self): """ Test rendering an annotation layer via a map render job """ layer = QgsAnnotationLayer('test', QgsAnnotationLayer.LayerOptions(QgsProject.instance().transformContext())) self.assertTrue(layer.isValid()) item = QgsAnnotationPolygonItem( QgsPolygon(QgsLineString([QgsPoint(11.5, 13), QgsPoint(12, 13), QgsPoint(12, 13.5), QgsPoint(11.5, 13)]))) item.setSymbol( QgsFillSymbol.createSimple({'color': '200,100,100', 'outline_color': 'black', 'outline_width': '2'})) item.setZIndex(1) i1_id = layer.addItem(item) item = QgsAnnotationLineItem(QgsLineString([QgsPoint(11, 13), QgsPoint(12, 13), QgsPoint(12, 15)])) item.setSymbol(QgsLineSymbol.createSimple({'color': '#ffff00', 'line_width': '3'})) item.setZIndex(2) i2_id = layer.addItem(item) item = QgsAnnotationMarkerItem(QgsPoint(12, 13)) item.setSymbol(QgsMarkerSymbol.createSimple({'color': '100,200,200', 'size': '6', 'outline_color': 'black'})) item.setZIndex(3) i3_id = layer.addItem(item) layer.setCrs(QgsCoordinateReferenceSystem('EPSG:4326')) settings = QgsMapSettings() settings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326')) settings.setExtent(QgsRectangle(10, 10, 18, 18)) settings.setOutputSize(QSize(200, 200)) settings.setLayers([layer]) job = QgsMapRendererParallelJob(settings) job.start() job.waitForFinished() # check rendered item results item_results = job.takeRenderedItemResults() item_details = item_results.renderedItems() self.assertEqual(len(item_details), 3) self.assertEqual([i.layerId() for i in item_details], [layer.id()] * 3) self.assertCountEqual([i.itemId() for i in item_details], [i1_id, i2_id, i3_id]) self.assertCountEqual( [i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(0, 0, 1, 1))], []) self.assertCountEqual( [i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(10, 10, 11, 18))], [i2_id]) self.assertCountEqual( [i.itemId() for i in item_results.renderedAnnotationItemsInBounds(QgsRectangle(10, 10, 12, 18))], [i1_id, i2_id, i3_id]) # bounds should be in map crs self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i1_id][0], QgsRectangle(11.5, 13, 12, 13.5)) self.assertEqual([i.boundingBox() for i in item_details if i.itemId() == i2_id][0], QgsRectangle(11, 13, 12, 15)) self.assertEqual([i.boundingBox().toString(1) for i in item_details if i.itemId() == i3_id][0], '11.5,12.5 : 12.5,13.5')
def render(settings): """ Render the given settings to a image and save to disk. name: The name of the final result file. settings: QgsMapSettings containing the settings to render exportpath: The folder for the images to be exported to. """ job = QgsMapRendererParallelJob(settings) #job = QgsMapRendererSequentialJob(settings) job.start() job.waitForFinished() image = job.renderedImage() return image, job.renderingTime()
def writeLegendPng(self, layer, mapTitle, mapAttr, operation): mapTitle = UTILS.normalizeName(mapTitle) mapAttr = UTILS.normalizeName(mapAttr) legendFolder = self.getPathForMap(mapTitle, mapAttr, operation) # e.g. vlayer = iface.activeLayer() options = QgsMapSettings() options.setLayers([layer]) options.setBackgroundColor(QColor(255, 128, 255)) options.setOutputSize(QSize(60, 60)) options.setExtent(layer.extent()) qgisRenderJob = QgsMapRendererParallelJob(options) def savePng(): img = qgisRenderJob.renderedImage() # save the image; e.g. img.save("/Users/myuser/render.png","png") img.save(os.path.join(legendFolder, "legend.png"), "png") qgisRenderJob.finished.connect(savePng) qgisRenderJob.start()
def renderMapToImage(mapsettings, parallel=False): """ Render current map to an image, via multi-threaded renderer :param QgsMapSettings mapsettings: :param bool parallel: Do parallel or sequential render job :rtype: QImage """ if parallel: job = QgsMapRendererParallelJob(mapsettings) else: job = QgsMapRendererSequentialJob(mapsettings) job.start() job.waitForFinished() return job.renderedImage()
def render_wms_to_image(xyz=True, extent=EXTENT, width=64, height=60): """ :type xyz: bool :type extent: tuple :type width: int :type height: int :rtype: QImage """ # p = QgsProject.instance() # p = QgsProject() uri = QgsDataSourceUri() if xyz: uri.setParam('type', 'xyz') uri.setParam('crs', 'EPSG:3857') uri.setParam('format', '') # uri.setParam('zmin', '0') # uri.setParam('zmax', '18') uri.setParam('url', XYZ_URL) else: uri.setParam('tileMatrixSet', 'GoogleMapsCompatible23') uri.setParam('crs', 'EPSG:3857') uri.setParam('format', 'image/png') uri.setParam('styles', '') uri.setParam('layers', 'Combined scene layer') uri.setParam('url', WMS_URL) # Important to do this conversion, else WMS provider will double encode; # instead of just `str(uri.encodedUri())`, which outputs "b'uri'" # This coerces QByteArray -> str ... assuming UTF-8 is valid. final_uri = bytes(uri.encodedUri()).decode("utf-8") layer = QgsRasterLayer(final_uri, "scene_layer", "wms") if not layer.isValid(): print('Layer is not valid') return QImage() # p.addMapLayer(layer) settings = QgsMapSettings() settings.setExtent(QgsRectangle(*extent)) settings.setOutputSize(QSize(width, height)) settings.setLayers([layer]) job = QgsMapRendererParallelJob(settings) job.start() # This blocks... # It should really be a QEventLoop or QTimer that checks for finished() # Any intermediate image can safely be pulled from renderedImage() job.waitForFinished() return job.renderedImage()
def render_layer(settings, layer, width, height): settings.setLayers([layer.id()]) settings.setFlags(settings.flags() ^ QgsMapSettings.Antialiasing) settings.setOutputSize(QSize(width, height)) job = QgsMapRendererParallelJob(settings) job.start() job.waitForFinished() image = job.renderedImage() # image.save(r"/media/nathan/Data/dev/qgis-term/{}.jpg".format(layer.name())) return image
def setMap(self): def finished(): super(SwipeMap, self).setContent(job.renderedImage(), self.canvas.extent()) if len(self.layers) == 0: return settings = QgsMapSettings(self.canvas.mapSettings()) settings.setLayers(self.layers) job = QgsMapRendererParallelJob(settings) job.start() job.finished.connect(finished) job.waitForFinished()
def render_qgis_map(self): logging.info("Rendering QGIS map") # Gross. Fix me if not self.settings and project: self.settings = project.map_settings # TODO We should only get visible layers here but this will do for now self.settings.setLayers(QgsMapLayerRegistry.instance().mapLayers().keys()) self.settings.setFlags(self.settings.flags() ^ QgsMapSettings.Antialiasing) logging.info(self.settings.flags()) logging.info(self.settings.testFlag(QgsMapSettings.Antialiasing)) height, width = self.mapwin.getmaxyx() logging.info("Setting output size to {}, {}".format(width, height)) self.settings.setOutputSize(QSize(width, height)) job = QgsMapRendererParallelJob(self.settings) job.start() job.waitForFinished() image = job.renderedImage() logging.info("Saving rendered image for checks...") image.save(r"F:\dev\qgis-term\render.jpg") return image
def create_image(self, extent, width, height, canvas_name, output_dir): ''' This method create an image :param extent: Extent :param width: Output image width :param height: Output image height :param canvas_name: Map name :param output_dir: Output directory for image ''' if not extent: raise_exception('extent is empty') if not width: raise_exception('width is empty') if not height: raise_exception('height is empty') if not canvas_name: raise_exception('canvas name is empty') if not output_dir: raise_exception('output_dir is empty') file_name = '{0}/{1}.png'.format(output_dir, canvas_name) settings = QgsMapSettings() settings.setLayers(iface.mapCanvas().layers()) settings.setBackgroundColor(QColor(255, 255, 255)) settings.setOutputSize(QSize(width, height)) settings.setExtent(extent) render = QgsMapRendererParallelJob(settings) render.start() render.waitForFinished() image = render.renderedImage() image.save(file_name, "png") return file_name
def _process(self): def finished(): image = job.renderedImage() if bool(self.data['canvas'].property('retro')): image = image.scaled(image.width() / 3, image.height() / 3) image = image.convertToFormat( QImage.Format_Indexed8, Qt.OrderedDither | Qt.OrderedAlphaDither) image.save(self.data['filepath'], "TIFF", 100) # 100: Uncompressed self._setParamsSendData(image) settings = QgsMapSettings(self.data['canvas'].mapSettings()) settings.setBackgroundColor(QColor(Qt.transparent)) settings.setLayers(self.data['layers']) job = QgsMapRendererParallelJob(settings) job.start() job.finished.connect(finished) job.waitForFinished()
def write_image(destination_crs, extent, filepath, imagetype): """ Save current QGIS canvas to image file. """ settings = QgsMapSettings() # build settings settings.setDestinationCrs(destination_crs) # set output crs settings.setExtent(extent) # in destination_crs layers = iface.mapCanvas().layers() # get visible layers settings.setLayers(layers) w = 1920 h = int((extent.yMaximum() - extent.yMinimum()) / (extent.xMaximum() - extent.xMinimum()) * w) settings.setOutputSize(QSize(w, h)) settings.setOutputDpi(200) render = QgsMapRendererParallelJob(settings) render.start() render.waitForFinished() image = render.renderedImage() try: image.save(filepath, imagetype) except IOError: raise QgsProcessingException(f"Image not writable at <{filepath}>")
def write_image( feedback, tex_layer, tex_pixel_size, destination_crs, destination_extent, filepath, imagetype, ): """ Save current QGIS canvas to image file. """ feedback.pushInfo("Rendering texture image (timeout in 30s)...") project = QgsProject.instance() # Get extent size in meters d = QgsDistanceArea() d.setSourceCrs( crs=destination_crs, context=QgsProject.instance().transformContext() ) p00, p10, p01 = ( QgsPointXY(destination_extent.xMinimum(), destination_extent.yMinimum()), QgsPointXY(destination_extent.xMaximum(), destination_extent.yMinimum()), QgsPointXY(destination_extent.xMinimum(), destination_extent.yMaximum()), ) wm = d.measureLine(p00, p10) # euclidean dist, extent width in m hm = d.measureLine(p00, p01) # euclidean dist, extent height in m # Image settings and texture layer choice settings = QgsMapSettings() # build settings settings.setDestinationCrs(destination_crs) # set output crs settings.setExtent(destination_extent) # in destination_crs if tex_layer: layers = (tex_layer,) # chosen texture layer else: canvas = iface.mapCanvas() layers = canvas.layers() # get visible layers wpix = int(wm / tex_pixel_size) hpix = int(hm / tex_pixel_size) settings.setOutputSize(QSize(wpix, hpix)) settings.setLayers(layers) feedback.pushInfo( f"Requested texture size: {wm:.2f}x{hm:.2f} m, {wpix}x{hpix} pixels." ) # Render and save image render = QgsMapRendererParallelJob(settings) render.start() t0 = time.time() while render.isActive(): dt = time.time() - t0 QCoreApplication.processEvents() if feedback.isCanceled(): render.cancelWithoutBlocking() return if dt >= 30.0: render.cancelWithoutBlocking() feedback.reportError("Render timed out, no texture saved.") return image = render.renderedImage() try: image.save(filepath, imagetype) except IOError: raise QgsProcessingException( f"Texture not writable to <{filepath}>, cannot proceed." ) feedback.pushInfo(f"Texture saved in {dt:.2f} seconds.")
def update_summary_sheet(self,lyr=None, force=None): ''' Creates a summary sheet with thumbnail, layer metadata and online view link ''' #create a layer snapshot and upload it to google drive if not lyr: lyr = self.lyr mapbox_style = self.service_sheet.sheet_cell('settings!A5') if not mapbox_style: logger("migrating mapbox style") self.service_sheet.set_style_mapbox(self.layer_style_to_json(self.lyr)) if not force and not self.dirty and not self.restyled: return if self.restyled: self.service_sheet.set_style_qgis(self.layer_style_to_xml(self.lyr)) self.service_sheet.set_style_sld(self.SLD_to_xml(self.lyr)) self.service_sheet.set_style_mapbox(self.layer_style_to_json(self.lyr)) self.saveMetadataState() canvas = QgsMapCanvas() canvas.resize(QSize(600,600)) canvas.setCanvasColor(Qt.white) canvas.setExtent(lyr.extent()) canvas.setLayers([lyr]) canvas.refresh() canvas.update() settings = canvas.mapSettings() settings.setLayers([lyr]) job = QgsMapRendererParallelJob(settings) job.start() job.waitForFinished() image = job.renderedImage() transparent_image = QImage(image.width(), image.height(), QImage.Format_ARGB32) transparent_image.fill(Qt.transparent) p = QPainter(transparent_image) mask = image.createMaskFromColor(QColor(255, 255, 255).rgb(), Qt.MaskInColor) p.setClipRegion(QRegion(QBitmap(QPixmap.fromImage(mask)))) p.drawPixmap(0, 0, QPixmap.fromImage(image)) p.end() tmp_path = os.path.join(self.parent.plugin_dir,self.service_sheet.name+".png") transparent_image.save(tmp_path,"PNG") image_istances = self.service_drive.list_files(mimeTypeFilter='image/png',filename=self.service_sheet.name+".png") for imagename, image_props in image_istances.items(): self.service_drive.delete_file(image_props['id']) result = self.service_drive.upload_image(tmp_path) self.service_drive.add_permission(result['id'],'anyone','reader') webLink = result['webContentLink'] #'https://drive.google.com/uc?export=view&id='+result['id'] logger("webLink:" + webLink) canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326)) worldfile = QgsMapSettingsUtils.worldFileContent(settings) lonlat_min = self.transformToWGS84(QgsPointXY(canvas.extent().xMinimum(), canvas.extent().yMinimum())) lonlat_max = self.transformToWGS84(QgsPointXY(canvas.extent().xMaximum(), canvas.extent().yMaximum())) keymap_extent = [lonlat_min.x(),lonlat_min.y(),lonlat_max.x(),lonlat_max.y()] os.remove(tmp_path) #update layer metadata summary_id = self.service_sheet.add_sheet('summary', no_grid=True) appPropsUpdate = [ ["keymap",webLink], ["worldfile",pack(worldfile)], ["keymap_extent", json.dumps(keymap_extent)] ] res = self.service_sheet.update_appProperties(self.spreadsheet_id,appPropsUpdate) self.saveMetadataState(metadata=self.get_layer_metadata()) self.parent.public_db.setKey(self.spreadsheet_id, dict(self.get_layer_metadata()+appPropsUpdate),only_update=True) #merge cells to visualize snapshot and aaply image snapshot request_body = { 'requests': [{ 'mergeCells': { "range": { "sheetId": summary_id, "startRowIndex": 9, "endRowIndex": 32, "startColumnIndex": 0, "endColumnIndex": 9, }, "mergeType": 'MERGE_ALL' } }] } self.service_sheet.service.spreadsheets().batchUpdate(spreadsheetId=self.spreadsheet_id, body=request_body).execute() self.service_sheet.set_sheet_cell('summary!A10','=IMAGE("%s",3)' % webLink) permissions = self.service_drive.file_property(self.spreadsheet_id,'permissions') for permission in permissions: if permission['type'] == 'anyone': public = True break else: public = False if public: update_range = 'summary!A9:B9' update_body = { "range": update_range, "values": [['public link', "https://enricofer.github.io/gdrive_provider/weblink/converter.html?spreadsheet_id="+self.spreadsheet_id]] } self.service_sheet.service.spreadsheets().values().update(spreadsheetId=self.spreadsheet_id,range=update_range, body=update_body, valueInputOption='USER_ENTERED').execute() #hide worksheets except summary sheets = self.service_sheet.get_sheets() #self.service_sheet.toggle_sheet('summary', sheets['summary'], hidden=None) for sheet_name,sheet_id in sheets.items(): if not sheet_name == 'summary': self.service_sheet.toggle_sheet(sheet_name, sheet_id, hidden=True)
def responseComplete(self): request = self.serverInterface().requestHandler() params = request.parameterMap() # SERVICE=RENDERGEOJSON -- we are taking over if params.get('SERVICE', '').upper() == 'RENDERGEOJSON': request.clear() try: # Parse parameters geojson = params.get('GEOJSON') if not geojson: raise ParameterError('Parameter GEOJSON must be set.') style = params.get('STYLE') if not style: raise ParameterError('Parameter STYLE must be set.') try: width = int(params.get('WIDTH')) except TypeError: raise ParameterError('Parameter WIDTH must be integer.') try: height = int(params.get('HEIGHT')) except TypeError: raise ParameterError('Parameter HEIGHT must be integer.') try: dpi = int(params.get('DPI', 96)) except TypeError: raise ParameterError('Parameter DPI must be integer.') try: minx, miny, maxx, maxy = params.get('BBOX').split(',') bbox = QgsRectangle(float(minx), float(miny), float(maxx), float(maxy)) except (ValueError, AttributeError): raise ParameterError( 'Parameter BBOX must be specified in the form `min_x,min_y,max_x,max_y`.' ) url = geojson geojson_file_name = self._resolve_url(geojson) if '$type' in style: polygon_style = self._resolve_url( style.replace('$type', 'polygons')) line_style = self._resolve_url( style.replace('$type', 'lines')) point_style = self._resolve_url( style.replace('$type', 'points')) else: polygon_style = self._resolve_url(style) line_style = polygon_style point_style = polygon_style polygon_layer = QgsVectorLayer( geojson_file_name + '|geometrytype=Polygon', 'polygons', 'ogr') self._load_style(polygon_layer, polygon_style) line_layer = QgsVectorLayer( geojson_file_name + '|geometrytype=Line', 'lines', 'ogr') self._load_style(line_layer, line_style) point_layer = QgsVectorLayer( geojson_file_name + '|geometrytype=Point', 'points', 'ogr') self._load_style(point_layer, point_style) settings = QgsMapSettings() settings.setOutputSize(QSize(width, height)) settings.setOutputDpi(dpi) settings.setExtent(bbox) settings.setLayers([polygon_layer, line_layer, point_layer]) settings.setBackgroundColor(QColor(Qt.transparent)) renderer = QgsMapRendererParallelJob(settings) event_loop = QEventLoop() renderer.finished.connect(event_loop.quit) renderer.start() event_loop.exec_() img = renderer.renderedImage() img.setDotsPerMeterX(dpi * 39.37) img.setDotsPerMeterY(dpi * 39.37) image_data = QByteArray() buf = QBuffer(image_data) buf.open(QIODevice.WriteOnly) img.save(buf, 'PNG') request.setResponseHeader('Content-type', 'image/png') request.appendBody(image_data) except ParameterError as e: QgsMessageLog.logMessage( "RenderGeojson.responseComplete :: ParameterError") request.setResponseHeader('Content-type', 'text/plain') request.appendBody(str(e).encode('utf-8')) except: QgsMessageLog.logMessage( "RenderGeojson.responseComplete :: Exception") QgsMessageLog.logMessage( "RenderGeojson.responseComplete :: {}".format( traceback.format_exc())) request.setResponseHeader('Content-type', 'text/plain') request.appendBody(b'Unhandled error') request.appendBody(traceback.format_exc().encode('utf-8'))
class DrawingPad(Ui_DrawingWindow, QWidget): def __init__(self, startimage=None, parent=None): super(DrawingPad, self).__init__(parent) self.saveAsActs = [] self.setupUi(self) self.scribbleArea = ScribbleArea() self.scribbleArea.clearImage() self.frame.layout().addWidget(self.scribbleArea) self.createActions() self.actionRedPen.trigger() self.setWindowTitle("Scribble") self.resize(500, 500) self.openImage(startimage) self.colorPickerButton.clicked.connect(self.pickcolour) self.canvas = None @property def canvas(self): return self._canvas @canvas.setter def canvas(self, value): enabled = True if value else False self.actionMapSnapshot.setEnabled(enabled) self._canvas = value @property def pixmap(self): return self.scribbleArea.pixmap @pixmap.setter def pixmap(self, value): self.scribbleArea.pixmap = value def pickcolour(self): colour = QColorDialog.getColor(Qt.black, self) self.scribbleArea.setPenColor(colour) def rendermap(self): image = self.renderjob.renderedImage() self.scribbleArea.addMapImage(image) try: gpslayer = QgsMapLayerRegistry.instance().mapLayersByName( "__gps_layer")[0] gpslayer.rollBack() except IndexError: pass def getmap(self): if self.canvas: settings = self.canvas.mapSettings() layers = settings.layers() if GPS.isConnected: try: gpslayer = QgsMapLayerRegistry.instance().mapLayersByName( "__gps_layer")[0] except IndexError: gpslayer = QgsVectorLayer("Point", "__gps_layer", "memory") symbol = QgsMarkerSymbolV2.createSimple({ 'name': 'circle', 'color': 'blue', "size": '5' }) gpslayer.rendererV2().setSymbol(symbol) QgsMapLayerRegistry.instance().addMapLayer(gpslayer, False) layers.append(gpslayer.id()) settings.setLayers(layers) map_pos = QgsPoint(GPS.gpsinfo("longitude"), GPS.gpsinfo("latitude")) # map_pos = QgsPoint(115.72589,-32.29597) geom = QgsGeometry.fromPoint(map_pos) feature = QgsFeature() feature.setGeometry(geom) gpslayer.startEditing() gpslayer.addFeature(feature) # gpslayer.commitChanges() self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self.rendermap) self.renderjob.start() def openImage(self, image): if not image is None and os.path.exists(image): self.scribbleArea.openImage(image) def saveImage(self, filename): filename = filename + ".png" log(filename) return self.scribbleArea.saveImage(filename, "png") def setPen(self, color, size=3): self.scribbleArea.setPenWidth(size) self.scribbleArea.setPenColor(color) def createActions(self): self.actionClearDrawing.triggered.connect(self.scribbleArea.clearImage) self.toolClear.setDefaultAction(self.actionClearDrawing) self.actionRedPen.triggered.connect( functools.partial(self.setPen, QtCore.Qt.red, 3)) self.toolRedPen.setDefaultAction(self.actionRedPen) self.actionBluePen.triggered.connect( functools.partial(self.setPen, QtCore.Qt.blue, 3)) self.toolBluePen.setDefaultAction(self.actionBluePen) self.actionBlackPen.triggered.connect( functools.partial(self.setPen, QtCore.Qt.black, 3)) self.toolBlackPen.setDefaultAction(self.actionBlackPen) self.actionEraser.triggered.connect( functools.partial(self.setPen, QtCore.Qt.white, 9)) self.toolEraser.setDefaultAction(self.actionEraser) self.actionMapSnapshot.triggered.connect(self.getmap) self.toolMapSnapshot.setDefaultAction(self.actionMapSnapshot) self.toolSave.setDefaultAction(self.actionSave) self.toolCancel.setDefaultAction(self.actionCancel)
class DrawingPad(Ui_DrawingWindow, QWidget): def __init__(self, startimage=None, parent=None): super(DrawingPad, self).__init__(parent) self.saveAsActs = [] self.setupUi(self) self.scribbleArea = ScribbleArea() self.scribbleArea.clearImage() self.frame.layout().addWidget(self.scribbleArea) self.createActions() self.actionRedPen.trigger() self.setWindowTitle("Scribble") self.resize(500, 500) self.openImage(startimage) self.colorPickerButton.clicked.connect(self.pickcolour) self.canvas = None @property def canvas(self): return self._canvas @canvas.setter def canvas(self, value): enabled = True if value else False self.actionMapSnapshot.setEnabled(enabled) self._canvas = value @property def pixmap(self): return self.scribbleArea.pixmap @pixmap.setter def pixmap(self, value): self.scribbleArea.pixmap = value def pickcolour(self): colour = QColorDialog.getColor(Qt.black, self) self.scribbleArea.setPenColor(colour) def rendermap(self): image = self.renderjob.renderedImage() self.scribbleArea.addMapImage(image) def getmap(self): if self.canvas: if QGis.QGIS_VERSION_INT > 20200: from qgis.core import QgsMapRendererParallelJob self.renderjob = QgsMapRendererParallelJob(self.canvas.mapSettings()) self.renderjob.finished.connect(self.rendermap) self.renderjob.start() else: pixmap = QPixmap(self.canvas.size()) pixmap.fill(self.canvas.canvasColor()) painter = QPainter(pixmap) renderer = self.canvas.mapRenderer() renderer.render(painter) del painter self.scribbleArea.addMap(pixmap) def openImage(self, image): if not image is None and os.path.exists(image): self.scribbleArea.openImage(image) def saveImage(self, filename): filename = filename + ".png" log(filename) return self.scribbleArea.saveImage(filename, "png") def setPen(self, color, size=3): self.scribbleArea.setPenWidth(size) self.scribbleArea.setPenColor(color) def createActions(self): self.actionClearDrawing.triggered.connect(self.scribbleArea.clearImage) self.toolClear.setDefaultAction(self.actionClearDrawing) self.actionRedPen.triggered.connect(functools.partial(self.setPen, QtCore.Qt.red, 3)) self.toolRedPen.setDefaultAction(self.actionRedPen) self.actionBluePen.triggered.connect(functools.partial(self.setPen, QtCore.Qt.blue, 3)) self.toolBluePen.setDefaultAction(self.actionBluePen) self.actionBlackPen.triggered.connect(functools.partial(self.setPen, QtCore.Qt.black, 3)) self.toolBlackPen.setDefaultAction(self.actionBlackPen) self.actionEraser.triggered.connect(functools.partial(self.setPen, QtCore.Qt.white, 9)) self.toolEraser.setDefaultAction(self.actionEraser) self.actionMapSnapshot.triggered.connect(self.getmap) self.toolMapSnapshot.setDefaultAction(self.actionMapSnapshot) self.toolSave.setDefaultAction(self.actionSave) self.toolCancel.setDefaultAction(self.actionCancel)
def _start_job(self): map_settings = None if self._node_type: log.debug(f'Rendering image for node type: {self._node_type}') # Not sure why this needs to be a string comparison, instead of enum if f'{self._node_type}' == 'PlanetNodeType.DAILY_SCENE': if not self._item_type_ids: log.debug('No item type_id keys list object passed') return if LOG_VERBOSE: log.debug(f'item_type_ids:\n{self._item_type_ids}') if not self._extent_json: log.debug('Extent is invalid') return if LOG_VERBOSE: log.debug(f'extent_json:\n{self._extent_json}') if not self._api_key: log.debug('No API in passed') return if self._width <= 0 or self._height <= 0: log.debug('Invalid output width or height') return log.debug(f'Starting render map setup for {self._item_key}') # noinspection PyArgumentList # p = QgsProject.instance() data_src_uri = tile_service_data_src_uri( self._item_type_ids, self._api_key, tile_hash=self._tile_hash) log.debug(f'Render data_src_uri:\n' f'{data_src_uri}') if not data_src_uri: log.debug('Invalid data source URI returned') return self._rlayer: QgsRasterLayer = \ QgsRasterLayer(data_src_uri, self._item_key, "wms") if not self._rlayer.isValid(): log.debug('Render layer is not valid') return # p.addMapLayer(rlayer, False) ext: QgsRectangle = \ qgsgeometry_from_geojson(self._extent_json).boundingBox() if ext.isEmpty(): log.debug('Extent bounding box is empty or null') return if ext.width() > ext.height(): self._height = int(ext.height() / ext.width() * self._height) elif ext.height() > ext.width(): self._width = int(ext.width() / ext.height() * self._width) # noinspection PyArgumentList transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem('EPSG:4326'), QgsCoordinateReferenceSystem(self._dest_crs), QgsProject.instance()) transform_extent = transform.transformBoundingBox(ext) if transform_extent.isEmpty(): log.debug('Transformed extent bounding box is empty or null') return map_settings = QgsMapSettings() map_settings.setExtent(transform_extent) map_settings.setOutputSize(QSize(self._width, self._height)) map_settings.setLayers([self._rlayer]) log.debug(f'QgsMapSettings set for {self._item_key}') if map_settings is not None: self._job = QgsMapRendererParallelJob(map_settings) # noinspection PyUnresolvedReferences self._job.finished.connect(self._job_finished) self._has_job = True log.debug(f'Render job initialized for {self._item_key}') else: log.debug(f'No render job initialized for {self._item_key}') self._job.start()
class PlanetQgisRenderJob(PlanetRenderJob): """ Generic wrapper class for QGIS isolated map renderer jobs """ fetchShouldCancel = pyqtSignal() # Base class for parallel and sequential jobs # _job: Type[QgsMapRendererQImageJob] _job: Optional[QgsMapRendererParallelJob] def __init__(self, item_key: str, api_key: str, extent_json: Optional[Union[str, dict]] = None, dest_crs: Optional[str] = 'EPSG:3857', item_id: Optional[str] = None, item_type: Optional[str] = None, item_type_ids: Optional[List[str]] = None, item_properties: Optional[dict] = None, node_type: Optional[PlanetNodeType] = None, image_url: Optional[str] = None, width: int = 256, height: int = 256, api_client: Optional[ClientV1] = None, cache_dir: Optional[str] = None, parent: Optional[QObject] = None): super().__init__(parent=parent) self._id = self._item_key = item_key self._api_key = api_key self._extent_json = extent_json self._dest_crs = dest_crs self._item_id = item_id self._item_type = item_type self._item_type_ids = item_type_ids self._item_properties = item_properties self._node_type = node_type self._image_url = image_url self._width = width self._height = height self._job = None self._has_job = False self._rlayer: Optional[QgsRasterLayer] = None self._tile_hash = None self._json_handler = JsonDownloadHandler() self._json_handler.aborted.connect(self._json_aborted) self._json_handler.errored.connect(self._json_errored) # connect 'finished' signal on a need-as basis self._api_client = api_client self._cache_dir = cache_dir self._watcher = PlanetCallbackWatcher( parent=self, timeout=RESPONSE_TIMEOUT) # self._watcher.responseRegistered.connect(self._some_slot) self._watcher.responseCancelled.connect(self._json_cancelled) self._watcher.responseTimedOut[int].connect(self._json_timed_out) self._watcher.responseFinished['PyQt_PyObject'].connect( self._json_tile_hash_finished_wbody) self.fetchShouldCancel.connect(self._watcher.cancel_response) def id(self) -> str: return self._id def has_job(self): return self._has_job @pyqtSlot() def start(self) -> None: if not self._api_key: log.debug('No API key, skip fetching tile hash') return None if not self._item_type_ids: log.debug('No item type:ids passed, skip fetching tile hash') return None # item_type_ids_reverse = list(self._item_type_ids) # item_type_ids_reverse.reverse() # data = {'ids': item_type_ids_reverse} data = {'ids': self._item_type_ids[::-1]} json_data = json.dumps(data) if LOG_VERBOSE: log.debug(f'json_data: {json_data}') tile_url = TILE_SERVICE_URL.format('') if USE_JSON_HANDLER: headers = dict() headers['Content-Type'] = 'application/json' bauth = bytes(f'{self._api_key}:', encoding='ascii') # auth = 'Basic {0}'.format(base64.b64encode(bauth)) # headers['Authorization'] = f'Basic {self._api_key}:' base64_auth = base64.b64encode(bauth).decode("ascii") # headers['Authorization'] = f'api-key {base64_auth}' headers['Authorization'] = f'Basic {base64_auth}' self._json_handler.finished.connect(self._json_tile_hash_finished) self._json_handler.post( tile_url, headers, data=json_data, ) else: # use async dispatcher auth = HTTPBasicAuth(self._api_key, '') self._api_client.dispatcher.session.auth = auth resp = self._api_client.dispatcher.response( api_models.Request( tile_url, self._api_client.auth, # None, body_type=api_models.JSON, method='POST', data=json_data, ) ) resp.get_body_async( handler=partial(dispatch_callback, watcher=self._watcher)) self._watcher.register_response(resp) @pyqtSlot() def _start_job(self): map_settings = None if self._node_type: log.debug(f'Rendering image for node type: {self._node_type}') # Not sure why this needs to be a string comparison, instead of enum if f'{self._node_type}' == 'PlanetNodeType.DAILY_SCENE': if not self._item_type_ids: log.debug('No item type_id keys list object passed') return if LOG_VERBOSE: log.debug(f'item_type_ids:\n{self._item_type_ids}') if not self._extent_json: log.debug('Extent is invalid') return if LOG_VERBOSE: log.debug(f'extent_json:\n{self._extent_json}') if not self._api_key: log.debug('No API in passed') return if self._width <= 0 or self._height <= 0: log.debug('Invalid output width or height') return log.debug(f'Starting render map setup for {self._item_key}') # noinspection PyArgumentList # p = QgsProject.instance() data_src_uri = tile_service_data_src_uri( self._item_type_ids, self._api_key, tile_hash=self._tile_hash) log.debug(f'Render data_src_uri:\n' f'{data_src_uri}') if not data_src_uri: log.debug('Invalid data source URI returned') return self._rlayer: QgsRasterLayer = \ QgsRasterLayer(data_src_uri, self._item_key, "wms") if not self._rlayer.isValid(): log.debug('Render layer is not valid') return # p.addMapLayer(rlayer, False) ext: QgsRectangle = \ qgsgeometry_from_geojson(self._extent_json).boundingBox() if ext.isEmpty(): log.debug('Extent bounding box is empty or null') return if ext.width() > ext.height(): self._height = int(ext.height() / ext.width() * self._height) elif ext.height() > ext.width(): self._width = int(ext.width() / ext.height() * self._width) # noinspection PyArgumentList transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem('EPSG:4326'), QgsCoordinateReferenceSystem(self._dest_crs), QgsProject.instance()) transform_extent = transform.transformBoundingBox(ext) if transform_extent.isEmpty(): log.debug('Transformed extent bounding box is empty or null') return map_settings = QgsMapSettings() map_settings.setExtent(transform_extent) map_settings.setOutputSize(QSize(self._width, self._height)) map_settings.setLayers([self._rlayer]) log.debug(f'QgsMapSettings set for {self._item_key}') if map_settings is not None: self._job = QgsMapRendererParallelJob(map_settings) # noinspection PyUnresolvedReferences self._job.finished.connect(self._job_finished) self._has_job = True log.debug(f'Render job initialized for {self._item_key}') else: log.debug(f'No render job initialized for {self._item_key}') self._job.start() @pyqtSlot() @pyqtSlot(str) def cancel(self, item_key: Optional[str] = None) -> None: self.fetchShouldCancel.emit() if self._job: self._job.cancelWithoutBlocking() log.debug('Job cancelled (without blocking)') # self.jobFinished.emit(None) # self.jobFinishedWithId.emit(self._id, None) else: log.debug('No job to cancel') # self._job = None # self._has_job = False self.jobCancelled.emit() if item_key and item_key != self._id: return self.jobCancelledWithId.emit(self._id) @pyqtSlot('PyQt_PyObject') def _json_tile_hash_finished_wbody(self, body: api_models.JSON): fetch = 'Render job JSON tile hash fetch' log.debug(f'{fetch} finished') if body is None or not hasattr(body, 'response'): log.debug(f'{fetch} failed: no response') return resp: ReqResponse = body.response log.debug(requests_response_metadata(resp)) if not resp.ok: log.debug(f'{fetch} failed: response not ok') return json_body = body.get() if 'name' in json_body: log.debug(f'{fetch} succeeded') self._tile_hash = json_body['name'] self._start_job() else: log.debug(f'{fetch} failed') return @pyqtSlot() def _json_tile_hash_finished(self): log.debug(f'Render job JSON tile hash fetch finished') self._json_handler.finished.disconnect(self._json_tile_hash_finished) json_body = self._json_handler.json if 'name' in json_body: log.debug(f'Render job JSON tile hash fetch succeeded') self._tile_hash = json_body['name'] self._start_job() else: log.debug(f'Render job JSON tile hash fetch failed') return @pyqtSlot() def _json_cancelled(self): log.debug(f'Render job JSON fetch cancelled') self.jobCancelled.emit() self.jobCancelledWithId.emit(self._id) @pyqtSlot() def _json_aborted(self): log.debug(f'Render job JSON fetch aborted') self.jobCancelled.emit() self.jobCancelledWithId.emit(self._id) @pyqtSlot() def _json_errored(self): log.debug(f'Render job JSON fetch errored') self.jobCancelled.emit() self.jobCancelledWithId.emit(self._id) @pyqtSlot() def _json_timed_out(self) -> None: log.debug(f'Render job JSON fetch timed out') self.jobTimedOut.emit() self.jobTimedOutWithId.emit(self._id) @pyqtSlot() def _job_finished(self): log.debug(f'Job rendering time (seconds): ' f'{self._job.renderingTime() / 1000}') item_path = None if self._job: img: QImage = self._job.renderedImage() if not img.isNull(): # TODO: Composite (centered) over top of full width/height # Image is unlikely to be square at this point, after # being clipped to transformed AOI bounding box. # # Or, do this as a standard operation in # PlanetThumbnailCache._thumbnail_job_finished()? cache_dir = self._cache_dir if f'{self._node_type}' in [ 'PlanetNodeType.DAILY_SCENE', ]: # Don't pollute user-defined cache with ephemeral thumbs cache_dir = TEMP_CACHE_DIR # Write .png image to cache directory item_path = os.path.join(cache_dir, f'{self._id}{THUMB_EXT}') if os.path.exists(item_path): log.debug(f'Removing existing job at:\n{item_path}') os.remove(item_path) log.debug(f'Saving thumbnail job to:\n{item_path}') img.save(item_path, 'PNG') else: log.debug('Rendered QImage is null') self.jobFinished.emit() self.jobFinishedWithId.emit(self._id, item_path) def create_job(*args, **kwargs): """Job factory for PlanetThumbnailCache""" return PlanetQgisRenderJob(*args, **kwargs)
class DrawingPad(Ui_DrawingWindow, QWidget): def __init__(self, startimage=None, parent=None): super(DrawingPad, self).__init__(parent) self.saveAsActs = [] self.setupUi(self) self.scribbleArea = ScribbleArea() self.scribbleArea.clearImage() self.frame.layout().addWidget(self.scribbleArea) self.createActions() self.actionRedPen.trigger() self.setWindowTitle("Scribble") self.resize(500, 500) self.openImage(startimage) self.colorPickerButton.clicked.connect(self.pickcolour) self.canvas = None @property def canvas(self): return self._canvas @canvas.setter def canvas(self, value): enabled = True if value else False self.actionMapSnapshot.setEnabled(enabled) self._canvas = value @property def pixmap(self): return self.scribbleArea.pixmap @pixmap.setter def pixmap(self, value): self.scribbleArea.pixmap = value def pickcolour(self): colour = QColorDialog.getColor(Qt.black, self) self.scribbleArea.setPenColor(colour) def rendermap(self): image = self.renderjob.renderedImage() self.scribbleArea.addMapImage(image) try: gpslayer = QgsMapLayerRegistry.instance().mapLayersByName("__gps_layer")[0] gpslayer.rollBack() except IndexError: pass def getmap(self): if self.canvas: settings = self.canvas.mapSettings() layers = settings.layers() if GPS.isConnected: try: gpslayer = QgsMapLayerRegistry.instance().mapLayersByName("__gps_layer")[0] except IndexError: gpslayer = QgsVectorLayer("Point", "__gps_layer", "memory") symbol = QgsMarkerSymbolV2.createSimple({'name': 'circle', 'color': 'blue', "size": '5'}) gpslayer.rendererV2().setSymbol(symbol) QgsMapLayerRegistry.instance().addMapLayer(gpslayer, False) layers.append(gpslayer.id()) settings.setLayers(layers) map_pos = QgsPoint(GPS.gpsinfo("longitude"), GPS.gpsinfo("latitude")) # map_pos = QgsPoint(115.72589,-32.29597) geom = QgsGeometry.fromPoint(map_pos) feature = QgsFeature() feature.setGeometry(geom) gpslayer.startEditing() gpslayer.addFeature(feature) # gpslayer.commitChanges() self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self.rendermap) self.renderjob.start() def openImage(self, image): if not image is None and os.path.exists(image): self.scribbleArea.openImage(image) def saveImage(self, filename): filename = filename + ".png" log(filename) return self.scribbleArea.saveImage(filename, "png") def setPen(self, color, size=3): self.scribbleArea.setPenWidth(size) self.scribbleArea.setPenColor(color) def createActions(self): self.actionClearDrawing.triggered.connect(self.scribbleArea.clearImage) self.toolClear.setDefaultAction(self.actionClearDrawing) self.actionRedPen.triggered.connect(functools.partial(self.setPen, QtCore.Qt.red, 3)) self.toolRedPen.setDefaultAction(self.actionRedPen) self.actionBluePen.triggered.connect(functools.partial(self.setPen, QtCore.Qt.blue, 3)) self.toolBluePen.setDefaultAction(self.actionBluePen) self.actionBlackPen.triggered.connect(functools.partial(self.setPen, QtCore.Qt.black, 3)) self.toolBlackPen.setDefaultAction(self.actionBlackPen) self.actionEraser.triggered.connect(functools.partial(self.setPen, QtCore.Qt.white, 9)) self.toolEraser.setDefaultAction(self.actionEraser) self.actionMapSnapshot.triggered.connect(self.getmap) self.toolMapSnapshot.setDefaultAction(self.actionMapSnapshot) self.toolSave.setDefaultAction(self.actionSave) self.toolCancel.setDefaultAction(self.actionCancel)
class LegendWidget(Ui_legendsWidget, QWidget): showmap = pyqtSignal() def __init__(self, parent=None): super(LegendWidget, self).__init__(parent) self.setupUi(self) self.canvasimage = QImage() self.items = {} self.framerect = QRect() self._lastextent = None self.legendareabrush = QBrush(QColor(255, 255, 255, 200)) self.legendareapen = QPen(QColor(255, 255, 255, 20)) self.legendareapen.setWidth(0.5) def paintEvent(self, event): def itemlist(): for layer, items in self.items.iteritems(): if len(items) == 1 and not layer.startswith("~"): yield layer, items[0][1] else: for text, icon in items: if not text or text.startswith("~"): continue yield text, icon def _drawitem(pixmap, text, itempostion): painter.drawPixmap(itempostion, pixmap) textrect = QRectF(pixmap.width() + currentx + 10, itempostion.y(), event.rect().width() - pixmap.width() - OFFSET_X, pixmap.height()) painter.drawText(textrect, text, QTextOption(Qt.AlignVCenter)) def calcitems(): font = painter.font() metrices = QFontMetrics(font) maxwidth = 0 maxheight = 0 for item, _ in itemlist(): maxwidth = max(metrices.boundingRect(item).width(), maxwidth) maxheight = max( metrices.boundingRect(item).height(), maxheight) return maxwidth, maxheight if not self.canvasimage: return painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) painter.drawImage(event.rect(), self.canvasimage) itemwidths, itemmaxheight = calcitems() OFFSET_X = 30 OFFSET_Y = itemmaxheight + 10 rect = event.rect() neededheight = (len(self.items) * OFFSET_Y) columns = 1 if neededheight > rect.height(): columns = math.ceil(neededheight / float(rect.height())) framerect = QRect(rect) framewidth = (itemwidths + OFFSET_X + ICON_SIZE.width() + 100) * columns framerect.setWidth(framewidth) painter.setBrush(self.legendareabrush) painter.setPen(self.legendareapen) painter.drawRect(framerect) self.framerect = framerect painter.setPen(Qt.black) currenty = OFFSET_Y currentx = OFFSET_X position = rect.topLeft() + QPoint(OFFSET_X, currenty) for text, icon in itemlist(): itempostion = QPoint(position) if currenty > rect.height(): currentx = itemwidths + OFFSET_X + 100 currenty = itemmaxheight + 10 itempostion.setX(currentx) itempostion.setY(currenty) _drawitem(icon, text, itempostion) currenty += OFFSET_Y position.setY(currenty + OFFSET_Y) def mousePressEvent(self, event): if self.framerect.contains(event.pos()): return self.showmap.emit() def updateitems(self, layers): self.items = {} for layer in layers: if not layer.type() == QgsMapLayer.VectorLayer: continue try: items = layer.rendererV2().legendSymbologyItems(ICON_SIZE) except AttributeError: continue self.items[layer.name()] = items self.update() def _renderimage(self): image = self.renderjob.renderedImage() self.canvasimage = image self.update() def updatecanvas(self, canvas): """ Update the canvas object for the legend background. """ if self._lastextent == canvas.extent(): return self._lastextent = canvas.extent() if QGis.QGIS_VERSION_INT > 20200: from qgis.core import QgsMapRendererParallelJob, QgsMapSettings settings = canvas.mapSettings() extent = settings.extent() settings.setOutputSize(self.size()) settings.setExtent(extent) #settings.setFlags(QgsMapSettings.Antialiasing | QgsMapSettings.DrawLabeling ) self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self._renderimage) self.renderjob.start() else: if canvas.isDrawing(): return pixmap = QPixmap(self.size()) pixmap.fill(canvas.canvasColor()) painter = QPainter(pixmap) painter.setRenderHints(QPainter.Antialiasing) renderer = canvas.mapRenderer() renderer.render(painter) del painter self.canvasimage = pixmap.toImage() self.update()
def update(self, *__args): settings = self.canvas.mapSettings() settings.setOutputSize(self.previewImage.size()) self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self._renderimage) self.renderjob.start()
def write_texture( feedback, tex_layer, tex_extent, tex_pixel_size, utm_crs, # destination_crs filepath, imagetype, ): """ Crop and save texture to image file. """ # Calc tex_extent size in meters (it is in utm) tex_extent_xm = tex_extent.xMaximum() - tex_extent.xMinimum() tex_extent_ym = tex_extent.yMaximum() - tex_extent.yMinimum() # Calc tex_extent size in pixels tex_extent_xpix = int(tex_extent_xm / tex_pixel_size) tex_extent_ypix = int(tex_extent_ym / tex_pixel_size) # Choose exporting layers if tex_layer: # use user tex layer layers = (tex_layer,) else: # no user tex layer, use map canvas canvas = iface.mapCanvas() layers = canvas.layers() # Image settings and texture layer choice settings = QgsMapSettings() # build settings settings.setDestinationCrs(utm_crs) # set output crs settings.setExtent(tex_extent) # in utm_crs settings.setOutputSize(QSize(tex_extent_xpix, tex_extent_ypix)) settings.setLayers(layers) feedback.pushInfo( f"Texture size: {tex_extent_xpix} x {tex_extent_ypix} pixels, {tex_extent_xm:.1f} x {tex_extent_ym:.1f} meters" ) # Render and save image render = QgsMapRendererParallelJob(settings) render.start() t0 = time.time() while render.isActive(): dt = time.time() - t0 QCoreApplication.processEvents() if feedback.isCanceled(): render.cancelWithoutBlocking() return if dt >= 30.0: render.cancelWithoutBlocking() feedback.reportError("Texture render timed out, no texture saved.") return image = render.renderedImage() try: image.save(filepath, imagetype) except IOError: raise QgsProcessingException( f"Texture not writable to <{filepath}>, cannot proceed." ) feedback.pushInfo(f"Saved (in {dt:.2f} s): <{filepath}>")
class LegendWidget(Ui_legendsWidget, QWidget): showmap = pyqtSignal() def __init__(self, parent=None): super(LegendWidget, self).__init__(parent) self.setupUi(self) self.canvasimage = QImage() self.items = {} self.framerect = QRect() self._lastextent = None self.legendareabrush = QBrush(QColor(255,255,255,200)) self.legendareapen = QPen(QColor(255,255,255,20)) self.legendareapen.setWidth(0.5) def paintEvent(self, event): def itemlist(): for layer, items in self.items.iteritems(): if len(items) == 1: yield layer, items[0][1] else: for text, icon in items: if not text: continue yield text, icon def _drawitem(pixmap, text, itempostion): painter.drawPixmap(itempostion, pixmap) textrect = QRectF(pixmap.width() + currentx + 10, itempostion.y(), event.rect().width() - pixmap.width() - OFFSET_X, pixmap.height()) painter.drawText(textrect, text, QTextOption(Qt.AlignVCenter)) def calcitems(): font = painter.font() metrices = QFontMetrics(font) maxwidth = 0 maxheight = 0 for item, _ in itemlist(): maxwidth = max(metrices.boundingRect(item).width(), maxwidth) maxheight = max(metrices.boundingRect(item).height(), maxheight) return maxwidth, maxheight if not self.canvasimage: return painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing) painter.drawImage(event.rect(), self.canvasimage) itemwidths, itemmaxheight = calcitems() OFFSET_X = 30 OFFSET_Y = itemmaxheight + 10 rect = event.rect() neededheight = (len(self.items) * OFFSET_Y) columns = 1 if neededheight > rect.height(): columns = math.ceil(neededheight / float(rect.height())) framerect = QRect(rect) framewidth = (itemwidths + OFFSET_X + ICON_SIZE.width() + 100) * columns framerect.setWidth(framewidth) painter.setBrush(self.legendareabrush) painter.setPen(self.legendareapen) painter.drawRect(framerect) self.framerect = framerect painter.setPen(Qt.black) currenty = OFFSET_Y currentx = OFFSET_X position = rect.topLeft() + QPoint(OFFSET_X, currenty) for text, icon in itemlist(): itempostion = QPoint(position) if currenty > rect.height(): currentx = itemwidths + OFFSET_X + 100 currenty = itemmaxheight + 10 itempostion.setX(currentx) itempostion.setY(currenty) _drawitem(icon, text, itempostion) currenty += OFFSET_Y position.setY(currenty + OFFSET_Y) def mousePressEvent(self, event): if self.framerect.contains(event.pos()): return self.showmap.emit() def updateitems(self, layers): self.items = {} for layer in layers: if not layer.type() == QgsMapLayer.VectorLayer: continue try: items = layer.rendererV2().legendSymbologyItems(ICON_SIZE) except AttributeError: continue self.items[layer.name()] = items self.update() def _renderimage(self): image = self.renderjob.renderedImage() self.canvasimage = image self.update() def updatecanvas(self, canvas): """ Update the canvas object for the legend background. """ if self._lastextent == canvas.extent(): return self._lastextent = canvas.extent() if QGis.QGIS_VERSION_INT > 20200: from qgis.core import QgsMapRendererParallelJob, QgsMapSettings settings = canvas.mapSettings() extent = settings.extent() settings.setOutputSize(self.size()) settings.setExtent(extent) #settings.setFlags(QgsMapSettings.Antialiasing | QgsMapSettings.DrawLabeling ) self.renderjob = QgsMapRendererParallelJob(settings) self.renderjob.finished.connect(self._renderimage) self.renderjob.start() else: if canvas.isDrawing(): return pixmap = QPixmap(self.size()) pixmap.fill(canvas.canvasColor()) painter = QPainter(pixmap) painter.setRenderHints(QPainter.Antialiasing) renderer = canvas.mapRenderer() renderer.render(painter) del painter self.canvasimage = pixmap.toImage() self.update()
def make_coco_dataset(self): layers = QgsProject.instance().mapLayers().values() vectorlayers = [layer for layer in layers if layer.type() == VectorLayer] rasterLayers = [layer for layer in layers if layer.type() == RasterLayer] vectorlayer = vectorlayers[self.dlg.vectorlayerselector.currentIndex()] rasterlayer = rasterLayers[self.dlg.rasterlayerselector.currentIndex()] img_size = int(self.dlg.imagesizeselector.currentText()) scale_realworld = int(self.dlg.zoomlevel.currentText()) buffer_size = int(self.dlg.buffersize.text()) / 100 target_dir = self.dlg.dirselectline.text() dataset_description = str(self.dlg.datasetdescription.text()) contributor = str(self.dlg.creatorname.text()) url = str(self.dlg.url.text()) version = str(self.dlg.datasetversion.text()) license = str(self.dlg.licenseselector.currentText()) # to implement scale_to_fit = self.dlg.scaletofit.isChecked() use_fieldcategory = bool(self.dlg.usecategoryfields.isChecked()) categoryfield = self.dlg.categoryfields.currentText() QgsMessageLog.logMessage(str(scale_to_fit), "cocotrainer", level=Qgis.Info) QgsMessageLog.logMessage(str(use_fieldcategory), "cocotrainer", level=Qgis.Info) # prepare directories os.makedirs(os.path.join(target_dir, "train"), exist_ok=True) os.makedirs(os.path.join(target_dir, "val"), exist_ok=True) QgsMessageLog.logMessage("====================================", "cocotrainer", level=Qgis.Info) # TODO: # - use field categories features_iterator = vectorlayer.getFeatures(QgsFeatureRequest().setFilterExpression('$area > 1')) features = [feature for feature in features_iterator] QgsMessageLog.logMessage("vector layer: " + vectorlayer.name(), "cocotrainer", level=Qgis.Info) QgsMessageLog.logMessage("Filtered polygons smaller than 1m2 from data", "cocotrainer", level=Qgis.Info) QgsMessageLog.logMessage("features: " + str(len(list(features))), "cocotrainer", level=Qgis.Info) options = QgsMapSettings() options.setLayers([rasterlayer]) options.setOutputSize(QSize(int(img_size), int(img_size))) QgsMessageLog.logMessage(str(scale_realworld), "cocotrainer", level=Qgis.Info) QgsMessageLog.logMessage(str(img_size), "cocotrainer", level=Qgis.Info) date = datetime.now() cat_dict = {} if use_fieldcategory: categories = [] # uniqueprovider = rasterlayer.dataProvider() fields = vectorlayer.fields() id = fields.indexFromName(categoryfield) uniquevalues = vectorlayer.uniqueValues(id) for i, val in enumerate(uniquevalues): categories.append({"supercategory": "object", "id": i, "name": str(val)}) cat_dict[str(val)] = i else: categories = [{"supercategory": "object", "id": 0, "name": "CATEGORYNAME"}] cat_dict["CATEGORYNAME"] = 0 coco_annotation = { "info": { "description": dataset_description, "url": url, "version": version, "year": int(date.strftime("%Y")), "contributor": contributor, "date_created": date.strftime("%d/%m/%y") }, "licenses": [self.license_dict[license]], "images": [], "annotations": [], "categories": categories, # < -- Not in Captionsannotations "segment_info": [] # < -- Only in Panoptic annotations } bboxes, polygons = {}, {} for i, feature in enumerate(features): polygons[i] = feature.geometry() bboxes[i] = feature.geometry().boundingBox() if use_fieldcategory: cats = {} for i, feature in enumerate(features): cats[i] = str(feature[categoryfield]) image_id = 0 annotation_id = 0 for i, feature in enumerate(features): self.dlg.progressBar.setValue((i / len(polygons) * 100)) QgsMessageLog.logMessage("something is happening", "cocotrainer", level=Qgis.Info) geoms = feature.geometry() bbox = geoms.boundingBox() if scale_to_fit: xmax = bbox.xMaximum() ymax = bbox.yMaximum() ymin = bbox.yMinimum() xmin = bbox.xMinimum() diffx = xmax - xmin diffy = ymax - ymin xmax = xmax + (buffer_size * diffx) xmin = xmin - (buffer_size * diffx) ymax = ymax + (buffer_size * diffy) ymin = ymin - (buffer_size * diffy) else: midpoint = bbox.center() QgsMessageLog.logMessage("trying to log centerpoint", "cocotrainer", level=Qgis.Info) QgsMessageLog.logMessage(str(midpoint.x()), "cocotrainer", level=Qgis.Info) QgsMessageLog.logMessage(str(midpoint.y()), "cocotrainer", level=Qgis.Info) xmin = midpoint.x() - scale_realworld / 2 xmax = midpoint.x() + scale_realworld / 2 ymin = midpoint.y() - scale_realworld / 2 ymax = midpoint.y() + scale_realworld / 2 extent = QgsRectangle(xmin, ymin, xmax, ymax) fileid = "{}_{}_{}".format(xmin, ymin, scale_realworld) image = { "license": 0, "file_name": fileid + ".png", "coco_url": None, "height": img_size, "width": img_size, "date_captured": date.strftime("%d/%m/%y"), "flickr_url": None, "id": image_id } coco_annotation["images"].append(image) for j, geo in bboxes.items(): if extent.contains(geo) or extent.intersects(geo): points = polygons[j].asMultiPolygon() xs = np.array([p.x() - xmin for p in points[0][0]]) ys = np.array([(ymax - ymin) - (p.y() - ymin) for p in points[0][0]]) xs *= (img_size / scale_realworld) xs = np.round(xs) xs = xs.astype(np.int32) ys *= (img_size / scale_realworld) ys = np.round(ys) ys = ys.astype(np.int32) ys[ys < 0] = 0 xs[xs < 0] = 0 polygon = [[int(p[0]), int(p[1])] for p in zip(xs, ys)] flat_list = [item for sublist in polygon for item in sublist] QgsMessageLog.logMessage(str(flat_list), "cocotrainer", level=Qgis.Info) pixelposbbox = [int(np.min(xs)), (int(np.min(ys))), geo.width() * (img_size / scale_realworld), geo.height() * (img_size / scale_realworld)] pixelposbbox = [number if number > 0 else 0 for number in pixelposbbox] QgsMessageLog.logMessage(str([geo.xMinimum(), geo.yMaximum(), geo.width(), geo.height()]), "cocotrainer", level=Qgis.Info) QgsMessageLog.logMessage(str(pixelposbbox), "cocotrainer", level=Qgis.Info) annotation = { "segmentation": [flat_list], # format is [x1,y1,x2,y2] "area": polygons[j].area(), "iscrowd": 0, "image_id": image_id, "bbox": pixelposbbox, # format is [top left x position, top left y position, width, height] "category_id": cat_dict[cats[j]], "id": annotation_id } annotation_id += 1 coco_annotation["annotations"].append(annotation) options.setExtent(extent) render = QgsMapRendererParallelJob(options) def finished(): QgsMessageLog.logMessage("saving image to disk", "cocotrainer", level=Qgis.Info) fname = os.path.join(target_dir, 'train', "{}.png".format(fileid)) img = render.renderedImage() if not img.save(fname, "png"): print("Error saving image") render.finished.connect(finished) render.start() render.waitForFinished() image_id += 1 QgsMessageLog.logMessage(str(coco_annotation), "cocotrainer", level=Qgis.Info) with open(os.path.join(target_dir, "annotation.json"), "w") as outfile: json.dump(coco_annotation, outfile) self.dlg.progressBar.setValue(100) self.dlg.finished_label.setText("parsed {} features".format(len(list(features))))