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_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 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 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 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 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_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 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 render_scene_to_image(item_keys: List[str], extent_json: Union[str, dict], api_key: str, node: Optional[PlanetNode] = None, width: int = 512, height: int = 512) -> QImage: img = QImage() if not item_keys: log.debug('No item type_id keys list object passed') return img if not extent_json: log.debug('Extent is invalid') return img if node and node.node_type() != NodeT.DAILY_SCENE: log.debug('Item type is not a Daily Scene') return img if not api_key: log.debug('No API in passed') return img # p = QgsProject.instance() ext: QgsRectangle = \ qgsgeometry_from_geojson(extent_json).boundingBox() if ext.width() > ext.height(): height = int(ext.height() / ext.width() * height) elif ext.height() > ext.width(): width = int(ext.width() / ext.height() * width) # noinspection PyArgumentList transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), QgsCoordinateReferenceSystem("EPSG:3857"), QgsProject.instance()) transform_extent = transform.transformBoundingBox(ext) data_src_uri = tile_service_data_src_uri(item_keys, api_key) log.debug(f'data_src_uri:\n' f'{data_src_uri}') rlayer = QgsRasterLayer(data_src_uri, "scene_layer", "wms") if not rlayer.isValid(): log.debug('Layer is not valid') return img # p.addMapLayer(rlayer) settings = QgsMapSettings() settings.setExtent(transform_extent) settings.setOutputSize(QSize(width, height)) settings.setLayers([rlayer]) 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 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 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)
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 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}>")
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 update_summary_sheet(self): ''' Creates a summary sheet with thumbnail, layer metadata and online view link ''' #create a layer snapshot and upload it to google drive mapbox_style = self.service_sheet.sheet_cell('settings!A5') if not mapbox_style: print "migrating mapbox style" self.service_sheet.set_style_mapbox(self.layer_style_to_json(self.lyr)) if not self.dirty: return canvas = QgsMapCanvas() canvas.resize(QSize(300,300)) canvas.setCanvasColor(Qt.white) canvas.setExtent(self.lyr.extent()) canvas.setLayerSet([QgsMapCanvasLayer(self.lyr)]) canvas.refresh() canvas.update() settings = canvas.mapSettings() settings.setLayers([self.lyr.id()]) job = QgsMapRendererParallelJob(settings) job.start() job.waitForFinished() image = job.renderedImage() tmp_path = os.path.join(self.parent.plugin_dir,self.service_sheet.name+".png") 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.iteritems(): print imagename, image_props['id'] self.service_drive.delete_file(image_props['id']) result = self.service_drive.upload_image(tmp_path) print "UPLOADED", result self.service_drive.add_permission(result['id'],'anyone','reader') webLink = 'https://drive.google.com/uc?export=view&id='+result['id'] os.remove(tmp_path) print 'result',result,webLink #update layer metadata summary_id = self.service_sheet.add_sheet('summary', no_grid=True) self.service_sheet.erase_cells('summary') metadata = self.get_layer_metadata() range = 'summary!A1:B8' update_body = { "range": range, "values": metadata, } print "update", self.service_sheet.service.spreadsheets().values().update(spreadsheetId=self.spreadsheet_id,range=range, body=update_body, valueInputOption='USER_ENTERED').execute() #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' } }] } print "merge", self.service_sheet.service.spreadsheets().batchUpdate(spreadsheetId=self.spreadsheet_id, body=request_body).execute() print "image", 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: range = 'summary!A9:B9' update_body = { "range": range, "values": [['public link', "https://enricofer.github.io/GooGIS2CSV/converter.html?spreadsheet_id="+self.spreadsheet_id]] } print "update_public_link", self.service_sheet.service.spreadsheets().values().update(spreadsheetId=self.spreadsheet_id,range=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.iteritems(): if not sheet_name == 'summary': print sheet_name, sheet_id self.service_sheet.toggle_sheet(sheet_name, sheet_id, hidden=True)