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
Пример #2
0
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
Пример #3
0
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()
Пример #4
0
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()
Пример #5
0
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()
Пример #6
0
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()
Пример #7
0
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()
Пример #8
0
    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
Пример #9
0
    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
Пример #10
0
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'))
Пример #13
0
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)
Пример #14
0
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.")
Пример #15
0
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}>")
Пример #16
0
    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)
Пример #17
0
    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)