def make_pdf():
    canvas = QgsMapCanvas()
    # Load our project
    QgsProject.instance().read(QFileInfo(project_path))
    bridge = QgsLayerTreeMapCanvasBridge(
        QgsProject.instance().layerTreeRoot(), canvas)
    bridge.setCanvasLayers()
    if canvas.layerCount() < 1:
        print 'No layers loaded from this project, exiting.'
        return
    print canvas.mapSettings().extent().toString()
    template_file = file(template_path)
    template_content = template_file.read()
    template_file.close()
    document = QDomDocument()
    document.setContent(template_content)
    composition = QgsComposition(canvas.mapSettings())
    # You can use this to replace any string like this [key]
    # in the template with a new value. e.g. to replace
    # [date] pass a map like this {'date': '1 Jan 2012'}
    substitution_map = {
        'DATE_TIME_START': TIME_START,
        'DATE_TIME_END': TIME_STOP}
    composition.loadFromTemplate(document, substitution_map)
    # You must set the id in the template
    map_item = composition.getComposerItemById('map')
    map_item.setMapCanvas(canvas)
    map_item.zoomToExtent(canvas.extent())
    # You must set the id in the template
    legend_item = composition.getComposerItemById('legend')
    legend_item.updateLegend()
    composition.refreshItems()
    composition.exportAsPDF(
        '/home/web/reports/pdf/%s/%s.pdf' % (TIME_SLICE, LABEL))
    QgsProject.instance().clear()
示例#2
0
    def testSubstitutionMap(self):
        """Test that we can use degree symbols in substitutions.
        """
        # Create a point and convert it to text containing a degree symbol.
        myPoint = QgsPoint(12.3, -33.33)
        myCoordinates = myPoint.toDegreesMinutesSeconds(2)
        myTokens = myCoordinates.split(',')
        myLongitude = myTokens[0]
        myLatitude = myTokens[1]
        myText = 'Latitude: %s, Longitude: %s' % (myLatitude, myLongitude)

        # Load the composition with the substitutions
        myComposition = QgsComposition(self.iface.mapCanvas().mapRenderer())
        mySubstitutionMap = {'replace-me': myText}
        myFile = os.path.join(TEST_DATA_DIR, 'template-for-substitution.qpt')
        myTemplateFile = file(myFile, 'rt')
        myTemplateContent = myTemplateFile.read()
        myTemplateFile.close()
        myDocument = QDomDocument()
        myDocument.setContent(myTemplateContent)
        myComposition.loadFromTemplate(myDocument, mySubstitutionMap)

        # We should be able to get map0
        myMap = myComposition.getComposerMapById(0)
        myMessage = ('Map 0 could not be found in template %s', myFile)
        assert myMap is not None, myMessage
示例#3
0
    def testPrintMapFromTemplate(self):
        """Test that we can get a map to render in the template."""
        myPath = os.path.join(TEST_DATA_DIR, 'landsat.tif')
        myFileInfo = QFileInfo(myPath)
        myRasterLayer = QgsRasterLayer(myFileInfo.filePath(),
                                       myFileInfo.completeBaseName())
        myRenderer = QgsMultiBandColorRenderer(
            myRasterLayer.dataProvider(), 2, 3, 4
        )
        #mRasterLayer.setRenderer( rasterRenderer )
        myPipe = myRasterLayer.pipe()
        assert myPipe.set(myRenderer), "Cannot set pipe renderer"

        QgsMapLayerRegistry.instance().addMapLayers([myRasterLayer])

        myMapRenderer = QgsMapRenderer()
        myLayerStringList = []
        myLayerStringList.append(myRasterLayer.id())
        myMapRenderer.setLayerSet(myLayerStringList)
        myMapRenderer.setProjectionsEnabled(False)

        myComposition = QgsComposition(myMapRenderer)
        myFile = os.path.join(TEST_DATA_DIR, 'template-for-substitution.qpt')
        myTemplateFile = file(myFile, 'rt')
        myTemplateContent = myTemplateFile.read()
        myTemplateFile.close()
        myDocument = QDomDocument()
        myDocument.setContent(myTemplateContent)
        myComposition.loadFromTemplate(myDocument)

        # now render the map, first zooming to the raster extents
        myMap = myComposition.getComposerMapById(0)
        myMessage = ('Map 0 could not be found in template %s', myFile)
        assert myMap is not None, myMessage

        myExtent = myRasterLayer.extent()
        myMap.setNewExtent(myExtent)

        myImagePath = os.path.join(str(QDir.tempPath()),
                                   'template_map_render_python.png')

        myPageNumber = 0
        myImage = myComposition.printPageAsRaster(myPageNumber)
        myImage.save(myImagePath)
        assert os.path.exists(myImagePath), 'Map render was not created.'

        # Not sure if this is a predictable way to test but its quicker than
        # rendering.
        myFileSize = QFileInfo(myImagePath).size()
        myExpectedFileSize = 100000
        myMessage = ('Expected file size to be greater than %s, got %s'
                     ' for %s' %
                     (myExpectedFileSize, myFileSize, myImagePath))
        assert myFileSize > myExpectedFileSize, myMessage
示例#4
0
    def testNoSubstitutionMap(self):
        """Test that we can get a map if we use no text substitutions."""
        myComposition = QgsComposition(self.iface.mapCanvas().mapRenderer())
        myFile = os.path.join(TEST_DATA_DIR, 'template-for-substitution.qpt')
        with open(myFile) as f:
            myTemplateContent = f.read()
        myDocument = QDomDocument()
        myDocument.setContent(myTemplateContent)
        myComposition.loadFromTemplate(myDocument)

        # We should be able to get map0
        myMap = myComposition.getComposerMapById(0)
        myMessage = ('Map 0 could not be found in template %s', myFile)
        assert myMap is not None, myMessage
 def testComposerHtmlAccessor(self):
     """Test that we can retrieve the ComposerHtml instance given an item.
     """
     myComposition = QgsComposition(self.iface.mapCanvas().mapRenderer())
     mySubstitutionMap = {'replace-me': 'Foo bar'}
     myFile = os.path.join(TEST_DATA_DIR, 'template.qpt')
     with open(myFile, 'rt') as myTemplateFile:
         myTemplateContent = myTemplateFile.read()
     myDocument = QDomDocument()
     myDocument.setContent(myTemplateContent)
     myComposition.loadFromTemplate(myDocument, mySubstitutionMap)
     myItem = myComposition.getComposerItemById('html-test')
     myComposerHtml = myComposition.getComposerHtmlByItem(myItem)
     myMessage = 'Could not retrieve the composer html given an item'
     self.assertIsNotNone(myComposerHtml, myMessage)
示例#6
0
 def export_pdf(self, title=''):
     '''
     Export Composition (map view and checked layers) to PDF
     '''
     title = self.active_scenario.name if self.active_scenario else ''
     dialog = ExportPDFDialog(title=title, parent=self)
     result = dialog.exec_()
     ok = result == QtGui.QDialog.Accepted
     if not ok:
         return
     title = dialog.title
     date = dialog.date
     filepath = browse_file(None, 'Export', PDF_FILTER, save=True, 
                            parent=self)
     if not filepath:
         return
     bridge = QgsLayerTreeMapCanvasBridge(
         QgsProject.instance().layerTreeRoot(), self.canvas)
     bridge.setCanvasLayers()
 
     template_file = file(REPORT_TEMPLATE_PATH)
     template_content = template_file.read()
     template_file.close()
     document = QDomDocument()
     document.setContent(template_content)
     composition = QgsComposition(self.canvas.mapSettings())
     # You can use this to replace any string like this [key]
     # in the template with a new value. e.g. to replace
     # [date] pass a map like this {'date': '1 Jan 2012'}
     substitution_map = {
         'TITLE': title,
         'DATE_TIME': date}
     composition.loadFromTemplate(document, substitution_map)
     # You must set the id in the template
     map_item = composition.getComposerItemById('map')
     map_item.setMapCanvas(self.canvas)
     map_item.zoomToExtent(self.canvas.extent())
     # You must set the id in the template
     legend_item = composition.getComposerItemById('legend')
     legend_item.updateLegend()
     composition.refreshItems()
     composition.exportAsPDF(filepath)
     if sys.platform.startswith('darwin'):
         subprocess.call(('open', filepath))
     elif os.name == 'nt':
         os.startfile(filepath)
     elif os.name == 'posix':
         subprocess.call(('xdg-open', filepath))
示例#7
0
 def testComposerHtmlAccessor(self):
     """Test that we can retrieve the ComposerHtml instance given an item.
     """
     myComposition = QgsComposition(CANVAS.mapRenderer())
     mySubstitutionMap = {"replace-me": "Foo bar"}
     myFile = os.path.join(TEST_DATA_DIR, "template.qpt")
     myTemplateFile = file(myFile, "rt")
     myTemplateContent = myTemplateFile.read()
     myTemplateFile.close()
     myDocument = QDomDocument()
     myDocument.setContent(myTemplateContent)
     myComposition.loadFromTemplate(myDocument, mySubstitutionMap)
     myItem = myComposition.getComposerItemById("html-test")
     myComposerHtml = myComposition.getComposerHtmlByItem(myItem)
     myMessage = "Could not retrieve the composer html given an item"
     assert myComposerHtml is not None, myMessage
示例#8
0
    def testPrintMapFromTemplate(self):
        """Test that we can get a map to render in the template."""
        myPath = os.path.join(TEST_DATA_DIR, 'landsat.tif')
        myFileInfo = QFileInfo(myPath)
        myRasterLayer = QgsRasterLayer(myFileInfo.filePath(),
                                       myFileInfo.completeBaseName())
        myRenderer = QgsMultiBandColorRenderer(myRasterLayer.dataProvider(), 2,
                                               3, 4)
        # mRasterLayer.setRenderer( rasterRenderer )
        myPipe = myRasterLayer.pipe()
        assert myPipe.set(myRenderer), "Cannot set pipe renderer"

        QgsProject.instance().addMapLayers([myRasterLayer])

        myComposition = QgsComposition(QgsProject.instance())
        myFile = os.path.join(TEST_DATA_DIR, 'template-for-substitution.qpt')
        with open(myFile) as f:
            myTemplateContent = f.read()
        myDocument = QDomDocument()
        myDocument.setContent(myTemplateContent)
        myComposition.loadFromTemplate(myDocument)

        # now render the map, first zooming to the raster extents
        myMap = myComposition.getComposerMapById(0)
        myMessage = ('Map 0 could not be found in template %s', myFile)
        assert myMap is not None, myMessage

        myExtent = myRasterLayer.extent()
        myMap.setNewExtent(myExtent)
        myMap.setLayers([myRasterLayer])

        myImagePath = os.path.join(str(QDir.tempPath()),
                                   'template_map_render_python.png')

        myPageNumber = 0
        myImage = myComposition.printPageAsRaster(myPageNumber)
        myImage.save(myImagePath)
        assert os.path.exists(myImagePath), 'Map render was not created.'

        # Not sure if this is a predictable way to test but its quicker than
        # rendering.
        myFileSize = QFileInfo(myImagePath).size()
        myExpectedFileSize = 100000
        myMessage = ('Expected file size to be greater than %s, got %s'
                     ' for %s' % (myExpectedFileSize, myFileSize, myImagePath))
        assert myFileSize > myExpectedFileSize, myMessage
    def _load_template(self):
        """Load the template.

        :return: QgsComposition containing the loaded template.
        :rtype: QgsComposition
        """
        template_file = file(self.template_path)
        template_content = template_file.read()
        template_file.close()
        document = QDomDocument()
        document.setContent(template_content)
        composition = QgsComposition(self.canvas.mapSettings())
        # You can use this to replace any string like this [key]
        # in the template with a new value. e.g. to replace
        # [date] pass a map like this {'date': '1 Jan 2012'}
        substitution_map = {'DATE_TIME_START': 'foo', 'DATE_TIME_END': 'bar'}
        composition.loadFromTemplate(document, substitution_map)
        return composition
示例#10
0
    def from_file(cls, template_path, mapsettings, data=None):
        """
        Create a template object from the given template path, map setttings, and data.
        :param template_path: The template path.
        :param mapsettings: QgsMapSettings used to render the map.
        :param data: A dict of data to use for labels.
        :return: A ComposerTemplate obect which wraps the created template.
        """
        if not data:
            data = {}

        with open(template_path) as f:
            template_content = f.read()

        document = QDomDocument()
        document.setContent(template_content)
        composition = QgsComposition(mapsettings)
        composition.loadFromTemplate(document, data)
        return cls(composition)
示例#11
0
    def from_file(cls, template_path, mapsettings, data=None):
        """
        Create a template object from the given template path, map setttings, and data.
        :param template_path: The template path.
        :param mapsettings: QgsMapSettings used to render the map.
        :param data: A dict of data to use for labels.
        :return: A ComposerTemplate obect which wraps the created template.
        """
        if not data:
            data = {}

        with open(template_path) as f:
            template_content = f.read()

        document = QDomDocument()
        document.setContent(template_content)
        composition = QgsComposition(mapsettings)
        composition.loadFromTemplate(document, data)
        return cls(composition)
    def _load_template(self):
        """Load the template.

        :return: QgsComposition containing the loaded template.
        :rtype: QgsComposition
        """
        template_file = file(self.template_path)
        template_content = template_file.read()
        template_file.close()
        document = QDomDocument()
        document.setContent(template_content)
        composition = QgsComposition(self.canvas.mapSettings())
        # You can use this to replace any string like this [key]
        # in the template with a new value. e.g. to replace
        # [date] pass a map like this {'date': '1 Jan 2012'}
        substitution_map = {
            'DATE_TIME_START': 'foo',
            'DATE_TIME_END': 'bar'}
        composition.loadFromTemplate(document, substitution_map)
        return composition
示例#13
0
    def loadTemplate(self, template):
        '''Load a print composer template from provided filename argument

        Args:
          template: readable .qpt template filename

        Returns:
          myComposition: a QgsComposition loaded from the provided template
          mapSettings: a QgsMapSettings object associated with myComposition'''
        mapSettings = QgsMapSettings()
        myComposition = QgsComposition(mapSettings)
        # Load template from filename
        with open(template, 'r') as templateFile:
            myTemplateContent = templateFile.read()

        myDocument = QDomDocument()
        myDocument.setContent(myTemplateContent)
        myComposition.loadFromTemplate(myDocument)

        return myComposition, mapSettings
示例#14
0
def make_pdf():
    canvas = QgsMapCanvas()
    # Load our project
    QgsProject.instance().read(QFileInfo(project_path))
    bridge = QgsLayerTreeMapCanvasBridge(QgsProject.instance().layerTreeRoot(),
                                         canvas)
    bridge.setCanvasLayers()
    if canvas.layerCount() < 1:
        print 'No layers loaded from this project, exiting.'
        return
    print canvas.mapSettings().extent().toString()
    template_file = file(template_path)
    template_content = template_file.read()
    template_file.close()
    document = QDomDocument()
    document.setContent(template_content)
    composition = QgsComposition(canvas.mapSettings())
    # You can use this to replace any string like this [key]
    # in the template with a new value. e.g. to replace
    # [date] pass a map like this {'date': '1 Jan 2012'}
    substitution_map = {
        'DATE_TIME_START': TIME_START,
        'DATE_TIME_END': TIME_STOP
    }
    composition.loadFromTemplate(document, substitution_map)
    # You must set the id in the template
    map_item = composition.getComposerItemById('map')
    map_item.setMapCanvas(canvas)
    map_item.zoomToExtent(canvas.extent())
    # You must set the id in the template
    legend_item = composition.getComposerItemById('legend')
    legend_item.updateLegend()
    composition.refreshItems()
    composition.exportAsPDF('/home/web/reports/pdf/%s/%s.pdf' %
                            (TIME_SLICE, LABEL))
    QgsProject.instance().clear()
示例#15
0
    def load_composition(self):
        """ Creates the composition object (which is needed for creating the file) from the template file.
            
            :returns: the composition object
            :rtype: QgsComposition
        """
        template_path = get_plugin_path() + self.templates[
            self.dockwidget.comboBox_template.currentText()]
        template_file = open(template_path, "r")
        content = template_file.read()
        template_file.close()

        # the method from QgsComposition for loading the template needs to be a QDomDocument
        document = QDomDocument()
        document.setContent(content)

        # composition = QgsComposition(iface.mapCanvas().mapSettings()) does not work
        #TODO: is deprecated but works ...fix in new version https://hub.qgis.org/issues/11077
        composition = QgsComposition(iface.mapCanvas().mapRenderer())
        if not composition.loadFromTemplate(document):
            iface.messageBar().pushMessage("Error while loading template!")
            return

        # set map
        map_item = composition.getComposerItemById("map")
        map_item.setMapCanvas(iface.mapCanvas())
        map_item.zoomToExtent(iface.mapCanvas().extent())

        # set legend
        try:
            legend_item = composition.getComposerItemById("legend")
            legend_item.updateLegend()
        except AttributeError:  # in case first template was selected
            pass

        composition.refreshItems()
        return composition
    def print_atlas(self,
                    project_path,
                    composer_name,
                    predefined_scales,
                    feature_filter=None,
                    page_name_expression=None):

        if not feature_filter:
            QgsMessageLog.logMessage(
                "atlasprint: NO feature_filter provided !")
            return None

        # Get composer from project
        # in QGIS 2, canno get composers without iface
        # so we reading project xml and extract composer
        # in QGIS 3.0, we will use  project layoutManager()
        from xml.etree import ElementTree as ET
        composer_xml = None
        with open(project_path, 'r') as f:
            tree = ET.parse(f)
            for elem in tree.findall('.//Composer[@title="%s"]' %
                                     composer_name):
                composer_xml = ET.tostring(elem, encoding='utf8', method='xml')

        if not composer_xml:
            QgsMessageLog.logMessage("atlasprint: Composer XML not parsed !")
            return None

        document = QDomDocument()
        document.setContent(composer_xml)

        # Get canvas, map setting & instantiate composition
        canvas = QgsMapCanvas()
        QgsProject.instance().read(QFileInfo(project_path))
        bridge = QgsLayerTreeMapCanvasBridge(
            QgsProject.instance().layerTreeRoot(), canvas)
        bridge.setCanvasLayers()
        ms = canvas.mapSettings()
        composition = QgsComposition(ms)

        # Load content from XML
        substitution_map = {}
        composition.loadFromTemplate(document, substitution_map)

        # Get atlas for this composition
        atlas = composition.atlasComposition()
        atlas.setEnabled(True)

        atlas_map = composition.getComposerMapById(0)
        atlas_map.setAtlasScalingMode(QgsComposerMap.Predefined)

        # get project scales
        atlas.setPredefinedScales(predefined_scales)
        atlas_map.setAtlasDriven(True)
        #atlas.setComposerMap(atlas_map)

        if page_name_expression:
            atlas.setPageNameExpression(page_name_expression)

        # Filter feature here to avoid QGIS looping through every feature when doing : composition.setAtlasMode(QgsComposition.ExportAtlas)
        coverageLayer = atlas.coverageLayer()

        # Filter by FID as QGIS cannot compile expressions with $id or other $ vars
        # which leads to bad perfs for big datasets
        useFid = None
        if '$id' in feature_filter:
            import re
            ids = map(int, re.findall(r'\d+', feature_filter))
            if len(ids) > 0:
                useFid = ids[0]
        if useFid:
            qReq = QgsFeatureRequest().setFilterFid(useFid)
        else:
            qReq = QgsFeatureRequest().setFilterExpression(feature_filter)

        # Change feature_filter in order to improve perfs
        pks = coverageLayer.dataProvider().pkAttributeIndexes()
        if useFid and len(pks) == 1:
            pk = coverageLayer.dataProvider().fields()[pks[0]].name()
            feature_filter = '"%s" IN (%s)' % (pk, useFid)
            QgsMessageLog.logMessage(
                "atlasprint: feature_filter changed into: %s" % feature_filter)
            qReq = QgsFeatureRequest().setFilterExpression(feature_filter)
        atlas.setFilterFeatures(True)
        atlas.setFeatureFilter(feature_filter)
        uid = uuid4()
        i = 0

        # Set Atlas mode
        composition.setAtlasMode(QgsComposition.ExportAtlas)
        atlas.beginRender()

        for feat in coverageLayer.getFeatures(qReq):
            atlas.prepareForFeature(feat)
            export_path = os.path.join(
                tempfile.gettempdir(),
                '%s_%s.pdf' % (atlas.nameForPage(i), uid))
            exported = composition.exportAsPDF(export_path)
            if not exported or not os.path.isfile(export_path):
                QgsMessageLog.logMessage(
                    "atlasprint: An error occured while exporting the atlas !")
                return None

            break

        atlas.endRender()

        if os.path.isfile(export_path):
            QgsMessageLog.logMessage("atlasprint: path generated %s" %
                                     export_path)
        return export_path
示例#17
0
class Map():
    """A class for creating a map."""
    def __init__(self, iface):
        """Constructor for the Map class.

        :param iface: Reference to the QGIS iface object.
        :type iface: QgsAppInterface
        """
        LOGGER.debug('InaSAFE Map class initialised')
        self.iface = iface
        self.layer = iface.activeLayer()
        self.keyword_io = KeywordIO()
        self.printer = None
        self.composition = None
        self.legend = None
        self.logo = ':/plugins/inasafe/bnpb_logo.png'
        self.template = ':/plugins/inasafe/inasafe.qpt'
        #self.page_width = 210  # width in mm
        #self.page_height = 297  # height in mm
        self.page_width = 0  # width in mm
        self.page_height = 0  # height in mm
        self.page_dpi = 300.0
        #self.page_margin = 10  # margin in mm
        self.show_frames = False  # intended for debugging use only
        self.page_margin = None
        #vertical spacing between elements
        self.vertical_spacing = None
        self.map_height = None
        self.mapWidth = None
        # make a square map where width = height = page width
        #self.map_height = self.page_width - (self.page_margin * 2)
        #self.mapWidth = self.map_height
        #self.disclaimer = self.tr('InaSAFE has been jointly developed by'
        #                          ' BNPB, AusAid & the World Bank')

    @staticmethod
    def tr(string):
        """We implement this since we do not inherit QObject.

        :param string: String for translation.
        :type string: QString, str

        :returns: Translated version of theString.
        :rtype: QString
        """
        # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
        return QtCore.QCoreApplication.translate('Map', string)

    def set_impact_layer(self, layer):
        """Set the layer that will be used for stats, legend and reporting.

        :param layer: Layer that will be used for stats, legend and reporting.
        :type layer: QgsMapLayer, QgsRasterLayer, QgsVectorLayer
        """
        self.layer = layer

    def set_logo(self, logo):
        """

        :param logo: Path to image that will be used as logo in report
        :type logo: str
        """
        self.logo = logo

    def set_template(self, template):
        """

        :param template: Path to composer template that will be used for report
        :type template: str
        """
        self.template = template

    def setup_composition(self):
        """Set up the composition ready for drawing elements onto it."""
        LOGGER.debug('InaSAFE Map setupComposition called')
        canvas = self.iface.mapCanvas()
        renderer = canvas.mapRenderer()
        self.composition = QgsComposition(renderer)
        self.composition.setPlotStyle(QgsComposition.Print)  # or preview
        #self.composition.setPaperSize(self.page_width, self.page_height)
        self.composition.setPrintResolution(self.page_dpi)
        self.composition.setPrintAsRaster(True)

    def compose_map(self):
        """Place all elements on the map ready for printing."""
        self.setup_composition()
        # Keep track of our vertical positioning as we work our way down
        # the page placing elements on it.
        top_offset = self.page_margin
        self.draw_logo(top_offset)
        label_height = self.draw_title(top_offset)
        # Update the map offset for the next row of content
        top_offset += label_height + self.vertical_spacing
        composer_map = self.draw_map(top_offset)
        self.draw_scalebar(composer_map, top_offset)
        # Update the top offset for the next horizontal row of items
        top_offset += self.map_height + self.vertical_spacing - 1
        impact_title_height = self.draw_impact_title(top_offset)
        # Update the top offset for the next horizontal row of items
        if impact_title_height:
            top_offset += impact_title_height + self.vertical_spacing + 2
        self.draw_legend(top_offset)
        self.draw_host_and_time(top_offset)
        self.draw_disclaimer()

    def render(self):
        """Render the map composition to an image and save that to disk.

        :returns: A three-tuple of:
            * str: image_path - absolute path to png of rendered map
            * QImage: image - in memory copy of rendered map
            * QRectF: target_area - dimensions of rendered map
        :rtype: tuple
        """
        LOGGER.debug('InaSAFE Map renderComposition called')
        # NOTE: we ignore self.composition.printAsRaster() and always rasterise
        width = int(self.page_dpi * self.page_width / 25.4)
        height = int(self.page_dpi * self.page_height / 25.4)
        image = QtGui.QImage(QtCore.QSize(width, height),
                             QtGui.QImage.Format_ARGB32)
        image.setDotsPerMeterX(dpi_to_meters(self.page_dpi))
        image.setDotsPerMeterY(dpi_to_meters(self.page_dpi))

        # Only works in Qt4.8
        #image.fill(QtGui.qRgb(255, 255, 255))
        # Works in older Qt4 versions
        image.fill(55 + 255 * 256 + 255 * 256 * 256)
        image_painter = QtGui.QPainter(image)
        source_area = QtCore.QRectF(0, 0, self.page_width, self.page_height)
        target_area = QtCore.QRectF(0, 0, width, height)
        self.composition.render(image_painter, target_area, source_area)
        image_painter.end()
        image_path = unique_filename(prefix='mapRender_',
                                     suffix='.png',
                                     dir=temp_dir())
        image.save(image_path)
        return image_path, image, target_area

    def make_pdf(self, filename):
        """Generate the printout for our final map.

        :param filename: Path on the file system to which the pdf should be
            saved. If None, a generated file name will be used.
        :type filename: str

        :returns: File name of the output file (equivalent to filename if
                provided).
        :rtype: str
        """
        LOGGER.debug('InaSAFE Map printToPdf called')
        if filename is None:
            map_pdf_path = unique_filename(prefix='report',
                                           suffix='.pdf',
                                           dir=temp_dir())
        else:
            # We need to cast to python string in case we receive a QString
            map_pdf_path = str(filename)

        self.load_template()

        resolution = self.composition.printResolution()
        self.printer = setup_printer(map_pdf_path, resolution=resolution)
        _, image, rectangle = self.render()
        painter = QtGui.QPainter(self.printer)
        painter.drawImage(rectangle, image, rectangle)
        painter.end()
        return map_pdf_path

    def draw_logo(self, top_offset):
        """Add a picture containing the logo to the map top left corner

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        logo = QgsComposerPicture(self.composition)
        logo.setPictureFile(':/plugins/inasafe/bnpb_logo.png')
        logo.setItemPosition(self.page_margin, top_offset, 10, 10)
        logo.setFrameEnabled(self.show_frames)
        logo.setZValue(1)  # To ensure it overlays graticule markers
        self.composition.addItem(logo)

    def draw_title(self, top_offset):
        """Add a title to the composition.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int

        :returns: The height of the label as rendered.
        :rtype: float
        """
        LOGGER.debug('InaSAFE Map drawTitle called')
        font_size = 14
        font_weight = QtGui.QFont.Bold
        italics_flag = False
        font = QtGui.QFont('verdana', font_size, font_weight, italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        heading = self.tr(
            'InaSAFE - Indonesia Scenario Assessment for Emergencies')
        label.setText(heading)
        label.adjustSizeToText()
        label_height = 10.0  # determined using qgis map composer
        label_width = 170.0  # item - position and size...option
        left_offset = self.page_width - self.page_margin - label_width
        label.setItemPosition(
            left_offset,
            top_offset - 2,  # -2 to push it up a little
            label_width,
            label_height)
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)
        return label_height

    def draw_map(self, top_offset):
        """Add a map to the composition and return the composer map instance.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int

        :returns: The composer map.
        :rtype: QgsComposerMap
        """
        LOGGER.debug('InaSAFE Map drawMap called')
        map_width = self.mapWidth
        composer_map = QgsComposerMap(self.composition, self.page_margin,
                                      top_offset, map_width, self.map_height)
        #myExtent = self.iface.mapCanvas().extent()
        # The dimensions of the map canvas and the print composer map may
        # differ. So we set the map composer extent using the canvas and
        # then defer to the map canvas's map extents thereafter
        # Update: disabled as it results in a rectangular rather than
        # square map
        #composer_map.setNewExtent(myExtent)
        composer_extent = composer_map.extent()
        # Recenter the composer map on the center of the canvas
        # Note that since the composer map is square and the canvas may be
        # arbitrarily shaped, we center based on the longest edge
        canvas_extent = self.iface.mapCanvas().extent()
        width = canvas_extent.width()
        height = canvas_extent.height()
        longest_length = width
        if width < height:
            longest_length = height
        half_length = longest_length / 2
        center = canvas_extent.center()
        min_x = center.x() - half_length
        max_x = center.x() + half_length
        min_y = center.y() - half_length
        max_y = center.y() + half_length
        square_extent = QgsRectangle(min_x, min_y, max_x, max_y)
        composer_map.setNewExtent(square_extent)

        composer_map.setGridEnabled(True)
        split_count = 5
        # .. todo:: Write logic to adjust precision so that adjacent tick marks
        #    always have different displayed values
        precision = 2
        x_interval = composer_extent.width() / split_count
        composer_map.setGridIntervalX(x_interval)
        y_interval = composer_extent.height() / split_count
        composer_map.setGridIntervalY(y_interval)
        composer_map.setGridStyle(QgsComposerMap.Cross)
        cross_length_mm = 1
        composer_map.setCrossLength(cross_length_mm)
        composer_map.setZValue(0)  # To ensure it does not overlay logo
        font_size = 6
        font_weight = QtGui.QFont.Normal
        italics_flag = False
        font = QtGui.QFont('verdana', font_size, font_weight, italics_flag)
        composer_map.setGridAnnotationFont(font)
        composer_map.setGridAnnotationPrecision(precision)
        composer_map.setShowGridAnnotation(True)
        composer_map.setGridAnnotationDirection(
            QgsComposerMap.BoundaryDirection, QgsComposerMap.Top)
        self.composition.addItem(composer_map)
        self.draw_graticule_mask(top_offset)
        return composer_map

    def draw_graticule_mask(self, top_offset):
        """A helper function to mask out graticule labels.

         It will hide labels on the right side by over painting a white
         rectangle with white border on them. **kludge**

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawGraticuleMask called')
        left_offset = self.page_margin + self.mapWidth
        rect = QgsComposerShape(left_offset + 0.5, top_offset,
                                self.page_width - left_offset,
                                self.map_height + 1, self.composition)

        rect.setShapeType(QgsComposerShape.Rectangle)
        pen = QtGui.QPen()
        pen.setColor(QtGui.QColor(0, 0, 0))
        pen.setWidthF(0.1)
        rect.setPen(pen)
        rect.setBackgroundColor(QtGui.QColor(255, 255, 255))
        rect.setTransparency(100)
        #rect.setLineWidth(0.1)
        #rect.setFrameEnabled(False)
        #rect.setOutlineColor(QtGui.QColor(255, 255, 255))
        #rect.setFillColor(QtGui.QColor(255, 255, 255))
        #rect.setOpacity(100)
        # These two lines seem superfluous but are needed
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        rect.setBrush(brush)
        self.composition.addItem(rect)

    def draw_native_scalebar(self, composer_map, top_offset):
        """Draw a scale bar using QGIS' native drawing.

        In the case of geographic maps, scale will be in degrees, not km.

        :param composer_map: Composer map on which to draw the scalebar.
        :type composer_map: QgsComposerMap

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawNativeScaleBar called')
        scale_bar = QgsComposerScaleBar(self.composition)
        scale_bar.setStyle('Numeric')  # optionally modify the style
        scale_bar.setComposerMap(composer_map)
        scale_bar.applyDefaultSize()
        scale_bar_height = scale_bar.boundingRect().height()
        scale_bar_width = scale_bar.boundingRect().width()
        # -1 to avoid overlapping the map border
        scale_bar.setItemPosition(
            self.page_margin + 1,
            top_offset + self.map_height - (scale_bar_height * 2),
            scale_bar_width, scale_bar_height)
        scale_bar.setFrameEnabled(self.show_frames)
        # Disabled for now
        #self.composition.addItem(scale_bar)

    def draw_scalebar(self, composer_map, top_offset):
        """Add a numeric scale to the bottom left of the map.

        We draw the scale bar manually because QGIS does not yet support
        rendering a scale bar for a geographic map in km.

        .. seealso:: :meth:`drawNativeScaleBar`

        :param composer_map: Composer map on which to draw the scalebar.
        :type composer_map: QgsComposerMap

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawScaleBar called')
        canvas = self.iface.mapCanvas()
        renderer = canvas.mapRenderer()
        #
        # Add a linear map scale
        #
        distance_area = QgsDistanceArea()
        distance_area.setSourceCrs(renderer.destinationCrs().srsid())
        distance_area.setEllipsoidalMode(True)
        # Determine how wide our map is in km/m
        # Starting point at BL corner
        composer_extent = composer_map.extent()
        start_point = QgsPoint(composer_extent.xMinimum(),
                               composer_extent.yMinimum())
        # Ending point at BR corner
        end_point = QgsPoint(composer_extent.xMaximum(),
                             composer_extent.yMinimum())
        ground_distance = distance_area.measureLine(start_point, end_point)
        # Get the equivalent map distance per page mm
        map_width = self.mapWidth
        # How far is 1mm on map on the ground in meters?
        mm_to_ground = ground_distance / map_width
        #print 'MM:', myMMDistance
        # How long we want the scale bar to be in relation to the map
        scalebar_to_map_ratio = 0.5
        # How many divisions the scale bar should have
        tick_count = 5
        scale_bar_width_mm = map_width * scalebar_to_map_ratio
        print_segment_width_mm = scale_bar_width_mm / tick_count
        # Segment width in real world (m)
        # We apply some logic here so that segments are displayed in meters
        # if each segment is less that 1000m otherwise km. Also the segment
        # lengths are rounded down to human looking numbers e.g. 1km not 1.1km
        units = ''
        ground_segment_width = print_segment_width_mm * mm_to_ground
        if ground_segment_width < 1000:
            units = 'm'
            ground_segment_width = round(ground_segment_width)
            # adjust the segment width now to account for rounding
            print_segment_width_mm = ground_segment_width / mm_to_ground
        else:
            units = 'km'
            # Segment with in real world (km)
            ground_segment_width = round(ground_segment_width / 1000)
            print_segment_width_mm = ((ground_segment_width * 1000) /
                                      mm_to_ground)
        # Now adjust the scalebar width to account for rounding
        scale_bar_width_mm = tick_count * print_segment_width_mm

        #print "SBWMM:", scale_bar_width_mm
        #print "SWMM:", print_segment_width_mm
        #print "SWM:", myGroundSegmentWidthM
        #print "SWKM:", myGroundSegmentWidthKM
        # start drawing in line segments
        scalebar_height = 5  # mm
        line_width = 0.3  # mm
        inset_distance = 7  # how much to inset the scalebar into the map by
        scalebar_x = self.page_margin + inset_distance
        scalebar_y = (top_offset + self.map_height - inset_distance -
                      scalebar_height)  # mm

        # Draw an outer background box - shamelessly hardcoded buffer
        rectangle = QgsComposerShape(
            scalebar_x - 4,  # left edge
            scalebar_y - 3,  # top edge
            scale_bar_width_mm + 13,  # right edge
            scalebar_height + 6,  # bottom edge
            self.composition)

        rectangle.setShapeType(QgsComposerShape.Rectangle)
        pen = QtGui.QPen()
        pen.setColor(QtGui.QColor(255, 255, 255))
        pen.setWidthF(line_width)
        rectangle.setPen(pen)
        #rectangle.setLineWidth(line_width)
        rectangle.setFrameEnabled(False)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        # workaround for missing setTransparentFill missing from python api
        rectangle.setBrush(brush)
        self.composition.addItem(rectangle)
        # Set up the tick label font
        font_weight = QtGui.QFont.Normal
        font_size = 6
        italics_flag = False
        font = QtGui.QFont('verdana', font_size, font_weight, italics_flag)
        # Draw the bottom line
        up_shift = 0.3  # shift the bottom line up for better rendering
        rectangle = QgsComposerShape(scalebar_x,
                                     scalebar_y + scalebar_height - up_shift,
                                     scale_bar_width_mm, 0.1, self.composition)

        rectangle.setShapeType(QgsComposerShape.Rectangle)
        pen = QtGui.QPen()
        pen.setColor(QtGui.QColor(255, 255, 255))
        pen.setWidthF(line_width)
        rectangle.setPen(pen)
        #rectangle.setLineWidth(line_width)
        rectangle.setFrameEnabled(False)
        self.composition.addItem(rectangle)

        # Now draw the scalebar ticks
        for tick_counter in range(0, tick_count + 1):
            distance_suffix = ''
            if tick_counter == tick_count:
                distance_suffix = ' ' + units
            real_world_distance = (
                '%.0f%s' %
                (tick_counter * ground_segment_width, distance_suffix))
            #print 'RW:', myRealWorldDistance
            mm_offset = scalebar_x + (tick_counter * print_segment_width_mm)
            #print 'MM:', mm_offset
            tick_height = scalebar_height / 2
            # Lines are not exposed by the api yet so we
            # bodge drawing lines using rectangles with 1px height or width
            tick_width = 0.1  # width or rectangle to be drawn
            uptick_line = QgsComposerShape(
                mm_offset, scalebar_y + scalebar_height - tick_height,
                tick_width, tick_height, self.composition)

            uptick_line.setShapeType(QgsComposerShape.Rectangle)
            pen = QtGui.QPen()
            pen.setWidthF(line_width)
            uptick_line.setPen(pen)
            #uptick_line.setLineWidth(line_width)
            uptick_line.setFrameEnabled(False)
            self.composition.addItem(uptick_line)
            #
            # Add a tick label
            #
            label = QgsComposerLabel(self.composition)
            label.setFont(font)
            label.setText(real_world_distance)
            label.adjustSizeToText()
            label.setItemPosition(mm_offset - 3, scalebar_y - tick_height)
            label.setFrameEnabled(self.show_frames)
            self.composition.addItem(label)

    def draw_impact_title(self, top_offset):
        """Draw the map subtitle - obtained from the impact layer keywords.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int

        :returns: The height of the label as rendered.
        :rtype: float
        """
        LOGGER.debug('InaSAFE Map drawImpactTitle called')
        title = self.map_title()
        if title is None:
            title = ''
        font_size = 20
        font_weight = QtGui.QFont.Bold
        italics_flag = False
        font = QtGui.QFont('verdana', font_size, font_weight, italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        heading = title
        label.setText(heading)
        label_width = self.page_width - (self.page_margin * 2)
        label_height = 12
        label.setItemPosition(self.page_margin, top_offset, label_width,
                              label_height)
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)
        return label_height

    def draw_legend(self, top_offset):
        """Add a legend to the map using our custom legend renderer.

        .. note:: getLegend generates a pixmap in 150dpi so if you set
           the map to a higher dpi it will appear undersized.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawLegend called')
        legend_attributes = self.map_legend_attributes()
        legend_notes = legend_attributes.get('legend_notes', None)
        legend_units = legend_attributes.get('legend_units', None)
        legend_title = legend_attributes.get('legend_title', None)
        LOGGER.debug(legend_attributes)
        legend = MapLegend(self.layer, self.page_dpi, legend_title,
                           legend_notes, legend_units)
        self.legend = legend.get_legend()
        picture1 = QgsComposerPicture(self.composition)
        legend_file_path = unique_filename(prefix='legend',
                                           suffix='.png',
                                           dir='work')
        self.legend.save(legend_file_path, 'PNG')
        picture1.setPictureFile(legend_file_path)
        legend_height = points_to_mm(self.legend.height(), self.page_dpi)
        legend_width = points_to_mm(self.legend.width(), self.page_dpi)
        picture1.setItemPosition(self.page_margin, top_offset, legend_width,
                                 legend_height)
        picture1.setFrameEnabled(False)
        self.composition.addItem(picture1)
        os.remove(legend_file_path)

    def draw_image(self, image, width_mm, left_offset, top_offset):
        """Helper to draw an image directly onto the QGraphicsScene.
        This is an alternative to using QgsComposerPicture which in
        some cases leaves artifacts under windows.

        The Pixmap will have a transform applied to it so that
        it is rendered with the same resolution as the composition.

        :param image: Image that will be rendered to the layout.
        :type image: QImage

        :param width_mm: Desired width in mm of output on page.
        :type width_mm: int

        :param left_offset: Offset from left of page.
        :type left_offset: int

        :param top_offset: Offset from top of page.
        :type top_offset: int

        :returns: Graphics scene item.
        :rtype: QGraphicsSceneItem
        """
        LOGGER.debug('InaSAFE Map drawImage called')
        desired_width_mm = width_mm  # mm
        desired_width_px = mm_to_points(desired_width_mm, self.page_dpi)
        actual_width_px = image.width()
        scale_factor = desired_width_px / actual_width_px

        LOGGER.debug('%s %s %s' %
                     (scale_factor, actual_width_px, desired_width_px))
        transform = QtGui.QTransform()
        transform.scale(scale_factor, scale_factor)
        transform.rotate(0.5)
        # noinspection PyArgumentList
        item = self.composition.addPixmap(QtGui.QPixmap.fromImage(image))
        item.setTransform(transform)
        item.setOffset(left_offset / scale_factor, top_offset / scale_factor)
        return item

    def draw_host_and_time(self, top_offset):
        """Add a note with hostname and time to the composition.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawDisclaimer called')
        #elapsed_time: 11.612545
        #user: timlinux
        #host_name: ultrabook
        #time_stamp: 2012-10-13_23:10:31
        #myUser = self.keyword_io.readKeywords(self.layer, 'user')
        #myHost = self.keyword_io.readKeywords(self.layer, 'host_name')
        date_time = self.keyword_io.read_keywords(self.layer, 'time_stamp')
        tokens = date_time.split('_')
        date = tokens[0]
        time = tokens[1]
        #myElapsedTime = self.keyword_io.readKeywords(self.layer,
        #                                            'elapsed_time')
        #myElapsedTime = humaniseSeconds(myElapsedTime)
        long_version = get_version()
        tokens = long_version.split('.')
        version = '%s.%s.%s' % (tokens[0], tokens[1], tokens[2])
        label_text = self.tr(
            'Date and time of assessment: %s %s\n'
            'Special note: This assessment is a guide - we strongly recommend '
            'that you ground truth the results shown here before deploying '
            'resources and / or personnel.\n'
            'Assessment carried out using InaSAFE release %s (QGIS '
            'plugin version).') % (date, time, version)
        font_size = 6
        font_weight = QtGui.QFont.Normal
        italics_flag = True
        font = QtGui.QFont('verdana', font_size, font_weight, italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        label.setText(label_text)
        label.adjustSizeToText()
        label_height = 50.0  # mm determined using qgis map composer
        label_width = (self.page_width / 2) - self.page_margin
        left_offset = self.page_width / 2  # put in right half of page
        label.setItemPosition(
            left_offset,
            top_offset,
            label_width,
            label_height,
        )
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)

    def draw_disclaimer(self):
        """Add a disclaimer to the composition."""
        LOGGER.debug('InaSAFE Map drawDisclaimer called')
        font_size = 10
        font_weight = QtGui.QFont.Normal
        italics_flag = True
        font = QtGui.QFont('verdana', font_size, font_weight, italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        label.setText(self.disclaimer)
        label.adjustSizeToText()
        label_height = 7.0  # mm determined using qgis map composer
        label_width = self.page_width  # item - position and size...option
        left_offset = self.page_margin
        top_offset = self.page_height - self.page_margin
        label.setItemPosition(
            left_offset,
            top_offset,
            label_width,
            label_height,
        )
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)

    def map_title(self):
        """Get the map title from the layer keywords if possible.

        :returns: None on error, otherwise the title.
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapTitle called')
        try:
            title = self.keyword_io.read_keywords(self.layer, 'map_title')
            return title
        except KeywordNotFoundError:
            return None
        except Exception:
            return None

    def map_legend_attributes(self):
        """Get the map legend attribute from the layer keywords if possible.

        :returns: None on error, otherwise the attributes (notes and units).
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapLegendAtributes called')
        legend_attribute_list = [
            'legend_notes', 'legend_units', 'legend_title'
        ]
        legend_attribute_dict = {}
        for myLegendAttribute in legend_attribute_list:
            try:
                legend_attribute_dict[myLegendAttribute] = \
                    self.keyword_io.read_keywords(
                        self.layer, myLegendAttribute)
            except KeywordNotFoundError:
                pass
            except Exception:
                pass
        return legend_attribute_dict

    def show_composer(self):
        """Show the composition in a composer view so the user can tweak it.
        """
        view = QgsComposerView(self.iface.mainWindow())
        view.show()

    def write_template(self, template_path):
        """Write current composition as a template that can be re-used in QGIS.

        :param template_path: Path to which template should be written.
        :type template_path: str
        """
        document = QtXml.QDomDocument()
        element = document.createElement('Composer')
        document.appendChild(element)
        self.composition.writeXML(element, document)
        xml = document.toByteArray()
        template_file = file(template_path, 'wb')
        template_file.write(xml)
        template_file.close()

    def load_template(self):
        """Load a QgsComposer map from a template and render it.

        .. note:: THIS METHOD IS EXPERIMENTAL
        """
        self.setup_composition()

        template_file = QtCore.QFile(self.template)
        template_file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()

        document = QtXml.QDomDocument()
        document.setContent(template_content)

        # get information for substitutions
        # date, time and plugin version
        date_time = self.keyword_io.read_keywords(self.layer, 'time_stamp')
        tokens = date_time.split('_')
        date = tokens[0]
        time = tokens[1]
        long_version = get_version()
        tokens = long_version.split('.')
        version = '%s.%s.%s' % (tokens[0], tokens[1], tokens[2])

        # map title
        LOGGER.debug('InaSAFE Map getMapTitle called')
        try:
            title = self.keyword_io.read_keywords(self.layer, 'map_title')
        except KeywordNotFoundError:
            title = None
        except Exception:
            title = None

        if not title:
            title = ''

        substitution_map = {
            'impact-title': title,
            'date': date,
            'time': time,
            'safe-version': version
        }
        LOGGER.debug(substitution_map)
        load_ok = self.composition.loadFromTemplate(document, substitution_map)
        if not load_ok:
            raise ReportCreationError(
                self.tr('Error loading template %s') % self.template)

        self.page_width = self.composition.paperWidth()
        self.page_height = self.composition.paperHeight()

        # set logo
        image = self.composition.getComposerItemById('safe-logo')
        image.setPictureFile(self.logo)

        # Get the main map canvas on the composition and set
        # its extents to the event.
        map = self.composition.getComposerItemById('impact-map')
        if map is not None:
            # Recenter the composer map on the center of the canvas
            # Note that since the composer map is square and the canvas may be
            # arbitrarily shaped, we center based on the longest edge
            canvas_extent = self.iface.mapCanvas().extent()
            width = canvas_extent.width()
            height = canvas_extent.height()
            longest_width = width
            if width < height:
                longest_width = height
            half_length = longest_width / 2
            center = canvas_extent.center()
            min_x = center.x() - half_length
            max_x = center.x() + half_length
            min_y = center.y() - half_length
            max_y = center.y() + half_length
            square_extent = QgsRectangle(min_x, min_y, max_x, max_y)
            map.setNewExtent(square_extent)

            # calculate intervals for grid
            split_count = 5
            x_interval = square_extent.width() / split_count
            map.setGridIntervalX(x_interval)
            y_interval = square_extent.height() / split_count
            map.setGridIntervalY(y_interval)
        else:
            raise ReportCreationError(
                self.tr('Map "impact-map" could not be found'))

        legend = self.composition.getComposerItemById('impact-legend')
        legend_attributes = self.map_legend_attributes()
        LOGGER.debug(legend_attributes)
        #legend_notes = mapLegendAttributes.get('legend_notes', None)
        #legend_units = mapLegendAttributes.get('legend_units', None)
        legend_title = legend_attributes.get('legend_title', None)
        if legend_title is None:
            legend_title = ""
        legend.setTitle(legend_title)
        legend.updateLegend()
    def export_all_features(self):
        pdf_painter = None
        """Export map to pdf atlas style (one page per feature)"""
        if VRP_DEBUG is True: QgsMessageLog.logMessage(u'exporting map', DLG_CAPTION)
        try:

            result = self.__delete_pdf()
            if not result is None:
                return result

            ids = []
            exp = QgsExpression(self.feature_filter)
            if exp.hasParserError():
                raise Exception(exp.parserErrorString())
            exp.prepare(self.coverage_layer.pendingFields())
            for feature in self.coverage_layer.getFeatures():
                value = exp.evaluate(feature)
                if exp.hasEvalError():
                    raise ValueError(exp.evalErrorString())
                if bool(value):
                    if VRP_DEBUG is True: QgsMessageLog.logMessage(u'export map, feature id:{0}'.format(feature.id()), DLG_CAPTION)
                    ids.append(feature.id())
            self.coverage_layer.select(ids)
            bbox = self.coverage_layer.boundingBoxOfSelected()
            self.canvas.zoomToSelected(self.coverage_layer)
            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox:{0}'.format(bbox.toString()), DLG_CAPTION)

            #self.map_renderer.setExtent(bbox)
            #self.map_renderer.updateScale()

            #read plotlayout
            composition = QgsComposition(self.map_renderer)
            self.composition = composition
            composition.setPlotStyle(QgsComposition.Print)
            error, xml_doc = self.__read_template()
            if not error is None:
                return error
            if composition.loadFromTemplate(xml_doc) is False:
                return u'Konnte Template nicht laden!\n{0}'.format(self.template_qpt)

            #read textinfo layout
            self.comp_textinfo = QgsComposition(self.map_renderer)
            self.comp_textinfo.setPlotStyle(QgsComposition.Print)
            error, xml_doc = self.__read_template(True)
            if not error is None:
                return error
            if self.comp_textinfo.loadFromTemplate(xml_doc) is False:
                return u'Konnte Template nicht laden!\n{0}'.format(self.settings.textinfo_layout())


            new_ext = bbox
            if QGis.QGIS_VERSION_INT > 20200:
                compmaps = self.__get_items(QgsComposerMap)
                if len(compmaps) < 1:
                    return u'Kein Kartenfenster im Layout vorhanden!'
                compmap = compmaps[0]
            else:
                if len(composition.composerMapItems()) < 1:
                    return u'Kein Kartenfenster im Layout vorhanden!'
                compmap = composition.composerMapItems()[0]

            self.composermap = compmap
            #self.composermap.setPreviewMode(QgsComposerMap.Render)
            #self.composermap.setPreviewMode(QgsComposerMap.Rectangle)
            #taken from QgsComposerMap::setNewAtlasFeatureExtent (not yet available in QGIS 2.0)
            #http://www.qgis.org/api/qgscomposermap_8cpp_source.html#l00610
            old_ratio = compmap.rect().width() / compmap.rect().height()
            new_ratio = new_ext.width() / new_ext.height()
            if old_ratio < new_ratio:
                new_height = new_ext.width() / old_ratio
                delta_height = new_height - new_ext.height()
                new_ext.setYMinimum( bbox.yMinimum() - delta_height / 2)
                new_ext.setYMaximum(bbox.yMaximum() + delta_height / 2)
            else:
                new_width = old_ratio * new_ext.height()
                delta_width = new_width - new_ext.width()
                new_ext.setXMinimum(bbox.xMinimum() - delta_width / 2)
                new_ext.setXMaximum(bbox.xMaximum() + delta_width / 2)

            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox old:{0}'.format(compmap.extent().toString()), DLG_CAPTION)
            compmap.setNewExtent(new_ext)
            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox new:{0}'.format(compmap.extent().toString()), DLG_CAPTION)
            #round up to next 1000
            compmap.setNewScale(math.ceil((compmap.scale()/1000.0)) * 1000.0)
            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox new (after scale):{0}'.format(compmap.extent().toString()), DLG_CAPTION)

            #add ORTHO after new extent -> performance
            if not self.ortho is None:
                self.ortho_lyr = self.__add_raster_layer(self.ortho, self.lyrname_ortho)
                self.__reorder_layers()

            self.comp_leg = self.__get_items(QgsComposerLegend)
            self.comp_lbl = self.__get_items(QgsComposerLabel)


            self.__update_composer_items(self.settings.dkm_gemeinde(self.gem_name)['lyrnamegstk'])

            if VRP_DEBUG is True:
                QgsMessageLog.logMessage(u'paperWidth:{0} paperHeight:{1}'.format(composition.paperWidth(), composition.paperHeight()), DLG_CAPTION)

            printer = QPrinter()
            printer.setOutputFormat(QPrinter.PdfFormat)
            printer.setOutputFileName(self.pdf_map)
            printer.setPaperSize(QSizeF(composition.paperWidth(), composition.paperHeight()), QPrinter.Millimeter)
            printer.setFullPage(True)
            printer.setColorMode(QPrinter.Color)
            printer.setResolution(composition.printResolution())

            pdf_painter = QPainter(printer)
            paper_rect_pixel = printer.pageRect(QPrinter.DevicePixel)
            paper_rect_mm = printer.pageRect(QPrinter.Millimeter)
            QgsPaintEngineHack.fixEngineFlags(printer.paintEngine())
            #DKM only
            if len(self.themen) < 1:
                composition.render(pdf_painter, paper_rect_pixel, paper_rect_mm)
            else:
                self.statistics = OrderedDict()
                try:
                    pass
                    #lyr = QgsVectorLayer('/home/bergw/VoGIS-Raumplanung-Daten/Geodaten/Raumplanung/Flaechenwidmung/Dornbirn/Flaechenwidmungsplan/fwp_flaeche.shp', 'flaeiw', 'ogr')
                    #lyr.loadNamedStyle('/home/bergw/VoGIS-Raumplanung-Daten/Geodaten/Raumplanung/Flaechenwidmung/Vorarlberg/Flaechenwidmungsplan/fwp_flaeche.qml')
                    #QgsMapLayerRegistry.instance().addMapLayer(lyr)
                except:
                    QgsMessageLog.logMessage('new lyr:{0}'.format(sys.exc_info()[0]), DLG_CAPTION)
                #QgsMapLayerRegistry.instance().addMapLayer(lyr)
                cntr = 0
                for thema, sub_themen in self.themen.iteritems():
                    if VRP_DEBUG is True: QgsMessageLog.logMessage('drucke Thema:{0}'.format(thema.name), DLG_CAPTION)
                    if sub_themen is None:
                        layers = self.__add_layers(thema)
                        self.__calculate_statistics(thema, thema, layers)
                        #no qml -> not visible -> means no map
                        if self.__at_least_one_visible(layers) is True:
                            if cntr > 0:
                                printer.newPage()
                            self.__reorder_layers()
                            self.__update_composer_items(thema.name, layers=layers)
                            composition.renderPage(pdf_painter, 0)
                            QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
                            cntr += 1
                        else:
                            QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
                    if not sub_themen is None:
                        for sub_thema in sub_themen:
                            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'drucke SubThema:{0}'.format(sub_thema.name), DLG_CAPTION)
                            layers = self.__add_layers(sub_thema)
                            self.__calculate_statistics(thema, sub_thema, layers)
                            #no qml -> not visible -> means no map
                            if self.__at_least_one_visible(layers) is True:
                                if cntr > 0:
                                    printer.newPage()
                                self.__reorder_layers()
                                self.__update_composer_items(thema.name, subthema=sub_thema.name, layers=layers)
                                composition.renderPage(pdf_painter, 0)
                                QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
                                cntr += 1
                            else:
                                QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
            #output statistics
            if len(self.statistics) > 0:
                printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
                tabelle = self.__get_item_byid(self.comp_textinfo, 'TABELLE')
                if tabelle is None:
                    self.iface.messageBar().pushMessage(u'Layout (Textinfo): Kein Textelement mit ID "TABELLE" vorhanden.', QgsMessageBar.CRITICAL)
                else:
                    try:
                        str_flaechen = ''
                        idx = 0
                        for gnr, stats in self.statistics.iteritems():
                            comma = ', ' if idx > 0 else ''
                            str_flaechen += u'{0}{1} ({2:.2f}m²)'.format(comma, gnr, stats[0].flaeche)
                            idx += 1
                        lbls = self.__get_items(QgsComposerLabel, self.comp_textinfo)
                        self.__update_composer_items('', labels=lbls, gnrflaeche=str_flaechen)
                        html = tabelle.text()
                        html += u'<table>'
                        #gnrcnt = 0
                        for gnr, stats in self.statistics.iteritems():
                            #if gnrcnt > 0:
                            #    html += u'<tr class="abstand"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>'
                            html += u'<tr><th class="gnr"></th><th class="gnr">{0}</th><th class="gnr"></th></tr>'.format(gnr)
                            #html += u'<tr class="abstand"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>'
                            curr_thema = ''
                            for stat in stats:
                                if stat.thema != curr_thema:
                                    html += u'<tr><th class="thema"></th><th class="thema">{0}</th><th class="thema"></th></tr>'.format(stat.thema)
                                curr_thema = stat.thema
                                for thema, subthema in stat.subthemen.iteritems():
                                    for quelle in subthema:
                                        html += u'<tr><td class="col1">{0}</td>'.format(quelle.name)
                                        attr_val = ''
                                        attr_area = ''
                                        for text, area in quelle.txt_area.iteritems():
                                            attr_val += u'{0}<br />'.format(text)
                                            attr_area += u'{0:.2f}m² <br />'.format(area)
                                        html += u'<td class="col2">{0}</td><td class="col3">{1}</td></tr>'.format(attr_val, attr_area)
                            #gnrcnt += 1
                        html += u'</table>'
                        tabelle.setText(html)
                        printer.newPage()
                        self.comp_textinfo.renderPage(pdf_painter, 0)
                    except:
                        msg = 'Statistikausgabe:\n\n{0}'.format(traceback.format_exc())
                        QgsMessageLog.logMessage(msg, DLG_CAPTION)
                        self.iface.messageBar().pushMessage(msg, QgsMessageBar.CRITICAL)
        except:
            msg = 'export pdf (catch all):\n\n{0}'.format(traceback.format_exc())
            QgsMessageLog.logMessage(msg, DLG_CAPTION)
            self.iface.messageBar().pushMessage(msg.replace(u'\n', u''), QgsMessageBar.CRITICAL)
            return msg
        finally:
            #end pdf
            if not pdf_painter is None:
                pdf_painter.end()
        return None
示例#19
0
def qgis_composer_renderer(impact_report, component):
    """Default Map Report Renderer using QGIS Composer.

    Render using qgis composer for a given impact_report data and component
    context

    :param impact_report: ImpactReport contains data about the report that is
        going to be generated
    :type impact_report: safe.report.impact_report.ImpactReport

    :param component: Contains the component metadata and context for
        rendering the output
    :type component:
        safe.report.report_metadata.QgisComposerComponentsMetadata

    :return: whatever type of output the component should be

    .. versionadded:: 4.0
    """
    context = component.context
    """:type: safe.report.extractors.composer.QGISComposerContext"""
    qgis_composition_context = impact_report.qgis_composition_context
    inasafe_context = impact_report.inasafe_context

    # load composition object
    composition = QgsComposition(qgis_composition_context.map_settings)

    # load template
    main_template_folder = impact_report.metadata.template_folder
    template_path = os.path.join(main_template_folder, component.template)

    with open(template_path) as template_file:
        template_content = template_file.read()

    document = QtXml.QDomDocument()
    document.setContent(template_content)

    load_status = composition.loadFromTemplate(
        document, context.substitution_map)

    if not load_status:
        raise TemplateLoadingError(
            tr('Error loading template: %s') % template_path)

    # replace image path
    for img in context.image_elements:
        item_id = img.get('id')
        path = img.get('path')
        image = composition.getComposerItemById(item_id)
        """:type: qgis.core.QgsComposerPicture"""
        if image is not None and path is not None:
            try:
                image.setPicturePath(path)
            except:
                pass

    # replace html frame
    for html_el in context.html_frame_elements:
        item_id = html_el.get('id')
        mode = html_el.get('mode')
        html_element = composition.getComposerItemById(item_id)
        """:type: qgis.core.QgsComposerHtml"""
        if html_element:
            if mode == 'text':
                text = html_el.get('text')
                text = text if text else ''
                html_element.setContentMode(QgsComposerHtml.ManualHtml)
                html_element.setHtml(text)
                html_element.loadHtml()
            elif mode == 'url':
                url = html_el.get('url')
                html_element.setContentMode(QgsComposerHtml.Url)
                qurl = QUrl.fromLocalFile(url)
                html_element.setUrl(qurl)

    # resize map extent
    for map_el in context.map_elements:
        item_id = map_el.get('id')
        split_count = map_el.get('grid_split_count')
        layers = map_el.get('layers')
        map_extent_option = map_el.get('extent')
        composer_map = composition.getComposerItemById(item_id)
        """:type: qgis.core.QgsComposerMap"""
        if isinstance(composer_map, QgsComposerMap):
            composer_map.setKeepLayerSet(True)
            layer_set = [l.id() for l in layers if isinstance(l, QgsMapLayer)]
            composer_map.setLayerSet(layer_set)
            if map_extent_option and isinstance(
                    map_extent_option, QgsRectangle):
                # use provided map extent
                extent = map_extent_option
            else:
                # if map extent not provided, try to calculate extent
                # from list of given layers. Combine it so all layers were
                # shown properly
                extent = QgsRectangle()
                extent.setMinimal()
                for l in layers:
                    # combine extent if different layer is provided.
                    extent.combineExtentWith(l.extent())

            width = extent.width()
            height = extent.height()
            longest_width = width if width > height else height
            half_length = longest_width / 2
            margin = half_length / 5
            center = extent.center()
            min_x = center.x() - half_length - margin
            max_x = center.x() + half_length + margin
            min_y = center.y() - half_length - margin
            max_y = center.y() + half_length + margin

            # noinspection PyCallingNonCallable
            square_extent = QgsRectangle(min_x, min_y, max_x, max_y)

            composer_map.zoomToExtent(square_extent)
            composer_map.renderModeUpdateCachedImage()

            actual_extent = composer_map.extent()

            # calculate intervals for grid
            x_interval = actual_extent.width() / split_count
            composer_map.grid().setIntervalX(x_interval)
            y_interval = actual_extent.height() / split_count
            composer_map.grid().setIntervalY(y_interval)

    # calculate legend element
    for leg_el in context.map_legends:
        item_id = leg_el.get('id')
        title = leg_el.get('title')
        layers = leg_el.get('layers')
        symbol_count = leg_el.get('symbol_count')
        column_count = leg_el.get('column_count')

        legend = composition.getComposerItemById(item_id)
        """:type: qgis.core.QgsComposerLegend"""
        if isinstance(legend, QgsComposerLegend):
            # set column count
            if column_count:
                legend.setColumnCount(column_count)
            elif symbol_count <= 5:
                legend.setColumnCount(1)
            else:
                legend.setColumnCount(symbol_count / 5 + 1)

            # set legend title
            if title is not None:
                legend.setTitle(title)

            # set legend
            root_group = legend.modelV2().rootGroup()
            for l in layers:
                # used for customizations
                tree_layer = root_group.addLayer(l)
                QgsLegendRenderer.setNodeLegendStyle(
                    tree_layer, QgsComposerLegendStyle.Hidden)
            legend.synchronizeWithModel()

    # process to output

    # in case output folder not specified
    if impact_report.output_folder is None:
        impact_report.output_folder = mkdtemp(dir=temp_dir())

    output_format = component.output_format
    component_output_path = impact_report.component_absolute_output_path(
        component.key)
    component_output = None

    doc_format = QgisComposerComponentsMetadata.OutputFormat.DOC_OUTPUT
    template_format = QgisComposerComponentsMetadata.OutputFormat.QPT
    if isinstance(output_format, list):
        component_output = []
        for i in range(len(output_format)):
            each_format = output_format[i]
            each_path = component_output_path[i]

            if each_format in doc_format:
                result_path = create_qgis_pdf_output(
                    each_path,
                    composition,
                    impact_report.qgis_composition_context,
                    each_format,
                    component)
                component_output.append(result_path)
            elif each_format == template_format:
                result_path = create_qgis_template_output(
                    each_path, composition)
                component_output.append(result_path)
    elif isinstance(output_format, dict):
        component_output = {}
        for key, each_format in output_format.iteritems():
            each_path = component_output_path[key]

            if each_format in doc_format:
                result_path = create_qgis_pdf_output(
                    each_path,
                    composition,
                    impact_report.qgis_composition_context,
                    each_format,
                    component)
                component_output[key] = result_path
            elif each_format == template_format:
                result_path = create_qgis_template_output(
                    each_path, composition)
                component_output[key] = result_path
    elif (output_format in
            QgisComposerComponentsMetadata.OutputFormat.SUPPORTED_OUTPUT):
        component_output = None

        if output_format in doc_format:
            result_path = create_qgis_pdf_output(
                component_output_path,
                composition,
                impact_report.qgis_composition_context,
                output_format,
                component)
            component_output = result_path
        elif output_format == template_format:
            result_path = create_qgis_template_output(
                component_output_path, composition)
            component_output = result_path

    component.output = component_output

    return component.output
示例#20
0
    def run(self, *args, **kwargs):
        """
        :param templatePath: The file path to the user-defined template.
        :param entityFieldName: The name of the column for the specified entity which
        must exist in the data source view or table.
        :param entityFieldValue: The value for filtering the records in the data source
        view or table.
        :param outputMode: Whether the output composition should be an image or PDF.
        :param filePath: The output file where the composition will be written to. Applies
        to single mode output generation.
        :param dataFields: List containing the field names whose values will be used to name the files.
        This is used in multiple mode configuration.
        :param fileExtension: The output file format. Used in multiple mode configuration.
        :param dbmodel: In order to name the files using the custom column mapping, a callable
        sqlalchemy data model must be specified.
        """
        #Unpack arguments
        templatePath = args[0]
        entityFieldName = args[1]
        entityFieldValue = args[2]
        outputMode = args[3]
        filePath = kwargs.get("filePath", None)
        dataFields = kwargs.get("dataFields", [])
        fileExtension = kwargs.get("fileExtension", "")
        dataModel = kwargs.get("dbmodel", None)

        templateFile = QFile(templatePath)

        if not templateFile.open(QIODevice.ReadOnly):
            return (False,
                    QApplication.translate("DocumentGenerator",
                                           "Cannot read template file."))

        templateDoc = QDomDocument()

        if templateDoc.setContent(templateFile):
            composerDS = ComposerDataSource.create(templateDoc)
            spatialFieldsConfig = SpatialFieldsConfiguration.create(
                templateDoc)
            composerDS.setSpatialFieldsConfig(spatialFieldsConfig)

            #Execute query
            dsTable, records = self._execQuery(composerDS.name(),
                                               entityFieldName,
                                               entityFieldValue)

            if records == None:
                return (False,
                        QApplication.translate(
                            "DocumentGenerator",
                            "No matching records in the database"))
            """
            Iterate through records where a single file output will be generated for each matching record.
            """
            for rec in records:
                composition = QgsComposition(self._mapRenderer)
                composition.loadFromTemplate(templateDoc)

                #Set value of composer items based on the corresponding db values
                for composerId in composerDS.dataFieldMappings().reverse:
                    #Use composer item id since the uuid is stripped off
                    composerItem = composition.getComposerItemById(composerId)

                    if composerItem != None:
                        fieldName = composerDS.dataFieldName(composerId)
                        fieldValue = getattr(rec, fieldName)
                        self._composerItemValueHandler(composerItem,
                                                       fieldValue)

                #Create memory layers for spatial features and add them to the map
                for mapId, spfmList in spatialFieldsConfig.spatialFieldsMapping(
                ).iteritems():
                    mapItem = composition.getComposerItemById(mapId)

                    if mapItem != None:
                        #Clear any previous memory layer
                        self.clearTemporaryLayers()

                        for spfm in spfmList:
                            #Use the value of the label field to name the layer
                            layerName = getattr(rec, spfm.labelField())

                            #Extract the geometry using geoalchemy spatial capabilities
                            geomFunc = getattr(
                                rec, spfm.spatialField()).ST_AsText()
                            geomWKT = self._dbSession.scalar(geomFunc)

                            #Create reference layer with feature
                            refLayer = self._buildVectorLayer(layerName)

                            #Add feature
                            bbox = self._addFeatureToLayer(refLayer, geomWKT)
                            bbox.scale(spfm.zoomLevel())

                            #Add layer to map
                            QgsMapLayerRegistry.instance().addMapLayer(
                                refLayer)
                            self._iface.mapCanvas().setExtent(bbox)
                            self._iface.mapCanvas().refresh()

                            #mapItem.storeCurrentLayerSet()
                            #mapItem.updateCachedImage()

                            #Add layer to memory layer list
                            self._memoryLayers.append(refLayer)

                        mapItem.setNewExtent(self._mapRenderer.extent())

                #Build output path and generate composition
                if filePath != None and len(dataFields) == 0:
                    self._writeOutput(composition, outputMode, filePath)

                elif filePath == None and len(dataFields) > 0:
                    docFileName = self._buildFileName(dataModel,
                                                      entityFieldName,
                                                      entityFieldValue,
                                                      dataFields,
                                                      fileExtension)
                    if docFileName == "":
                        return (
                            False,
                            QApplication.translate(
                                "DocumentGenerator",
                                "File name could not be generated from the data fields."
                            ))

                    outputDir = self._composerOutputPath()
                    if outputDir == None:
                        return (
                            False,
                            QApplication.translate(
                                "DocumentGenerator",
                                "System could not read the location of the output directory in the registry."
                            ))

                    qDir = QDir()
                    if not qDir.exists(outputDir):
                        return (False,
                                QApplication.translate(
                                    "DocumentGenerator",
                                    "Output directory does not exist"))

                    absDocPath = unicode(outputDir) + "/" + docFileName
                    self._writeOutput(composition, outputMode, absDocPath)

            #Clear temporary layers
            self.clearTemporaryLayers()

            return (True, "Success")

        return (False, "Composition could not be generated")
示例#21
0
    def generate_report(self):
        # Generate pdf report from impact/hazard
        LOGGER.info('Generating report')
        if not self.impact_exists:
            # Cannot generate report when no impact layer present
            LOGGER.info('Cannot Generate report when no impact present.')
            return

        project_instance = QgsProject.instance()
        project_instance.setFileName(self.project_path)
        project_instance.read()

        # get layer registry
        layer_registry = QgsMapLayerRegistry.instance()
        layer_registry.removeAllMapLayers()

        # Set up the map renderer that will be assigned to the composition
        map_renderer = CANVAS.mapRenderer()

        # Enable on the fly CRS transformations
        map_renderer.setProjectionsEnabled(True)

        default_crs = map_renderer.destinationCrs()
        crs = QgsCoordinateReferenceSystem('EPSG:4326')
        map_renderer.setDestinationCrs(crs)

        # add place name layer
        layer_registry.addMapLayer(self.cities_layer, False)

        # add airport layer
        layer_registry.addMapLayer(self.airport_layer, False)

        # add volcano layer
        layer_registry.addMapLayer(self.volcano_layer, False)

        # add impact layer
        hazard_layer = read_qgis_layer(self.hazard_path,
                                       self.tr('People Affected'))
        layer_registry.addMapLayer(hazard_layer, False)

        # add basemap layer
        layer_registry.addMapLayer(self.highlight_base_layer, False)

        # add basemap layer
        layer_registry.addMapLayer(self.overview_layer, False)

        CANVAS.setExtent(hazard_layer.extent())
        CANVAS.refresh()

        template_path = self.ash_fixtures_dir('realtime-ash.qpt')

        with open(template_path) as f:
            template_content = f.read()

        document = QDomDocument()
        document.setContent(template_content)

        # Now set up the composition
        # map_settings = QgsMapSettings()
        # composition = QgsComposition(map_settings)
        composition = QgsComposition(map_renderer)

        subtitution_map = self.event_dict()
        LOGGER.debug(subtitution_map)

        # load composition object from template
        result = composition.loadFromTemplate(document, subtitution_map)
        if not result:
            LOGGER.exception('Error loading template %s with keywords\n %s',
                             template_path, subtitution_map)
            raise MapComposerError

        # get main map canvas on the composition and set extent
        map_impact = composition.getComposerItemById('map-impact')
        if map_impact:
            map_impact.zoomToExtent(hazard_layer.extent())
            map_impact.renderModeUpdateCachedImage()
        else:
            LOGGER.exception('Map canvas could not be found in template %s',
                             template_path)
            raise MapComposerError

        # get overview map canvas on the composition and set extent
        map_overall = composition.getComposerItemById('map-overall')
        if map_overall:
            map_overall.setLayerSet([self.overview_layer.id()])
            # this is indonesia extent
            indonesia_extent = QgsRectangle(94.0927980005593554,
                                            -15.6629591962689343,
                                            142.0261493318861312,
                                            10.7379406374101816)
            map_overall.zoomToExtent(indonesia_extent)
            map_overall.renderModeUpdateCachedImage()
        else:
            LOGGER.exception('Map canvas could not be found in template %s',
                             template_path)
            raise MapComposerError

        # setup impact table
        self.render_population_table()
        self.render_nearby_table()
        self.render_landcover_table()

        impact_table = composition.getComposerItemById('table-impact')
        if impact_table is None:
            message = 'table-impact composer item could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        impacts_html = composition.getComposerHtmlByItem(impact_table)
        if impacts_html is None:
            message = 'Impacts QgsComposerHtml could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        impacts_html.setUrl(QUrl(self.population_html_path))

        # setup nearby table
        nearby_table = composition.getComposerItemById('table-nearby')
        if nearby_table is None:
            message = 'table-nearby composer item could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        nearby_html = composition.getComposerHtmlByItem(nearby_table)
        if nearby_html is None:
            message = 'Nearby QgsComposerHtml could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        nearby_html.setUrl(QUrl(self.nearby_html_path))

        # setup landcover table
        landcover_table = composition.getComposerItemById('table-landcover')
        if landcover_table is None:
            message = 'table-landcover composer item could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        landcover_html = composition.getComposerHtmlByItem(landcover_table)
        if landcover_html is None:
            message = 'Landcover QgsComposerHtml could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        landcover_html.setUrl(QUrl(self.landcover_html_path))

        # setup logos
        logos_id = ['logo-bnpb', 'logo-geologi']
        for logo_id in logos_id:
            logo_picture = composition.getComposerItemById(logo_id)
            if logo_picture is None:
                message = '%s composer item could not be found' % logo_id
                LOGGER.exception(message)
                raise MapComposerError(message)
            pic_path = os.path.basename(logo_picture.picturePath())
            pic_path = os.path.join('logo', pic_path)
            logo_picture.setPicturePath(self.ash_fixtures_dir(pic_path))

        # save a pdf
        composition.exportAsPDF(self.map_report_path)

        project_instance.write(QFileInfo(self.project_path))

        layer_registry.removeAllMapLayers()
        map_renderer.setDestinationCrs(default_crs)
        map_renderer.setProjectionsEnabled(False)
        LOGGER.info('Report generation completed.')
示例#22
0
文件: map.py 项目: vdeparday/inasafe
class Map():
    """A class for creating a map."""
    def __init__(self, iface):
        """Constructor for the Map class.

        :param iface: Reference to the QGIS iface object.
        :type iface: QgsAppInterface
        """
        LOGGER.debug('InaSAFE Map class initialised')
        self.iface = iface
        self.layer = iface.activeLayer()
        self.keyword_io = KeywordIO()
        self.printer = None
        self.composition = None
        self.legend = None
        self.logo = ':/plugins/inasafe/bnpb_logo.png'
        self.template = ':/plugins/inasafe/inasafe.qpt'
        #self.page_width = 210  # width in mm
        #self.page_height = 297  # height in mm
        self.page_width = 0  # width in mm
        self.page_height = 0  # height in mm
        self.page_dpi = 300.0
        #self.page_margin = 10  # margin in mm
        self.show_frames = False  # intended for debugging use only
        self.page_margin = None
        #vertical spacing between elements
        self.vertical_spacing = None
        self.map_height = None
        self.mapWidth = None
        # make a square map where width = height = page width
        #self.map_height = self.page_width - (self.page_margin * 2)
        #self.mapWidth = self.map_height
        #self.disclaimer = self.tr('InaSAFE has been jointly developed by'
        #                          ' BNPB, AusAid & the World Bank')

    @staticmethod
    def tr(string):
        """We implement this since we do not inherit QObject.

        :param string: String for translation.
        :type string: QString, str

        :returns: Translated version of theString.
        :rtype: QString
        """
        # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
        return QtCore.QCoreApplication.translate('Map', string)

    def set_impact_layer(self, layer):
        """Set the layer that will be used for stats, legend and reporting.

        :param layer: Layer that will be used for stats, legend and reporting.
        :type layer: QgsMapLayer, QgsRasterLayer, QgsVectorLayer
        """
        self.layer = layer

    def set_logo(self, logo):
        """

        :param logo: Path to image that will be used as logo in report
        :type logo: str
        """
        self.logo = logo

    def set_template(self, template):
        """

        :param template: Path to composer template that will be used for report
        :type template: str
        """
        self.template = template

    def setup_composition(self):
        """Set up the composition ready for drawing elements onto it."""
        LOGGER.debug('InaSAFE Map setupComposition called')
        canvas = self.iface.mapCanvas()
        renderer = canvas.mapRenderer()
        self.composition = QgsComposition(renderer)
        self.composition.setPlotStyle(QgsComposition.Print)  # or preview
        #self.composition.setPaperSize(self.page_width, self.page_height)
        self.composition.setPrintResolution(self.page_dpi)
        self.composition.setPrintAsRaster(True)

    def compose_map(self):
        """Place all elements on the map ready for printing."""
        self.setup_composition()
        # Keep track of our vertical positioning as we work our way down
        # the page placing elements on it.
        top_offset = self.page_margin
        self.draw_logo(top_offset)
        label_height = self.draw_title(top_offset)
        # Update the map offset for the next row of content
        top_offset += label_height + self.vertical_spacing
        composer_map = self.draw_map(top_offset)
        self.draw_scalebar(composer_map, top_offset)
        # Update the top offset for the next horizontal row of items
        top_offset += self.map_height + self.vertical_spacing - 1
        impact_title_height = self.draw_impact_title(top_offset)
        # Update the top offset for the next horizontal row of items
        if impact_title_height:
            top_offset += impact_title_height + self.vertical_spacing + 2
        self.draw_legend(top_offset)
        self.draw_host_and_time(top_offset)
        self.draw_disclaimer()

    def render(self):
        """Render the map composition to an image and save that to disk.

        :returns: A three-tuple of:
            * str: image_path - absolute path to png of rendered map
            * QImage: image - in memory copy of rendered map
            * QRectF: target_area - dimensions of rendered map
        :rtype: tuple
        """
        LOGGER.debug('InaSAFE Map renderComposition called')
        # NOTE: we ignore self.composition.printAsRaster() and always rasterise
        width = int(self.page_dpi * self.page_width / 25.4)
        height = int(self.page_dpi * self.page_height / 25.4)
        image = QtGui.QImage(
            QtCore.QSize(width, height),
            QtGui.QImage.Format_ARGB32)
        image.setDotsPerMeterX(dpi_to_meters(self.page_dpi))
        image.setDotsPerMeterY(dpi_to_meters(self.page_dpi))

        # Only works in Qt4.8
        #image.fill(QtGui.qRgb(255, 255, 255))
        # Works in older Qt4 versions
        image.fill(55 + 255 * 256 + 255 * 256 * 256)
        image_painter = QtGui.QPainter(image)
        source_area = QtCore.QRectF(
            0, 0, self.page_width,
            self.page_height)
        target_area = QtCore.QRectF(0, 0, width, height)
        self.composition.render(image_painter, target_area, source_area)
        image_painter.end()
        image_path = unique_filename(
            prefix='mapRender_',
            suffix='.png',
            dir=temp_dir())
        image.save(image_path)
        return image_path, image, target_area

    def make_pdf(self, filename):
        """Generate the printout for our final map.

        :param filename: Path on the file system to which the pdf should be
            saved. If None, a generated file name will be used.
        :type filename: str

        :returns: File name of the output file (equivalent to filename if
                provided).
        :rtype: str
        """
        LOGGER.debug('InaSAFE Map printToPdf called')
        if filename is None:
            map_pdf_path = unique_filename(
                prefix='report', suffix='.pdf', dir=temp_dir())
        else:
            # We need to cast to python string in case we receive a QString
            map_pdf_path = str(filename)

        self.load_template()

        resolution = self.composition.printResolution()
        self.printer = setup_printer(map_pdf_path, resolution=resolution)
        _, image, rectangle = self.render()
        painter = QtGui.QPainter(self.printer)
        painter.drawImage(rectangle, image, rectangle)
        painter.end()
        return map_pdf_path

    def draw_logo(self, top_offset):
        """Add a picture containing the logo to the map top left corner

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        logo = QgsComposerPicture(self.composition)
        logo.setPictureFile(':/plugins/inasafe/bnpb_logo.png')
        logo.setItemPosition(self.page_margin, top_offset, 10, 10)
        logo.setFrameEnabled(self.show_frames)
        logo.setZValue(1)  # To ensure it overlays graticule markers
        self.composition.addItem(logo)

    def draw_title(self, top_offset):
        """Add a title to the composition.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int

        :returns: The height of the label as rendered.
        :rtype: float
        """
        LOGGER.debug('InaSAFE Map drawTitle called')
        font_size = 14
        font_weight = QtGui.QFont.Bold
        italics_flag = False
        font = QtGui.QFont(
            'verdana',
            font_size,
            font_weight,
            italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        heading = self.tr(
            'InaSAFE - Indonesia Scenario Assessment for Emergencies')
        label.setText(heading)
        label.adjustSizeToText()
        label_height = 10.0  # determined using qgis map composer
        label_width = 170.0   # item - position and size...option
        left_offset = self.page_width - self.page_margin - label_width
        label.setItemPosition(
            left_offset,
            top_offset - 2,  # -2 to push it up a little
            label_width,
            label_height)
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)
        return label_height

    def draw_map(self, top_offset):
        """Add a map to the composition and return the composer map instance.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int

        :returns: The composer map.
        :rtype: QgsComposerMap
        """
        LOGGER.debug('InaSAFE Map drawMap called')
        map_width = self.mapWidth
        composer_map = QgsComposerMap(
            self.composition,
            self.page_margin,
            top_offset,
            map_width,
            self.map_height)
        #myExtent = self.iface.mapCanvas().extent()
        # The dimensions of the map canvas and the print composer map may
        # differ. So we set the map composer extent using the canvas and
        # then defer to the map canvas's map extents thereafter
        # Update: disabled as it results in a rectangular rather than
        # square map
        #composer_map.setNewExtent(myExtent)
        composer_extent = composer_map.extent()
        # Recenter the composer map on the center of the canvas
        # Note that since the composer map is square and the canvas may be
        # arbitrarily shaped, we center based on the longest edge
        canvas_extent = self.iface.mapCanvas().extent()
        width = canvas_extent.width()
        height = canvas_extent.height()
        longest_length = width
        if width < height:
            longest_length = height
        half_length = longest_length / 2
        center = canvas_extent.center()
        min_x = center.x() - half_length
        max_x = center.x() + half_length
        min_y = center.y() - half_length
        max_y = center.y() + half_length
        square_extent = QgsRectangle(min_x, min_y, max_x, max_y)
        composer_map.setNewExtent(square_extent)

        composer_map.setGridEnabled(True)
        split_count = 5
        # .. todo:: Write logic to adjust precision so that adjacent tick marks
        #    always have different displayed values
        precision = 2
        x_interval = composer_extent.width() / split_count
        composer_map.setGridIntervalX(x_interval)
        y_interval = composer_extent.height() / split_count
        composer_map.setGridIntervalY(y_interval)
        composer_map.setGridStyle(QgsComposerMap.Cross)
        cross_length_mm = 1
        composer_map.setCrossLength(cross_length_mm)
        composer_map.setZValue(0)  # To ensure it does not overlay logo
        font_size = 6
        font_weight = QtGui.QFont.Normal
        italics_flag = False
        font = QtGui.QFont(
            'verdana',
            font_size,
            font_weight,
            italics_flag)
        composer_map.setGridAnnotationFont(font)
        composer_map.setGridAnnotationPrecision(precision)
        composer_map.setShowGridAnnotation(True)
        composer_map.setGridAnnotationDirection(
            QgsComposerMap.BoundaryDirection, QgsComposerMap.Top)
        self.composition.addItem(composer_map)
        self.draw_graticule_mask(top_offset)
        return composer_map

    def draw_graticule_mask(self, top_offset):
        """A helper function to mask out graticule labels.

         It will hide labels on the right side by over painting a white
         rectangle with white border on them. **kludge**

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawGraticuleMask called')
        left_offset = self.page_margin + self.mapWidth
        rect = QgsComposerShape(
            left_offset + 0.5,
            top_offset,
            self.page_width - left_offset,
            self.map_height + 1,
            self.composition)

        rect.setShapeType(QgsComposerShape.Rectangle)
        pen = QtGui.QPen()
        pen.setColor(QtGui.QColor(0, 0, 0))
        pen.setWidthF(0.1)
        rect.setPen(pen)
        rect.setBackgroundColor(QtGui.QColor(255, 255, 255))
        rect.setTransparency(100)
        #rect.setLineWidth(0.1)
        #rect.setFrameEnabled(False)
        #rect.setOutlineColor(QtGui.QColor(255, 255, 255))
        #rect.setFillColor(QtGui.QColor(255, 255, 255))
        #rect.setOpacity(100)
        # These two lines seem superfluous but are needed
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        rect.setBrush(brush)
        self.composition.addItem(rect)

    def draw_native_scalebar(self, composer_map, top_offset):
        """Draw a scale bar using QGIS' native drawing.

        In the case of geographic maps, scale will be in degrees, not km.

        :param composer_map: Composer map on which to draw the scalebar.
        :type composer_map: QgsComposerMap

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawNativeScaleBar called')
        scale_bar = QgsComposerScaleBar(self.composition)
        scale_bar.setStyle('Numeric')  # optionally modify the style
        scale_bar.setComposerMap(composer_map)
        scale_bar.applyDefaultSize()
        scale_bar_height = scale_bar.boundingRect().height()
        scale_bar_width = scale_bar.boundingRect().width()
        # -1 to avoid overlapping the map border
        scale_bar.setItemPosition(
            self.page_margin + 1,
            top_offset + self.map_height - (scale_bar_height * 2),
            scale_bar_width,
            scale_bar_height)
        scale_bar.setFrameEnabled(self.show_frames)
        # Disabled for now
        #self.composition.addItem(scale_bar)

    def draw_scalebar(self, composer_map, top_offset):
        """Add a numeric scale to the bottom left of the map.

        We draw the scale bar manually because QGIS does not yet support
        rendering a scale bar for a geographic map in km.

        .. seealso:: :meth:`drawNativeScaleBar`

        :param composer_map: Composer map on which to draw the scalebar.
        :type composer_map: QgsComposerMap

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawScaleBar called')
        canvas = self.iface.mapCanvas()
        renderer = canvas.mapRenderer()
        #
        # Add a linear map scale
        #
        distance_area = QgsDistanceArea()
        distance_area.setSourceCrs(renderer.destinationCrs().srsid())
        distance_area.setEllipsoidalMode(True)
        # Determine how wide our map is in km/m
        # Starting point at BL corner
        composer_extent = composer_map.extent()
        start_point = QgsPoint(
            composer_extent.xMinimum(),
            composer_extent.yMinimum())
        # Ending point at BR corner
        end_point = QgsPoint(
            composer_extent.xMaximum(),
            composer_extent.yMinimum())
        ground_distance = distance_area.measureLine(start_point, end_point)
        # Get the equivalent map distance per page mm
        map_width = self.mapWidth
        # How far is 1mm on map on the ground in meters?
        mm_to_ground = ground_distance / map_width
        #print 'MM:', myMMDistance
        # How long we want the scale bar to be in relation to the map
        scalebar_to_map_ratio = 0.5
        # How many divisions the scale bar should have
        tick_count = 5
        scale_bar_width_mm = map_width * scalebar_to_map_ratio
        print_segment_width_mm = scale_bar_width_mm / tick_count
        # Segment width in real world (m)
        # We apply some logic here so that segments are displayed in meters
        # if each segment is less that 1000m otherwise km. Also the segment
        # lengths are rounded down to human looking numbers e.g. 1km not 1.1km
        units = ''
        ground_segment_width = print_segment_width_mm * mm_to_ground
        if ground_segment_width < 1000:
            units = 'm'
            ground_segment_width = round(ground_segment_width)
            # adjust the segment width now to account for rounding
            print_segment_width_mm = ground_segment_width / mm_to_ground
        else:
            units = 'km'
            # Segment with in real world (km)
            ground_segment_width = round(ground_segment_width / 1000)
            print_segment_width_mm = (
                (ground_segment_width * 1000) / mm_to_ground)
        # Now adjust the scalebar width to account for rounding
        scale_bar_width_mm = tick_count * print_segment_width_mm

        #print "SBWMM:", scale_bar_width_mm
        #print "SWMM:", print_segment_width_mm
        #print "SWM:", myGroundSegmentWidthM
        #print "SWKM:", myGroundSegmentWidthKM
        # start drawing in line segments
        scalebar_height = 5  # mm
        line_width = 0.3  # mm
        inset_distance = 7  # how much to inset the scalebar into the map by
        scalebar_x = self.page_margin + inset_distance
        scalebar_y = (
            top_offset + self.map_height - inset_distance -
            scalebar_height)  # mm

        # Draw an outer background box - shamelessly hardcoded buffer
        rectangle = QgsComposerShape(
            scalebar_x - 4,  # left edge
            scalebar_y - 3,  # top edge
            scale_bar_width_mm + 13,  # right edge
            scalebar_height + 6,  # bottom edge
            self.composition)

        rectangle.setShapeType(QgsComposerShape.Rectangle)
        pen = QtGui.QPen()
        pen.setColor(QtGui.QColor(255, 255, 255))
        pen.setWidthF(line_width)
        rectangle.setPen(pen)
        #rectangle.setLineWidth(line_width)
        rectangle.setFrameEnabled(False)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        # workaround for missing setTransparentFill missing from python api
        rectangle.setBrush(brush)
        self.composition.addItem(rectangle)
        # Set up the tick label font
        font_weight = QtGui.QFont.Normal
        font_size = 6
        italics_flag = False
        font = QtGui.QFont(
            'verdana',
            font_size,
            font_weight,
            italics_flag)
        # Draw the bottom line
        up_shift = 0.3  # shift the bottom line up for better rendering
        rectangle = QgsComposerShape(
            scalebar_x,
            scalebar_y + scalebar_height - up_shift,
            scale_bar_width_mm,
            0.1,
            self.composition)

        rectangle.setShapeType(QgsComposerShape.Rectangle)
        pen = QtGui.QPen()
        pen.setColor(QtGui.QColor(255, 255, 255))
        pen.setWidthF(line_width)
        rectangle.setPen(pen)
        #rectangle.setLineWidth(line_width)
        rectangle.setFrameEnabled(False)
        self.composition.addItem(rectangle)

        # Now draw the scalebar ticks
        for tick_counter in range(0, tick_count + 1):
            distance_suffix = ''
            if tick_counter == tick_count:
                distance_suffix = ' ' + units
            real_world_distance = (
                '%.0f%s' %
                (tick_counter *
                ground_segment_width,
                distance_suffix))
            #print 'RW:', myRealWorldDistance
            mm_offset = scalebar_x + (
                tick_counter * print_segment_width_mm)
            #print 'MM:', mm_offset
            tick_height = scalebar_height / 2
            # Lines are not exposed by the api yet so we
            # bodge drawing lines using rectangles with 1px height or width
            tick_width = 0.1  # width or rectangle to be drawn
            uptick_line = QgsComposerShape(
                mm_offset,
                scalebar_y + scalebar_height - tick_height,
                tick_width,
                tick_height,
                self.composition)

            uptick_line.setShapeType(QgsComposerShape.Rectangle)
            pen = QtGui.QPen()
            pen.setWidthF(line_width)
            uptick_line.setPen(pen)
            #uptick_line.setLineWidth(line_width)
            uptick_line.setFrameEnabled(False)
            self.composition.addItem(uptick_line)
            #
            # Add a tick label
            #
            label = QgsComposerLabel(self.composition)
            label.setFont(font)
            label.setText(real_world_distance)
            label.adjustSizeToText()
            label.setItemPosition(
                mm_offset - 3,
                scalebar_y - tick_height)
            label.setFrameEnabled(self.show_frames)
            self.composition.addItem(label)

    def draw_impact_title(self, top_offset):
        """Draw the map subtitle - obtained from the impact layer keywords.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int

        :returns: The height of the label as rendered.
        :rtype: float
        """
        LOGGER.debug('InaSAFE Map drawImpactTitle called')
        title = self.map_title()
        if title is None:
            title = ''
        font_size = 20
        font_weight = QtGui.QFont.Bold
        italics_flag = False
        font = QtGui.QFont(
            'verdana', font_size, font_weight, italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        heading = title
        label.setText(heading)
        label_width = self.page_width - (self.page_margin * 2)
        label_height = 12
        label.setItemPosition(
            self.page_margin, top_offset, label_width, label_height)
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)
        return label_height

    def draw_legend(self, top_offset):
        """Add a legend to the map using our custom legend renderer.

        .. note:: getLegend generates a pixmap in 150dpi so if you set
           the map to a higher dpi it will appear undersized.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawLegend called')
        legend_attributes = self.map_legend_attributes()
        legend_notes = legend_attributes.get('legend_notes', None)
        legend_units = legend_attributes.get('legend_units', None)
        legend_title = legend_attributes.get('legend_title', None)
        LOGGER.debug(legend_attributes)
        legend = MapLegend(
            self.layer,
            self.page_dpi,
            legend_title,
            legend_notes,
            legend_units)
        self.legend = legend.get_legend()
        picture1 = QgsComposerPicture(self.composition)
        legend_file_path = unique_filename(
            prefix='legend', suffix='.png', dir='work')
        self.legend.save(legend_file_path, 'PNG')
        picture1.setPictureFile(legend_file_path)
        legend_height = points_to_mm(self.legend.height(), self.page_dpi)
        legend_width = points_to_mm(self.legend.width(), self.page_dpi)
        picture1.setItemPosition(
            self.page_margin,
            top_offset,
            legend_width,
            legend_height)
        picture1.setFrameEnabled(False)
        self.composition.addItem(picture1)
        os.remove(legend_file_path)

    def draw_image(self, image, width_mm, left_offset, top_offset):
        """Helper to draw an image directly onto the QGraphicsScene.
        This is an alternative to using QgsComposerPicture which in
        some cases leaves artifacts under windows.

        The Pixmap will have a transform applied to it so that
        it is rendered with the same resolution as the composition.

        :param image: Image that will be rendered to the layout.
        :type image: QImage

        :param width_mm: Desired width in mm of output on page.
        :type width_mm: int

        :param left_offset: Offset from left of page.
        :type left_offset: int

        :param top_offset: Offset from top of page.
        :type top_offset: int

        :returns: Graphics scene item.
        :rtype: QGraphicsSceneItem
        """
        LOGGER.debug('InaSAFE Map drawImage called')
        desired_width_mm = width_mm  # mm
        desired_width_px = mm_to_points(desired_width_mm, self.page_dpi)
        actual_width_px = image.width()
        scale_factor = desired_width_px / actual_width_px

        LOGGER.debug('%s %s %s' % (
            scale_factor, actual_width_px, desired_width_px))
        transform = QtGui.QTransform()
        transform.scale(scale_factor, scale_factor)
        transform.rotate(0.5)
        # noinspection PyArgumentList
        item = self.composition.addPixmap(QtGui.QPixmap.fromImage(image))
        item.setTransform(transform)
        item.setOffset(
            left_offset / scale_factor, top_offset / scale_factor)
        return item

    def draw_host_and_time(self, top_offset):
        """Add a note with hostname and time to the composition.

        :param top_offset: Vertical offset at which the logo should be drawn.
        :type top_offset: int
        """
        LOGGER.debug('InaSAFE Map drawDisclaimer called')
        #elapsed_time: 11.612545
        #user: timlinux
        #host_name: ultrabook
        #time_stamp: 2012-10-13_23:10:31
        #myUser = self.keyword_io.readKeywords(self.layer, 'user')
        #myHost = self.keyword_io.readKeywords(self.layer, 'host_name')
        date_time = self.keyword_io.read_keywords(self.layer, 'time_stamp')
        tokens = date_time.split('_')
        date = tokens[0]
        time = tokens[1]
        #myElapsedTime = self.keyword_io.readKeywords(self.layer,
        #                                            'elapsed_time')
        #myElapsedTime = humaniseSeconds(myElapsedTime)
        long_version = get_version()
        tokens = long_version.split('.')
        version = '%s.%s.%s' % (tokens[0], tokens[1], tokens[2])
        label_text = self.tr(
            'Date and time of assessment: %s %s\n'
            'Special note: This assessment is a guide - we strongly recommend '
            'that you ground truth the results shown here before deploying '
            'resources and / or personnel.\n'
            'Assessment carried out using InaSAFE release %s (QGIS '
            'plugin version).') % (date, time, version)
        font_size = 6
        font_weight = QtGui.QFont.Normal
        italics_flag = True
        font = QtGui.QFont(
            'verdana',
            font_size,
            font_weight,
            italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        label.setText(label_text)
        label.adjustSizeToText()
        label_height = 50.0  # mm determined using qgis map composer
        label_width = (self.page_width / 2) - self.page_margin
        left_offset = self.page_width / 2  # put in right half of page
        label.setItemPosition(
            left_offset,
            top_offset,
            label_width,
            label_height,)
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)

    def draw_disclaimer(self):
        """Add a disclaimer to the composition."""
        LOGGER.debug('InaSAFE Map drawDisclaimer called')
        font_size = 10
        font_weight = QtGui.QFont.Normal
        italics_flag = True
        font = QtGui.QFont(
            'verdana',
            font_size,
            font_weight,
            italics_flag)
        label = QgsComposerLabel(self.composition)
        label.setFont(font)
        label.setText(self.disclaimer)
        label.adjustSizeToText()
        label_height = 7.0  # mm determined using qgis map composer
        label_width = self.page_width   # item - position and size...option
        left_offset = self.page_margin
        top_offset = self.page_height - self.page_margin
        label.setItemPosition(
            left_offset,
            top_offset,
            label_width,
            label_height,)
        label.setFrameEnabled(self.show_frames)
        self.composition.addItem(label)

    def map_title(self):
        """Get the map title from the layer keywords if possible.

        :returns: None on error, otherwise the title.
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapTitle called')
        try:
            title = self.keyword_io.read_keywords(self.layer, 'map_title')
            return title
        except KeywordNotFoundError:
            return None
        except Exception:
            return None

    def map_legend_attributes(self):
        """Get the map legend attribute from the layer keywords if possible.

        :returns: None on error, otherwise the attributes (notes and units).
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapLegendAtributes called')
        legend_attribute_list = [
            'legend_notes',
            'legend_units',
            'legend_title']
        legend_attribute_dict = {}
        for myLegendAttribute in legend_attribute_list:
            try:
                legend_attribute_dict[myLegendAttribute] = \
                    self.keyword_io.read_keywords(
                        self.layer, myLegendAttribute)
            except KeywordNotFoundError:
                pass
            except Exception:
                pass
        return legend_attribute_dict

    def show_composer(self):
        """Show the composition in a composer view so the user can tweak it.
        """
        view = QgsComposerView(self.iface.mainWindow())
        view.show()

    def write_template(self, template_path):
        """Write current composition as a template that can be re-used in QGIS.

        :param template_path: Path to which template should be written.
        :type template_path: str
        """
        document = QtXml.QDomDocument()
        element = document.createElement('Composer')
        document.appendChild(element)
        self.composition.writeXML(element, document)
        xml = document.toByteArray()
        template_file = file(template_path, 'wb')
        template_file.write(xml)
        template_file.close()

    def load_template(self):
        """Load a QgsComposer map from a template and render it.

        .. note:: THIS METHOD IS EXPERIMENTAL
        """
        self.setup_composition()

        template_file = QtCore.QFile(self.template)
        template_file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()

        document = QtXml.QDomDocument()
        document.setContent(template_content)

        # get information for substitutions
        # date, time and plugin version
        date_time = self.keyword_io.read_keywords(self.layer, 'time_stamp')
        tokens = date_time.split('_')
        date = tokens[0]
        time = tokens[1]
        long_version = get_version()
        tokens = long_version.split('.')
        version = '%s.%s.%s' % (tokens[0], tokens[1], tokens[2])

        # map title
        LOGGER.debug('InaSAFE Map getMapTitle called')
        try:
            title = self.keyword_io.read_keywords(self.layer, 'map_title')
        except KeywordNotFoundError:
            title = None
        except Exception:
            title = None

        if not title:
            title = ''

        substitution_map = {
            'impact-title': title,
            'date': date,
            'time': time,
            'safe-version': version
        }
        LOGGER.debug(substitution_map)
        load_ok = self.composition.loadFromTemplate(document,
                                                    substitution_map)
        if not load_ok:
            raise ReportCreationError(
                self.tr('Error loading template %s') %
                self.template)

        self.page_width = self.composition.paperWidth()
        self.page_height = self.composition.paperHeight()

        # set logo
        image = self.composition.getComposerItemById('safe-logo')
        image.setPictureFile(self.logo)

        # Get the main map canvas on the composition and set
        # its extents to the event.
        map = self.composition.getComposerItemById('impact-map')
        if map is not None:
            # Recenter the composer map on the center of the canvas
            # Note that since the composer map is square and the canvas may be
            # arbitrarily shaped, we center based on the longest edge
            canvas_extent = self.iface.mapCanvas().extent()
            width = canvas_extent.width()
            height = canvas_extent.height()
            longest_width = width
            if width < height:
                longest_width = height
            half_length = longest_width / 2
            center = canvas_extent.center()
            min_x = center.x() - half_length
            max_x = center.x() + half_length
            min_y = center.y() - half_length
            max_y = center.y() + half_length
            square_extent = QgsRectangle(min_x, min_y, max_x, max_y)
            map.setNewExtent(square_extent)

            # calculate intervals for grid
            split_count = 5
            x_interval = square_extent.width() / split_count
            map.setGridIntervalX(x_interval)
            y_interval = square_extent.height() / split_count
            map.setGridIntervalY(y_interval)
        else:
            raise ReportCreationError(self.tr(
                'Map "impact-map" could not be found'))

        legend = self.composition.getComposerItemById('impact-legend')
        legend_attributes = self.map_legend_attributes()
        LOGGER.debug(legend_attributes)
        #legend_notes = mapLegendAttributes.get('legend_notes', None)
        #legend_units = mapLegendAttributes.get('legend_units', None)
        legend_title = legend_attributes.get('legend_title', None)
        if legend_title is None:
            legend_title = ""
        legend.setTitle(legend_title)
        legend.updateLegend()
示例#23
0
文件: map.py 项目: borysiasty/inasafe
class Map():
    """A class for creating a map."""
    def __init__(self, iface):
        """Constructor for the Map class.

        :param iface: Reference to the QGIS iface object.
        :type iface: QgsAppInterface
        """
        LOGGER.debug('InaSAFE Map class initialised')
        self.iface = iface
        self.layer = iface.activeLayer()
        self.keyword_io = KeywordIO()
        self.printer = None
        self.composition = None
        self.extent = iface.mapCanvas().extent()
        self.safe_logo = ':/plugins/inasafe/inasafe-logo-url.svg'
        self.north_arrow = ':/plugins/inasafe/simple_north_arrow.png'
        self.org_logo = ':/plugins/inasafe/supporters.png'
        self.template = ':/plugins/inasafe/inasafe-portrait-a4.qpt'
        self.disclaimer = disclaimer()
        self.page_width = 0  # width in mm
        self.page_height = 0  # height in mm
        self.page_dpi = 300.0
        self.show_frames = False  # intended for debugging use only

    @staticmethod
    def tr(string):
        """We implement this since we do not inherit QObject.

        :param string: String for translation.
        :type string: QString, str

        :returns: Translated version of theString.
        :rtype: QString
        """
        # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
        return QtCore.QCoreApplication.translate('Map', string)

    def set_impact_layer(self, layer):
        """Set the layer that will be used for stats, legend and reporting.

        :param layer: Layer that will be used for stats, legend and reporting.
        :type layer: QgsMapLayer, QgsRasterLayer, QgsVectorLayer
        """
        self.layer = layer

    def set_north_arrow_image(self, logo_path):
        """Set image that will be used as organisation logo in reports.

        :param logo_path: Path to image file
        :type logo_path: str
        """
        self.north_arrow = logo_path

    def set_organisation_logo(self, logo):
        """Set image that will be used as organisation logo in reports.

        :param logo: Path to image file
        :type logo: str
        """
        self.org_logo = logo

    def set_disclaimer(self, text):
        """Set text that will be used as disclaimer in reports.

        :param text: Disclaimer text
        :type text: str
        """
        self.disclaimer = text

    def set_template(self, template):
        """Set template that will be used for report generation.

        :param template: Path to composer template
        :type template: str
        """
        self.template = template

    def set_extent(self, extent):
        """Set extent or the report map

        :param extent: Extent of the report map
        :type extent: QgsRectangle

        """
        self.extent = extent

    def setup_composition(self):
        """Set up the composition ready for drawing elements onto it."""
        LOGGER.debug('InaSAFE Map setupComposition called')
        canvas = self.iface.mapCanvas()
        renderer = canvas.mapRenderer()
        self.composition = QgsComposition(renderer)
        self.composition.setPlotStyle(QgsComposition.Preview)  # or preview
        self.composition.setPrintResolution(self.page_dpi)
        self.composition.setPrintAsRaster(True)

    def make_pdf(self, filename):
        """Generate the printout for our final map.

        :param filename: Path on the file system to which the pdf should be
            saved. If None, a generated file name will be used.
        :type filename: str

        :returns: File name of the output file (equivalent to filename if
                provided).
        :rtype: str
        """
        LOGGER.debug('InaSAFE Map printToPdf called')
        if filename is None:
            map_pdf_path = unique_filename(
                prefix='report', suffix='.pdf', dir=temp_dir())
        else:
            # We need to cast to python string in case we receive a QString
            map_pdf_path = str(filename)

        self.load_template()
        self.composition.exportAsPDF(map_pdf_path)
        return map_pdf_path

    def map_title(self):
        """Get the map title from the layer keywords if possible.

        :returns: None on error, otherwise the title.
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapTitle called')
        try:
            title = self.keyword_io.read_keywords(self.layer, 'map_title')
            return title
        except KeywordNotFoundError:
            return None
        except Exception:
            return None

    def map_legend_attributes(self):
        """Get the map legend attribute from the layer keywords if possible.

        :returns: None on error, otherwise the attributes (notes and units).
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapLegendAttributes called')
        legend_attribute_list = [
            'legend_notes',
            'legend_units',
            'legend_title']
        legend_attribute_dict = {}
        for myLegendAttribute in legend_attribute_list:
            # noinspection PyBroadException
            try:
                legend_attribute_dict[myLegendAttribute] = \
                    self.keyword_io.read_keywords(
                        self.layer, myLegendAttribute)
            except KeywordNotFoundError:
                pass
            except Exception:
                pass
        return legend_attribute_dict

    def load_template(self):
        """Load a QgsComposer map from a template.
        """
        self.setup_composition()

        template_file = QtCore.QFile(self.template)
        template_file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()

        document = QtXml.QDomDocument()
        document.setContent(template_content)

        # get information for substitutions
        # date, time and plugin version
        date_time = self.keyword_io.read_keywords(self.layer, 'time_stamp')
        if date_time is None:
            date = ''
            time = ''
        else:
            tokens = date_time.split('_')
            date = tokens[0]
            time = tokens[1]
        long_version = get_version()
        tokens = long_version.split('.')
        version = '%s.%s.%s' % (tokens[0], tokens[1], tokens[2])

        title = self.map_title()
        if not title:
            title = ''

        substitution_map = {
            'impact-title': title,
            'date': date,
            'time': time,
            'safe-version': version,
            'disclaimer': self.disclaimer
        }
        LOGGER.debug(substitution_map)
        load_ok = self.composition.loadFromTemplate(
            document, substitution_map)
        if not load_ok:
            raise ReportCreationError(
                self.tr('Error loading template %s') %
                self.template)

        self.page_width = self.composition.paperWidth()
        self.page_height = self.composition.paperHeight()

        # set InaSAFE logo
        image = self.composition.getComposerItemById('safe-logo')
        if image is not None:
            image.setPictureFile(self.safe_logo)
        else:
            raise ReportCreationError(self.tr(
                'Image "safe-logo" could not be found'))

        # set north arrow
        image = self.composition.getComposerItemById('north-arrow')
        if image is not None:
            image.setPictureFile(self.north_arrow)
        else:
            raise ReportCreationError(self.tr(
                'Image "north arrow" could not be found'))

        # set organisation logo
        image = self.composition.getComposerItemById('organisation-logo')
        if image is not None:
            image.setPictureFile(self.org_logo)
        else:
            raise ReportCreationError(self.tr(
                'Image "organisation-logo" could not be found'))

        # set impact report table
        table = self.composition.getComposerItemById('impact-report')
        if table is not None:
            text = self.keyword_io.read_keywords(self.layer, 'impact_summary')
            if text is None:
                text = ''
            table.setText(text)
            table.setHtmlState(1)
        else:
            LOGGER.debug('"impact-report" element not found.')

        # Get the main map canvas on the composition and set
        # its extents to the event.
        composer_map = self.composition.getComposerItemById('impact-map')
        if composer_map is not None:
            # Recenter the composer map on the center of the extent
            # Note that since the composer map is square and the canvas may be
            # arbitrarily shaped, we center based on the longest edge
            canvas_extent = self.extent
            width = canvas_extent.width()
            height = canvas_extent.height()
            longest_width = width
            if width < height:
                longest_width = height
            half_length = longest_width / 2
            center = canvas_extent.center()
            min_x = center.x() - half_length
            max_x = center.x() + half_length
            min_y = center.y() - half_length
            max_y = center.y() + half_length
            square_extent = QgsRectangle(min_x, min_y, max_x, max_y)
            composer_map.setNewExtent(square_extent)

            # calculate intervals for grid
            split_count = 5
            x_interval = square_extent.width() / split_count
            composer_map.setGridIntervalX(x_interval)
            y_interval = square_extent.height() / split_count
            composer_map.setGridIntervalY(y_interval)
        else:
            raise ReportCreationError(self.tr(
                'Map "impact-map" could not be found'))

        legend = self.composition.getComposerItemById('impact-legend')
        legend_attributes = self.map_legend_attributes()
        LOGGER.debug(legend_attributes)
        #legend_notes = mapLegendAttributes.get('legend_notes', None)
        #legend_units = mapLegendAttributes.get('legend_units', None)
        legend_title = legend_attributes.get('legend_title', None)

        symbol_count = 1
        if self.layer.type() == QgsMapLayer.VectorLayer:
            renderer = self.layer.rendererV2()
            if renderer.type() in ['', '']:
                symbol_count = len(self.layer.legendSymbologyItems())
        else:
            renderer = self.layer.renderer()
            if renderer.type() in ['']:
                symbol_count = len(self.layer.legendSymbologyItems())

        if symbol_count <= 5:
            legend.setColumnCount(1)
        else:
            legend.setColumnCount(symbol_count / 5 + 1)

        if legend_title is None:
            legend_title = ""
        legend.setTitle(legend_title)
        legend.updateLegend()

        # remove from legend all layers, except impact one
        model = legend.model()
        if model.rowCount() > 0 and model.columnCount() > 0:
            impact_item = model.findItems(self.layer.name())[0]
            row = impact_item.index().row()
            model.removeRows(row + 1, model.rowCount() - row)
            if row > 0:
                model.removeRows(0, row)
示例#24
0
文件: map.py 项目: borysiasty/inasafe
class Map():
    """A class for creating a map."""
    def __init__(self, iface):
        """Constructor for the Map class.

        :param iface: Reference to the QGIS iface object.
        :type iface: QgsAppInterface
        """
        LOGGER.debug('InaSAFE Map class initialised')
        self.iface = iface
        self.layer = iface.activeLayer()
        self.keyword_io = KeywordIO()
        self.printer = None
        self.composition = None
        self.extent = iface.mapCanvas().extent()
        self.safe_logo = ':/plugins/inasafe/inasafe-logo-url.svg'
        self.north_arrow = ':/plugins/inasafe/simple_north_arrow.png'
        self.org_logo = ':/plugins/inasafe/supporters.png'
        self.template = ':/plugins/inasafe/inasafe-portrait-a4.qpt'
        self.disclaimer = disclaimer()
        self.page_width = 0  # width in mm
        self.page_height = 0  # height in mm
        self.page_dpi = 300.0
        self.show_frames = False  # intended for debugging use only

    @staticmethod
    def tr(string):
        """We implement this since we do not inherit QObject.

        :param string: String for translation.
        :type string: QString, str

        :returns: Translated version of theString.
        :rtype: QString
        """
        # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
        return QtCore.QCoreApplication.translate('Map', string)

    def set_impact_layer(self, layer):
        """Set the layer that will be used for stats, legend and reporting.

        :param layer: Layer that will be used for stats, legend and reporting.
        :type layer: QgsMapLayer, QgsRasterLayer, QgsVectorLayer
        """
        self.layer = layer

    def set_north_arrow_image(self, logo_path):
        """Set image that will be used as organisation logo in reports.

        :param logo_path: Path to image file
        :type logo_path: str
        """
        self.north_arrow = logo_path

    def set_organisation_logo(self, logo):
        """Set image that will be used as organisation logo in reports.

        :param logo: Path to image file
        :type logo: str
        """
        self.org_logo = logo

    def set_disclaimer(self, text):
        """Set text that will be used as disclaimer in reports.

        :param text: Disclaimer text
        :type text: str
        """
        self.disclaimer = text

    def set_template(self, template):
        """Set template that will be used for report generation.

        :param template: Path to composer template
        :type template: str
        """
        self.template = template

    def set_extent(self, extent):
        """Set extent or the report map

        :param extent: Extent of the report map
        :type extent: QgsRectangle

        """
        self.extent = extent

    def setup_composition(self):
        """Set up the composition ready for drawing elements onto it."""
        LOGGER.debug('InaSAFE Map setupComposition called')
        canvas = self.iface.mapCanvas()
        renderer = canvas.mapRenderer()
        self.composition = QgsComposition(renderer)
        self.composition.setPlotStyle(QgsComposition.Preview)  # or preview
        self.composition.setPrintResolution(self.page_dpi)
        self.composition.setPrintAsRaster(True)

    def make_pdf(self, filename):
        """Generate the printout for our final map.

        :param filename: Path on the file system to which the pdf should be
            saved. If None, a generated file name will be used.
        :type filename: str

        :returns: File name of the output file (equivalent to filename if
                provided).
        :rtype: str
        """
        LOGGER.debug('InaSAFE Map printToPdf called')
        if filename is None:
            map_pdf_path = unique_filename(prefix='report',
                                           suffix='.pdf',
                                           dir=temp_dir())
        else:
            # We need to cast to python string in case we receive a QString
            map_pdf_path = str(filename)

        self.load_template()
        self.composition.exportAsPDF(map_pdf_path)
        return map_pdf_path

    def map_title(self):
        """Get the map title from the layer keywords if possible.

        :returns: None on error, otherwise the title.
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapTitle called')
        try:
            title = self.keyword_io.read_keywords(self.layer, 'map_title')
            return title
        except KeywordNotFoundError:
            return None
        except Exception:
            return None

    def map_legend_attributes(self):
        """Get the map legend attribute from the layer keywords if possible.

        :returns: None on error, otherwise the attributes (notes and units).
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapLegendAttributes called')
        legend_attribute_list = [
            'legend_notes', 'legend_units', 'legend_title'
        ]
        legend_attribute_dict = {}
        for myLegendAttribute in legend_attribute_list:
            # noinspection PyBroadException
            try:
                legend_attribute_dict[myLegendAttribute] = \
                    self.keyword_io.read_keywords(
                        self.layer, myLegendAttribute)
            except KeywordNotFoundError:
                pass
            except Exception:
                pass
        return legend_attribute_dict

    def load_template(self):
        """Load a QgsComposer map from a template.
        """
        self.setup_composition()

        template_file = QtCore.QFile(self.template)
        template_file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()

        document = QtXml.QDomDocument()
        document.setContent(template_content)

        # get information for substitutions
        # date, time and plugin version
        date_time = self.keyword_io.read_keywords(self.layer, 'time_stamp')
        if date_time is None:
            date = ''
            time = ''
        else:
            tokens = date_time.split('_')
            date = tokens[0]
            time = tokens[1]
        long_version = get_version()
        tokens = long_version.split('.')
        version = '%s.%s.%s' % (tokens[0], tokens[1], tokens[2])

        title = self.map_title()
        if not title:
            title = ''

        substitution_map = {
            'impact-title': title,
            'date': date,
            'time': time,
            'safe-version': version,
            'disclaimer': self.disclaimer
        }
        LOGGER.debug(substitution_map)
        load_ok = self.composition.loadFromTemplate(document, substitution_map)
        if not load_ok:
            raise ReportCreationError(
                self.tr('Error loading template %s') % self.template)

        self.page_width = self.composition.paperWidth()
        self.page_height = self.composition.paperHeight()

        # set InaSAFE logo
        image = self.composition.getComposerItemById('safe-logo')
        if image is not None:
            image.setPictureFile(self.safe_logo)
        else:
            raise ReportCreationError(
                self.tr('Image "safe-logo" could not be found'))

        # set north arrow
        image = self.composition.getComposerItemById('north-arrow')
        if image is not None:
            image.setPictureFile(self.north_arrow)
        else:
            raise ReportCreationError(
                self.tr('Image "north arrow" could not be found'))

        # set organisation logo
        image = self.composition.getComposerItemById('organisation-logo')
        if image is not None:
            image.setPictureFile(self.org_logo)
        else:
            raise ReportCreationError(
                self.tr('Image "organisation-logo" could not be found'))

        # set impact report table
        table = self.composition.getComposerItemById('impact-report')
        if table is not None:
            text = self.keyword_io.read_keywords(self.layer, 'impact_summary')
            if text is None:
                text = ''
            table.setText(text)
            table.setHtmlState(1)
        else:
            LOGGER.debug('"impact-report" element not found.')

        # Get the main map canvas on the composition and set
        # its extents to the event.
        composer_map = self.composition.getComposerItemById('impact-map')
        if composer_map is not None:
            # Recenter the composer map on the center of the extent
            # Note that since the composer map is square and the canvas may be
            # arbitrarily shaped, we center based on the longest edge
            canvas_extent = self.extent
            width = canvas_extent.width()
            height = canvas_extent.height()
            longest_width = width
            if width < height:
                longest_width = height
            half_length = longest_width / 2
            center = canvas_extent.center()
            min_x = center.x() - half_length
            max_x = center.x() + half_length
            min_y = center.y() - half_length
            max_y = center.y() + half_length
            square_extent = QgsRectangle(min_x, min_y, max_x, max_y)
            composer_map.setNewExtent(square_extent)

            # calculate intervals for grid
            split_count = 5
            x_interval = square_extent.width() / split_count
            composer_map.setGridIntervalX(x_interval)
            y_interval = square_extent.height() / split_count
            composer_map.setGridIntervalY(y_interval)
        else:
            raise ReportCreationError(
                self.tr('Map "impact-map" could not be found'))

        legend = self.composition.getComposerItemById('impact-legend')
        legend_attributes = self.map_legend_attributes()
        LOGGER.debug(legend_attributes)
        #legend_notes = mapLegendAttributes.get('legend_notes', None)
        #legend_units = mapLegendAttributes.get('legend_units', None)
        legend_title = legend_attributes.get('legend_title', None)

        symbol_count = 1
        if self.layer.type() == QgsMapLayer.VectorLayer:
            renderer = self.layer.rendererV2()
            if renderer.type() in ['', '']:
                symbol_count = len(self.layer.legendSymbologyItems())
        else:
            renderer = self.layer.renderer()
            if renderer.type() in ['']:
                symbol_count = len(self.layer.legendSymbologyItems())

        if symbol_count <= 5:
            legend.setColumnCount(1)
        else:
            legend.setColumnCount(symbol_count / 5 + 1)

        if legend_title is None:
            legend_title = ""
        legend.setTitle(legend_title)
        legend.updateLegend()

        # remove from legend all layers, except impact one
        model = legend.model()
        if model.rowCount() > 0 and model.columnCount() > 0:
            impact_item = model.findItems(self.layer.name())[0]
            row = impact_item.index().row()
            model.removeRows(row + 1, model.rowCount() - row)
            if row > 0:
                model.removeRows(0, row)
示例#25
0
    def exportToPdf(self, print_context, targetFile=None):
        self.iface.mapCanvas().setDestinationCrs(
            self.settings_instance.projection.crs())
        myComposition = QgsComposition(self.iface.mapCanvas().mapSettings())
        template = self.env.get_template(print_context['template'])

        custom_qpt = template.render(CONTEXT=print_context)
        myDocument = QDomDocument()
        myDocument.setContent(custom_qpt)
        myComposition.loadFromTemplate(myDocument)

        suggestedFile = os.path.join(
            self.settings_instance.projectFolderPath.text(),
            print_context['title'] + ".pdf")
        if not targetFile:
            targetFile = QFileDialog.getSaveFileName(
                None, "Export " + print_context['job'], suggestedFile, "*.pdf")
            interactive = True
            if not targetFile:
                return
        else:
            interactive = None

        outputDir = tempfile.gettempdir()

        with open(os.path.join(targetFile + '.qpt'), "wb") as qpt_file:
            qpt_file.write(custom_qpt)

        if print_context['type'] == 'report':
            myComposition.exportAsPDF(targetFile)

        elif print_context['type'] == 'map':
            print myComposition.getComposerMapById(0)
            print myComposition.getComposerItemById('5')

            for composer_map in myComposition.composerMapItems():
                print composer_map, composer_map.id()

            fdtm_extent = None
            for layer in [
                    self.settings_instance.EPpLayer,
                    self.settings_instance.EApLayer,
                    self.settings_instance.WRLayer,
                    self.settings_instance.WDSLayer
            ]:
                if layer.featureCount() > 0:
                    if fdtm_extent:
                        fdtm_extent.combineExtentWith(layer.extent())
                    else:
                        fdtm_extent = layer.extent()
            fdtm_extent.scale(1.1)
            composer_map.zoomToExtent(fdtm_extent)
            composer_map.updateItem()
            myComposition.refreshItems()

            myComposition.exportAsPDF(targetFile)

        elif print_context['type'] == 'mapppp':
            print print_context
            myComposition = QgsComposition(
                self.iface.mapCanvas().mapSettings())
            myComposition.setPlotStyle(QgsComposition.Print)
            myComposition.setPaperSize(297, 210)
            composer_map = QgsComposerMap(myComposition, 10, 10, 190, 190)
            fdtm_extent = self.settings_instance.EPpLayer.extent()
            for layer in [
                    self.settings_instance.EApLayer,
                    self.settings_instance.WRLayer,
                    self.settings_instance.WDSLayer
            ]:
                fdtm_extent.combineExtentWith(layer.extent())
            composer_map.zoomToExtent(fdtm_extent)
            composer_map.updateItem()
            myComposition.addItem(composer_map)

            table = QgsComposerAttributeTable(myComposition)
            table.setItemPosition(205, 170)
            table.setVectorLayer(QgsMapLayerRegistry.instance().mapLayer(
                print_context['id']))
            table.setMaximumNumberOfFeatures(20)
            table.setFilterFeatures(True)
            col1 = QgsComposerTableColumn()
            col1.setAttribute('types')
            col1.setHeading("types")
            col2 = QgsComposerTableColumn()
            col2.setAttribute('project units')
            col2.setHeading("project units")
            col3 = QgsComposerTableColumn()
            col3.setAttribute('Areas')
            col3.setHeading("Areas")
            col4 = QgsComposerTableColumn()
            col4.setAttribute('Lengths')
            col4.setHeading("Lengths")
            col5 = QgsComposerTableColumn()
            col5.setAttribute('Cost')
            col5.setHeading("Cost")
            table.setColumns([col1, col2, col3, col4, col5])
            myComposition.addItem(table)

            myComposition.exportAsPDF(targetFile)

        elif print_context['type'] == 'atlas':

            myComposition.setAtlasMode(QgsComposition.ExportAtlas)
            for composer_map in myComposition.composerMapItems():
                print composer_map, composer_map.id()

            atlas = myComposition.atlasComposition()
            atlas.setComposerMap(composer_map)  #DEPRECATED
            atlas.setPredefinedScales(self.PREDEFINED_SCALES)
            composer_map.setAtlasDriven(True)
            composer_map.setAtlasScalingMode(QgsComposerMap.Predefined)

            atlas.beginRender()
            rendered_pdf = []
            progress = progressBar(self, "exporting " + print_context['job'],
                                   atlas.numFeatures())
            for i in range(0, atlas.numFeatures()):
                atlas.prepareForFeature(i)
                current_filename = atlas.currentFilename()
                file_name = '_'.join(current_filename.split())
                file_path = '%s.pdf' % file_name
                path = os.path.join(outputDir, file_path)
                myComposition.exportAsPDF(path)
                rendered_pdf.append(path)
                progress.setStep(i)
            progress.stop(print_context['job'] + "exported to " + targetFile)
            atlas.endRender()

            merge_pdfs(rendered_pdf, targetFile)

        if interactive:
            open_file(targetFile)

        return targetFile
示例#26
0
    def generate_report(self):
        # Generate pdf report from impact/hazard
        if not self.impact_exists:
            # Cannot generate report when no impact layer present
            return

        project_path = os.path.join(
            self.report_path, 'project-%s.qgs' % self.locale)
        project_instance = QgsProject.instance()
        project_instance.setFileName(project_path)
        project_instance.read()

        # Set up the map renderer that will be assigned to the composition
        map_renderer = CANVAS.mapRenderer()
        # Set the labelling engine for the canvas
        labelling_engine = QgsPalLabeling()
        map_renderer.setLabelingEngine(labelling_engine)

        # Enable on the fly CRS transformations
        map_renderer.setProjectionsEnabled(True)

        default_crs = map_renderer.destinationCrs()
        crs = QgsCoordinateReferenceSystem('EPSG:4326')
        map_renderer.setDestinationCrs(crs)

        # get layer registry
        layer_registry = QgsMapLayerRegistry.instance()
        layer_registry.removeAllMapLayers()
        # add impact layer
        population_affected_layer = read_qgis_layer(
            self.population_aggregate_path, self.tr('People Affected'))
        layer_registry.addMapLayer(population_affected_layer, True)
        # add boundary mask
        boundary_mask = read_qgis_layer(
            self.flood_fixtures_dir('boundary-mask.shp'))
        layer_registry.addMapLayer(boundary_mask, False)
        # add hazard layer
        hazard_layer = read_qgis_layer(
            self.hazard_path, self.tr('Flood Depth (cm)'))
        layer_registry.addMapLayer(hazard_layer, True)
        # add boundary layer
        boundary_layer = read_qgis_layer(
            self.flood_fixtures_dir('boundary-5.shp'))
        layer_registry.addMapLayer(boundary_layer, False)
        CANVAS.setExtent(boundary_layer.extent())
        CANVAS.refresh()
        # add basemap layer
        # this code uses OpenlayersPlugin
        base_map = QgsRasterLayer(
            self.flood_fixtures_dir('jakarta.jpg'))
        layer_registry.addMapLayer(base_map, False)
        CANVAS.refresh()

        template_path = self.flood_fixtures_dir('realtime-flood.qpt')

        with open(template_path) as f:
            template_content = f.read()

        document = QDomDocument()
        document.setContent(template_content)

        # set destination CRS to Jakarta CRS
        # EPSG:32748
        # This allows us to use the scalebar in meter unit scale
        crs = QgsCoordinateReferenceSystem('EPSG:32748')
        map_renderer.setDestinationCrs(crs)

        # Now set up the composition
        composition = QgsComposition(map_renderer)

        subtitution_map = self.event_dict()
        LOGGER.debug(subtitution_map)

        # load composition object from template
        result = composition.loadFromTemplate(document, subtitution_map)
        if not result:
            LOGGER.exception(
                'Error loading template %s with keywords\n %s',
                template_path, subtitution_map)
            raise MapComposerError

        # get main map canvas on the composition and set extent
        map_canvas = composition.getComposerItemById('map-canvas')
        if map_canvas:
            map_canvas.setNewExtent(map_canvas.currentMapExtent())
            map_canvas.renderModeUpdateCachedImage()
        else:
            LOGGER.exception('Map canvas could not be found in template %s',
                             template_path)
            raise MapComposerError

        # get map legend on the composition
        map_legend = composition.getComposerItemById('map-legend')
        if map_legend:
            # show only legend for Flood Depth
            # ''.star
            # showed_legend = [layer_id for layer_id in map_renderer.layerSet()
            #                  if layer_id.startswith('Flood_Depth')]
            # LOGGER.info(showed_legend)
            # LOGGER.info(map_renderer.layerSet())
            # LOGGER.info(hazard_layer.id())

            # map_legend.model().setLayerSet(showed_legend)
            # map_legend.modelV2().clear()
            # print dir(map_legend.modelV2())
            # map_legend.setLegendFilterByMapEnabled(True)

            map_legend.model().setLayerSet(
                [hazard_layer.id(), population_affected_layer.id()])

        else:
            LOGGER.exception('Map legend could not be found in template %s',
                             template_path)
            raise MapComposerError

        content_analysis = composition.getComposerItemById(
            'content-analysis-result')
        if not content_analysis:
            message = 'Content analysis composer item could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        content_analysis_html = content_analysis.multiFrame()
        if content_analysis_html:
            # set url to generated html
            analysis_html_path = self.generate_analysis_result_html()
            # We're using manual HTML to avoid memory leak and segfault
            # happened when using Url Mode
            content_analysis_html.setContentMode(QgsComposerHtml.ManualHtml)
            with open(analysis_html_path) as f:
                content_analysis_html.setHtml(f.read())
                content_analysis_html.loadHtml()
        else:
            message = 'Content analysis HTML not found in template'
            LOGGER.exception(message)
            raise MapComposerError(message)

        # save a pdf
        composition.exportAsPDF(self.map_report_path)

        project_instance.write(QFileInfo(project_path))

        layer_registry.removeAllMapLayers()
        map_renderer.setDestinationCrs(default_crs)
        map_renderer.setProjectionsEnabled(False)
示例#27
0
    def generate_report(self):
        # Generate pdf report from impact/hazard
        LOGGER.info("Generating report")
        if not self.impact_exists:
            # Cannot generate report when no impact layer present
            LOGGER.info("Cannot Generate report when no impact present.")
            return

        project_instance = QgsProject.instance()
        project_instance.setFileName(self.project_path)
        project_instance.read()

        # get layer registry
        layer_registry = QgsMapLayerRegistry.instance()
        layer_registry.removeAllMapLayers()

        # Set up the map renderer that will be assigned to the composition
        map_renderer = CANVAS.mapRenderer()

        # Enable on the fly CRS transformations
        map_renderer.setProjectionsEnabled(True)

        default_crs = map_renderer.destinationCrs()
        crs = QgsCoordinateReferenceSystem("EPSG:4326")
        map_renderer.setDestinationCrs(crs)

        # add place name layer
        layer_registry.addMapLayer(self.cities_layer, False)

        # add airport layer
        layer_registry.addMapLayer(self.airport_layer, False)

        # add volcano layer
        layer_registry.addMapLayer(self.volcano_layer, False)

        # add impact layer
        hazard_layer = read_qgis_layer(self.hazard_path, self.tr("People Affected"))
        layer_registry.addMapLayer(hazard_layer, False)

        # add basemap layer
        layer_registry.addMapLayer(self.highlight_base_layer, False)

        # add basemap layer
        layer_registry.addMapLayer(self.overview_layer, False)

        CANVAS.setExtent(hazard_layer.extent())
        CANVAS.refresh()

        template_path = self.ash_fixtures_dir("realtime-ash.qpt")

        with open(template_path) as f:
            template_content = f.read()

        document = QDomDocument()
        document.setContent(template_content)

        # Now set up the composition
        # map_settings = QgsMapSettings()
        # composition = QgsComposition(map_settings)
        composition = QgsComposition(map_renderer)

        subtitution_map = self.event_dict()
        LOGGER.debug(subtitution_map)

        # load composition object from template
        result = composition.loadFromTemplate(document, subtitution_map)
        if not result:
            LOGGER.exception("Error loading template %s with keywords\n %s", template_path, subtitution_map)
            raise MapComposerError

        # get main map canvas on the composition and set extent
        map_impact = composition.getComposerItemById("map-impact")
        if map_impact:
            map_impact.zoomToExtent(hazard_layer.extent())
            map_impact.renderModeUpdateCachedImage()
        else:
            LOGGER.exception("Map canvas could not be found in template %s", template_path)
            raise MapComposerError

        # get overview map canvas on the composition and set extent
        map_overall = composition.getComposerItemById("map-overall")
        if map_overall:
            map_overall.setLayerSet([self.overview_layer.id()])
            # this is indonesia extent
            indonesia_extent = QgsRectangle(
                94.0927980005593554, -15.6629591962689343, 142.0261493318861312, 10.7379406374101816
            )
            map_overall.zoomToExtent(indonesia_extent)
            map_overall.renderModeUpdateCachedImage()
        else:
            LOGGER.exception("Map canvas could not be found in template %s", template_path)
            raise MapComposerError

        # setup impact table
        self.render_population_table()
        self.render_nearby_table()
        self.render_landcover_table()

        impact_table = composition.getComposerItemById("table-impact")
        if impact_table is None:
            message = "table-impact composer item could not be found"
            LOGGER.exception(message)
            raise MapComposerError(message)
        impacts_html = composition.getComposerHtmlByItem(impact_table)
        if impacts_html is None:
            message = "Impacts QgsComposerHtml could not be found"
            LOGGER.exception(message)
            raise MapComposerError(message)
        impacts_html.setUrl(QUrl(self.population_html_path))

        # setup nearby table
        nearby_table = composition.getComposerItemById("table-nearby")
        if nearby_table is None:
            message = "table-nearby composer item could not be found"
            LOGGER.exception(message)
            raise MapComposerError(message)
        nearby_html = composition.getComposerHtmlByItem(nearby_table)
        if nearby_html is None:
            message = "Nearby QgsComposerHtml could not be found"
            LOGGER.exception(message)
            raise MapComposerError(message)
        nearby_html.setUrl(QUrl(self.nearby_html_path))

        # setup landcover table
        landcover_table = composition.getComposerItemById("table-landcover")
        if landcover_table is None:
            message = "table-landcover composer item could not be found"
            LOGGER.exception(message)
            raise MapComposerError(message)
        landcover_html = composition.getComposerHtmlByItem(landcover_table)
        if landcover_html is None:
            message = "Landcover QgsComposerHtml could not be found"
            LOGGER.exception(message)
            raise MapComposerError(message)
        landcover_html.setUrl(QUrl(self.landcover_html_path))

        # setup logos
        logos_id = ["logo-bnpb", "logo-geologi"]
        for logo_id in logos_id:
            logo_picture = composition.getComposerItemById(logo_id)
            if logo_picture is None:
                message = "%s composer item could not be found" % logo_id
                LOGGER.exception(message)
                raise MapComposerError(message)
            pic_path = os.path.basename(logo_picture.picturePath())
            pic_path = os.path.join("logo", pic_path)
            logo_picture.setPicturePath(self.ash_fixtures_dir(pic_path))

        # save a pdf
        composition.exportAsPDF(self.map_report_path)

        project_instance.write(QFileInfo(self.project_path))

        layer_registry.removeAllMapLayers()
        map_renderer.setDestinationCrs(default_crs)
        map_renderer.setProjectionsEnabled(False)
        LOGGER.info("Report generation completed.")
    def print_atlas(self,
                    project_path,
                    composer_name,
                    predefined_scales,
                    feature_filter=None,
                    page_name_expression=None):

        # Get composer from project
        # in QGIS 2, canno get composers without iface
        # so we reading project xml and extract composer
        # in QGIS 3.0, we will use  project layoutManager()
        from xml.etree import ElementTree as ET
        composer_xml = None
        with open(project_path, 'r') as f:
            tree = ET.parse(f)
            for elem in tree.findall('.//Composer[@title="%s"]' %
                                     composer_name):
                composer_xml = ET.tostring(elem, encoding='utf8', method='xml')

        if not composer_xml:
            return

        document = QDomDocument()
        document.setContent(composer_xml)

        # Get canvas, map setting & instantiate composition
        canvas = QgsMapCanvas()
        QgsProject.instance().read(QFileInfo(project_path))
        bridge = QgsLayerTreeMapCanvasBridge(
            QgsProject.instance().layerTreeRoot(), canvas)
        bridge.setCanvasLayers()
        ms = canvas.mapSettings()
        composition = QgsComposition(ms)

        # Load content from XML
        substitution_map = {}
        composition.loadFromTemplate(document, substitution_map)

        # Get atlas for this composition
        atlas = composition.atlasComposition()
        atlas.setEnabled(True)
        atlas_map = composition.getComposerMapById(0)
        atlas_map.setAtlasScalingMode(QgsComposerMap.Predefined)

        # get project scales
        atlas.setPredefinedScales(predefined_scales)
        atlas.setComposerMap(atlas_map)

        #on definit le filtre
        if feature_filter:
            atlas.setFilterFeatures(True)
            atlas.setFeatureFilter(feature_filter)
        if page_name_expression:
            atlas.setPageNameExpression(page_name_expression)

        # Set atlas mode
        composition.setAtlasMode(QgsComposition.ExportAtlas)

        # Generate atlas
        atlas.beginRender()
        uid = uuid4()
        for i in range(0, atlas.numFeatures()):
            atlas.prepareForFeature(i)
            export_path = os.path.join(
                tempfile.gettempdir(),
                '%s_%s.pdf' % (atlas.nameForPage(i), uid))
            exported = composition.exportAsPDF(export_path)
            if not exported or not os.path.isfile(export_path):
                return None
            break

        atlas.endRender()

        return export_path
示例#29
0
def qgis_composer_renderer(impact_report, component):
    """Default Map Report Renderer using QGIS Composer.

    Render using qgis composer for a given impact_report data and component
    context.

    :param impact_report: ImpactReport contains data about the report that is
        going to be generated.
    :type impact_report: safe.report.impact_report.ImpactReport

    :param component: Contains the component metadata and context for
        rendering the output.
    :type component:
        safe.report.report_metadata.QgisComposerComponentsMetadata

    :return: Whatever type of output the component should be.

    .. versionadded:: 4.0
    """
    context = component.context
    """:type: safe.report.extractors.composer.QGISComposerContext"""
    qgis_composition_context = impact_report.qgis_composition_context

    # load composition object
    composition = QgsComposition(qgis_composition_context.map_settings)

    # load template
    main_template_folder = impact_report.metadata.template_folder

    # we do this condition in case custom template was found
    if component.template.startswith('../qgis-composer-templates/'):
        template_path = os.path.join(main_template_folder, component.template)
    else:
        template_path = component.template

    with open(template_path) as template_file:
        template_content = template_file.read()

    document = QtXml.QDomDocument()
    document.setContent(template_content)

    load_status = composition.loadFromTemplate(
        document, context.substitution_map)

    if not load_status:
        raise TemplateLoadingError(
            tr('Error loading template: %s') % template_path)

    # replace image path
    for img in context.image_elements:
        item_id = img.get('id')
        path = img.get('path')
        image = composition_item(composition, item_id, QgsComposerPicture)
        """:type: qgis.core.QgsComposerPicture"""
        if image and path:
            image.setPicturePath(path)

    # replace html frame
    for html_el in context.html_frame_elements:
        item_id = html_el.get('id')
        mode = html_el.get('mode')
        composer_item = composition.getComposerItemById(item_id)
        try:
            html_element = composition.getComposerHtmlByItem(composer_item)
        except:
            pass
        """:type: qgis.core.QgsComposerHtml"""
        if html_element:
            if mode == 'text':
                text = html_el.get('text')
                text = text if text else ''
                html_element.setContentMode(QgsComposerHtml.ManualHtml)
                html_element.setHtml(text)
                html_element.loadHtml()
            elif mode == 'url':
                url = html_el.get('url')
                html_element.setContentMode(QgsComposerHtml.Url)
                qurl = QUrl.fromLocalFile(url)
                html_element.setUrl(qurl)

    original_crs = impact_report.impact_function.crs
    destination_crs = qgis_composition_context.map_settings.destinationCrs()
    coord_transform = QgsCoordinateTransform(original_crs, destination_crs)

    # resize map extent
    for map_el in context.map_elements:
        item_id = map_el.get('id')
        split_count = map_el.get('grid_split_count')
        layers = [
            layer for layer in map_el.get('layers') if isinstance(
                layer, QgsMapLayer)
        ]
        map_extent_option = map_el.get('extent')
        composer_map = composition_item(composition, item_id, QgsComposerMap)

        for index, layer in enumerate(layers):
            # we need to check whether the layer is registered or not
            registered_layer = (
                QgsMapLayerRegistry.instance().mapLayer(layer.id()))
            if registered_layer:
                if not registered_layer == layer:
                    layers[index] = registered_layer
            else:
                QgsMapLayerRegistry.instance().addMapLayer(layer)

        """:type: qgis.core.QgsComposerMap"""
        if composer_map:

            # Search for specified map extent in the template.
            min_x = composer_map.extent().xMinimum() if (
                impact_report.use_template_extent) else None
            min_y = composer_map.extent().yMinimum() if (
                impact_report.use_template_extent) else None
            max_x = composer_map.extent().xMaximum() if (
                impact_report.use_template_extent) else None
            max_y = composer_map.extent().yMaximum() if (
                impact_report.use_template_extent) else None

            composer_map.setKeepLayerSet(True)
            layer_set = [l.id() for l in layers if isinstance(l, QgsMapLayer)]
            composer_map.setLayerSet(layer_set)
            map_overview_extent = None
            if map_extent_option and isinstance(
                    map_extent_option, QgsRectangle):
                # use provided map extent
                extent = coord_transform.transform(map_extent_option)
                for l in [layer for layer in layers if
                          isinstance(layer, QgsMapLayer)]:
                    layer_extent = coord_transform.transform(l.extent())
                    if l.name() == map_overview['id']:
                        map_overview_extent = layer_extent
            else:
                # if map extent not provided, try to calculate extent
                # from list of given layers. Combine it so all layers were
                # shown properly
                extent = QgsRectangle()
                extent.setMinimal()
                for l in [layer for layer in layers if
                          isinstance(layer, QgsMapLayer)]:
                    # combine extent if different layer is provided.
                    layer_extent = coord_transform.transform(l.extent())
                    extent.combineExtentWith(layer_extent)
                    if l.name() == map_overview['id']:
                        map_overview_extent = layer_extent

            width = extent.width()
            height = extent.height()
            longest_width = width if width > height else height
            half_length = longest_width / 2
            margin = half_length / 5
            center = extent.center()
            min_x = min_x or (center.x() - half_length - margin)
            max_x = max_x or (center.x() + half_length + margin)
            min_y = min_y or (center.y() - half_length - margin)
            max_y = max_y or (center.y() + half_length + margin)

            # noinspection PyCallingNonCallable
            square_extent = QgsRectangle(min_x, min_y, max_x, max_y)

            if component.key == 'population-infographic' and (
                    map_overview_extent):
                square_extent = map_overview_extent

            composer_map.zoomToExtent(square_extent)
            composer_map.renderModeUpdateCachedImage()

            actual_extent = composer_map.extent()

            # calculate intervals for grid
            x_interval = actual_extent.width() / split_count
            composer_map.grid().setIntervalX(x_interval)
            y_interval = actual_extent.height() / split_count
            composer_map.grid().setIntervalY(y_interval)

    # calculate legend element
    for leg_el in context.map_legends:
        item_id = leg_el.get('id')
        title = leg_el.get('title')
        layers = [
            layer for layer in leg_el.get('layers') if isinstance(
                layer, QgsMapLayer)
        ]
        symbol_count = leg_el.get('symbol_count')
        column_count = leg_el.get('column_count')

        legend = composition_item(composition, item_id, QgsComposerLegend)
        """:type: qgis.core.QgsComposerLegend"""
        if legend:
            # set column count
            if column_count:
                legend.setColumnCount(column_count)
            elif symbol_count <= 7:
                legend.setColumnCount(1)
            else:
                legend.setColumnCount(symbol_count / 7 + 1)

            # set legend title
            if title is not None and not impact_report.legend_layers:
                legend.setTitle(title)

            # set legend
            root_group = legend.modelV2().rootGroup()
            for layer in layers:
                # we need to check whether the layer is registered or not
                registered_layer = (
                    QgsMapLayerRegistry.instance().mapLayer(layer.id()))
                if registered_layer:
                    if not registered_layer == layer:
                        layer = registered_layer
                else:
                    QgsMapLayerRegistry.instance().addMapLayer(layer)
                # used for customizations
                tree_layer = root_group.addLayer(layer)
                if impact_report.legend_layers or (
                        not impact_report.multi_exposure_impact_function):
                    QgsLegendRenderer.setNodeLegendStyle(
                        tree_layer, QgsComposerLegendStyle.Hidden)
            legend.synchronizeWithModel()

    # process to output

    # in case output folder not specified
    if impact_report.output_folder is None:
        impact_report.output_folder = mkdtemp(dir=temp_dir())

    output_format = component.output_format
    component_output_path = impact_report.component_absolute_output_path(
        component.key)
    component_output = None

    doc_format = QgisComposerComponentsMetadata.OutputFormat.DOC_OUTPUT
    template_format = QgisComposerComponentsMetadata.OutputFormat.QPT
    if isinstance(output_format, list):
        component_output = []
        for i in range(len(output_format)):
            each_format = output_format[i]
            each_path = component_output_path[i]

            if each_format in doc_format:
                result_path = create_qgis_pdf_output(
                    impact_report,
                    each_path,
                    composition,
                    each_format,
                    component)
                component_output.append(result_path)
            elif each_format == template_format:
                result_path = create_qgis_template_output(
                    each_path, composition)
                component_output.append(result_path)
    elif isinstance(output_format, dict):
        component_output = {}
        for key, each_format in output_format.iteritems():
            each_path = component_output_path[key]

            if each_format in doc_format:
                result_path = create_qgis_pdf_output(
                    impact_report,
                    each_path,
                    composition,
                    each_format,
                    component)
                component_output[key] = result_path
            elif each_format == template_format:
                result_path = create_qgis_template_output(
                    each_path, composition)
                component_output[key] = result_path
    elif (output_format in
            QgisComposerComponentsMetadata.OutputFormat.SUPPORTED_OUTPUT):
        component_output = None

        if output_format in doc_format:
            result_path = create_qgis_pdf_output(
                impact_report,
                component_output_path,
                composition,
                output_format,
                component)
            component_output = result_path
        elif output_format == template_format:
            result_path = create_qgis_template_output(
                component_output_path, composition)
            component_output = result_path

    component.output = component_output

    return component.output
示例#30
0
 def run(self,*args,**kwargs):
     """
     :param templatePath: The file path to the user-defined template.
     :param entityFieldName: The name of the column for the specified entity which
     must exist in the data source view or table.
     :param entityFieldValue: The value for filtering the records in the data source
     view or table.
     :param outputMode: Whether the output composition should be an image or PDF.
     :param filePath: The output file where the composition will be written to. Applies
     to single mode output generation.
     :param dataFields: List containing the field names whose values will be used to name the files.
     This is used in multiple mode configuration.
     :param fileExtension: The output file format. Used in multiple mode configuration.
     :param dbmodel: In order to name the files using the custom column mapping, a callable
     sqlalchemy data model must be specified.
     """
     #Unpack arguments
     templatePath = args[0]
     entityFieldName = args[1]
     entityFieldValue = args[2]
     outputMode = args[3]
     filePath = kwargs.get("filePath",None)
     dataFields = kwargs.get("dataFields",[])
     fileExtension = kwargs.get("fileExtension","")
     dataModel = kwargs.get("dbmodel",None)
     
     templateFile = QFile(templatePath)
     
     if not templateFile.open(QIODevice.ReadOnly):
         return (False,QApplication.translate("DocumentGenerator","Cannot read template file."))   
      
     templateDoc = QDomDocument()
     
     if templateDoc.setContent(templateFile):
         composerDS = ComposerDataSource.create(templateDoc)
         spatialFieldsConfig = SpatialFieldsConfiguration.create(templateDoc)
         composerDS.setSpatialFieldsConfig(spatialFieldsConfig)
         
         #Execute query
         dsTable,records = self._execQuery(composerDS.name(), entityFieldName, entityFieldValue)
         
         if records == None:
             return (False,QApplication.translate("DocumentGenerator","No matching records in the database"))
         
         """
         Iterate through records where a single file output will be generated for each matching record.
         """
         for rec in records:
             composition = QgsComposition(self._mapRenderer)
             composition.loadFromTemplate(templateDoc)
             
             #Set value of composer items based on the corresponding db values
             for composerId in composerDS.dataFieldMappings().reverse:
                 #Use composer item id since the uuid is stripped off
                 composerItem = composition.getComposerItemById(composerId)
                 
                 if composerItem != None:
                     fieldName = composerDS.dataFieldName(composerId)
                     fieldValue = getattr(rec,fieldName)
                     self._composerItemValueHandler(composerItem, fieldValue)
                         
             #Create memory layers for spatial features and add them to the map
             for mapId,spfmList in spatialFieldsConfig.spatialFieldsMapping().iteritems():
                 mapItem = composition.getComposerItemById(mapId)
                 
                 if mapItem!= None:
                     #Clear any previous memory layer
                     self.clearTemporaryLayers()
                     
                     for spfm in spfmList:
                         #Use the value of the label field to name the layer
                         layerName = getattr(rec,spfm.labelField())
                         
                         #Extract the geometry using geoalchemy spatial capabilities
                         geomFunc = getattr(rec,spfm.spatialField()).ST_AsText()
                         geomWKT = self._dbSession.scalar(geomFunc)
                         
                         #Create reference layer with feature
                         refLayer = self._buildVectorLayer(layerName)
                         
                         #Add feature
                         bbox = self._addFeatureToLayer(refLayer, geomWKT)
                         bbox.scale(spfm.zoomLevel())
                         
                         #Add layer to map
                         QgsMapLayerRegistry.instance().addMapLayer(refLayer)
                         self._iface.mapCanvas().setExtent(bbox)
                         self._iface.mapCanvas().refresh()
                         
                         #mapItem.storeCurrentLayerSet()
                         #mapItem.updateCachedImage()
                         
                         #Add layer to memory layer list
                         self._memoryLayers.append(refLayer)
                         
                     mapItem.setNewExtent(self._mapRenderer.extent())
                         
             #Build output path and generate composition
             if filePath != None and len(dataFields) == 0:
                 self._writeOutput(composition,outputMode,filePath) 
                 
             elif filePath == None and len(dataFields) > 0:
                 docFileName = self._buildFileName(dataModel,entityFieldName,entityFieldValue,dataFields,fileExtension)
                 if docFileName == "":
                     return (False,QApplication.translate("DocumentGenerator",
                                                          "File name could not be generated from the data fields."))
                     
                 outputDir = self._composerOutputPath()
                 if outputDir == None:
                     return (False,QApplication.translate("DocumentGenerator",
                                                          "System could not read the location of the output directory in the registry."))
                 
                 qDir = QDir()
                 if not qDir.exists(outputDir):
                     return (False,QApplication.translate("DocumentGenerator",
                                                          "Output directory does not exist"))
                 
                 absDocPath = unicode(outputDir) + "/" + docFileName
                 self._writeOutput(composition,outputMode,absDocPath)
         
         #Clear temporary layers
         self.clearTemporaryLayers()        
         
         return (True,"Success")
     
     return (False,"Composition could not be generated")
示例#31
0
app = QgsApplication(sys.argv, True)
QgsApplication.setPrefixPath("/usr", True)
QgsApplication.initQgis()

project_path = 'report_maps.qgs'
template_path = 'owner_occupancy.qpt'

canvas = QgsMapCanvas()
canvas.resize(QSize(1450, 850))
#start = time.time()
QgsProject.instance().read(QFileInfo(project_path))
#end = time.time()
root = QgsProject.instance().layerTreeRoot()
bridge = QgsLayerTreeMapCanvasBridge(root, canvas)
bridge.setCanvasLayers()
registry = QgsMapLayerRegistry.instance()

template_file = file(template_path)
template_content = template_file.read()
template_file.close()
document = QDomDocument()
document.setContent(template_content)
map_settings = canvas.mapSettings()
composition = QgsComposition(map_settings)
#start = time.time()
composition.loadFromTemplate(document)
#end = time.time()

#create list of all layers currently in the map
map_layers = [lyr for lyr in registry.mapLayers() if root.findLayer(lyr)]
class VRPPrintComposer:
    """Atlas Generation"""
    def __init__(
                 self,
                 iface,
                 gemname,
                 coveragelayer,
                 json_settings,
                 gnr_nbrs,
                 featurefilter,
                 orthoimage,
                 themen,
                 templateqpt,
                 pdfmap
                 ):
        self.iface = iface
        self.legiface = self.iface.legendInterface()
        self.toc = self.iface.mainWindow().findChild(QTreeWidget, 'theMapLegend')
        self.canvas = self.iface.mapCanvas()
        self.map_renderer = self.canvas.mapRenderer()
        self.gem_name = gemname
        self.coverage_layer = coveragelayer
        self.gnrs = gnr_nbrs
        self.settings = json_settings
        self.feature_filter = featurefilter
        self.ortho = orthoimage
        self.ortho_lyr = None
        self.themen = themen
        self.composition = None
        self.composermap = None
        self.comp_textinfo = None
        self.template_qpt = templateqpt
        self.pdf_map = pdfmap
        dkmgem = self.settings.dkm_gemeinde(gemname)
        self.lyrname_ortho = self.settings.luftbild_lyrname()
        self.lyrname_dkm_gst = dkmgem['lyrnamegstk']
        self.lyrname_dkm_gnr = dkmgem['lyrnamegnr']
        self.comp_leg = []
        self.comp_lbl = []
        self.statistics = OrderedDict()

    def export_all_features_TEST(self):
        lyr = QgsVectorLayer('/home/bergw/VoGIS-Raumplanung-Daten/Geodaten/Raumplanung/Flaechenwidmung/Dornbirn/Flaechenwidmungsplan/fwp_flaeche.shp', 'flaeiw', 'ogr')
        lyr.loadNamedStyle('/home/bergw/VoGIS-Raumplanung-Daten/Geodaten/Raumplanung/Flaechenwidmung/Vorarlberg/Flaechenwidmungsplan/fwp_flaeche.qml')
        QgsMapLayerRegistry.instance().addMapLayer(lyr)

    def export_all_features(self):
        pdf_painter = None
        """Export map to pdf atlas style (one page per feature)"""
        if VRP_DEBUG is True: QgsMessageLog.logMessage(u'exporting map', DLG_CAPTION)
        try:

            result = self.__delete_pdf()
            if not result is None:
                return result

            ids = []
            exp = QgsExpression(self.feature_filter)
            if exp.hasParserError():
                raise Exception(exp.parserErrorString())
            exp.prepare(self.coverage_layer.pendingFields())
            for feature in self.coverage_layer.getFeatures():
                value = exp.evaluate(feature)
                if exp.hasEvalError():
                    raise ValueError(exp.evalErrorString())
                if bool(value):
                    if VRP_DEBUG is True: QgsMessageLog.logMessage(u'export map, feature id:{0}'.format(feature.id()), DLG_CAPTION)
                    ids.append(feature.id())
            self.coverage_layer.select(ids)
            bbox = self.coverage_layer.boundingBoxOfSelected()
            self.canvas.zoomToSelected(self.coverage_layer)
            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox:{0}'.format(bbox.toString()), DLG_CAPTION)

            #self.map_renderer.setExtent(bbox)
            #self.map_renderer.updateScale()

            #read plotlayout
            composition = QgsComposition(self.map_renderer)
            self.composition = composition
            composition.setPlotStyle(QgsComposition.Print)
            error, xml_doc = self.__read_template()
            if not error is None:
                return error
            if composition.loadFromTemplate(xml_doc) is False:
                return u'Konnte Template nicht laden!\n{0}'.format(self.template_qpt)

            #read textinfo layout
            self.comp_textinfo = QgsComposition(self.map_renderer)
            self.comp_textinfo.setPlotStyle(QgsComposition.Print)
            error, xml_doc = self.__read_template(True)
            if not error is None:
                return error
            if self.comp_textinfo.loadFromTemplate(xml_doc) is False:
                return u'Konnte Template nicht laden!\n{0}'.format(self.settings.textinfo_layout())


            new_ext = bbox
            if QGis.QGIS_VERSION_INT > 20200:
                compmaps = self.__get_items(QgsComposerMap)
                if len(compmaps) < 1:
                    return u'Kein Kartenfenster im Layout vorhanden!'
                compmap = compmaps[0]
            else:
                if len(composition.composerMapItems()) < 1:
                    return u'Kein Kartenfenster im Layout vorhanden!'
                compmap = composition.composerMapItems()[0]

            self.composermap = compmap
            #self.composermap.setPreviewMode(QgsComposerMap.Render)
            #self.composermap.setPreviewMode(QgsComposerMap.Rectangle)
            #taken from QgsComposerMap::setNewAtlasFeatureExtent (not yet available in QGIS 2.0)
            #http://www.qgis.org/api/qgscomposermap_8cpp_source.html#l00610
            old_ratio = compmap.rect().width() / compmap.rect().height()
            new_ratio = new_ext.width() / new_ext.height()
            if old_ratio < new_ratio:
                new_height = new_ext.width() / old_ratio
                delta_height = new_height - new_ext.height()
                new_ext.setYMinimum( bbox.yMinimum() - delta_height / 2)
                new_ext.setYMaximum(bbox.yMaximum() + delta_height / 2)
            else:
                new_width = old_ratio * new_ext.height()
                delta_width = new_width - new_ext.width()
                new_ext.setXMinimum(bbox.xMinimum() - delta_width / 2)
                new_ext.setXMaximum(bbox.xMaximum() + delta_width / 2)

            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox old:{0}'.format(compmap.extent().toString()), DLG_CAPTION)
            compmap.setNewExtent(new_ext)
            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox new:{0}'.format(compmap.extent().toString()), DLG_CAPTION)
            #round up to next 1000
            compmap.setNewScale(math.ceil((compmap.scale()/1000.0)) * 1000.0)
            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'bbox new (after scale):{0}'.format(compmap.extent().toString()), DLG_CAPTION)

            #add ORTHO after new extent -> performance
            if not self.ortho is None:
                self.ortho_lyr = self.__add_raster_layer(self.ortho, self.lyrname_ortho)
                self.__reorder_layers()

            self.comp_leg = self.__get_items(QgsComposerLegend)
            self.comp_lbl = self.__get_items(QgsComposerLabel)


            self.__update_composer_items(self.settings.dkm_gemeinde(self.gem_name)['lyrnamegstk'])

            if VRP_DEBUG is True:
                QgsMessageLog.logMessage(u'paperWidth:{0} paperHeight:{1}'.format(composition.paperWidth(), composition.paperHeight()), DLG_CAPTION)

            printer = QPrinter()
            printer.setOutputFormat(QPrinter.PdfFormat)
            printer.setOutputFileName(self.pdf_map)
            printer.setPaperSize(QSizeF(composition.paperWidth(), composition.paperHeight()), QPrinter.Millimeter)
            printer.setFullPage(True)
            printer.setColorMode(QPrinter.Color)
            printer.setResolution(composition.printResolution())

            pdf_painter = QPainter(printer)
            paper_rect_pixel = printer.pageRect(QPrinter.DevicePixel)
            paper_rect_mm = printer.pageRect(QPrinter.Millimeter)
            QgsPaintEngineHack.fixEngineFlags(printer.paintEngine())
            #DKM only
            if len(self.themen) < 1:
                composition.render(pdf_painter, paper_rect_pixel, paper_rect_mm)
            else:
                self.statistics = OrderedDict()
                try:
                    pass
                    #lyr = QgsVectorLayer('/home/bergw/VoGIS-Raumplanung-Daten/Geodaten/Raumplanung/Flaechenwidmung/Dornbirn/Flaechenwidmungsplan/fwp_flaeche.shp', 'flaeiw', 'ogr')
                    #lyr.loadNamedStyle('/home/bergw/VoGIS-Raumplanung-Daten/Geodaten/Raumplanung/Flaechenwidmung/Vorarlberg/Flaechenwidmungsplan/fwp_flaeche.qml')
                    #QgsMapLayerRegistry.instance().addMapLayer(lyr)
                except:
                    QgsMessageLog.logMessage('new lyr:{0}'.format(sys.exc_info()[0]), DLG_CAPTION)
                #QgsMapLayerRegistry.instance().addMapLayer(lyr)
                cntr = 0
                for thema, sub_themen in self.themen.iteritems():
                    if VRP_DEBUG is True: QgsMessageLog.logMessage('drucke Thema:{0}'.format(thema.name), DLG_CAPTION)
                    if sub_themen is None:
                        layers = self.__add_layers(thema)
                        self.__calculate_statistics(thema, thema, layers)
                        #no qml -> not visible -> means no map
                        if self.__at_least_one_visible(layers) is True:
                            if cntr > 0:
                                printer.newPage()
                            self.__reorder_layers()
                            self.__update_composer_items(thema.name, layers=layers)
                            composition.renderPage(pdf_painter, 0)
                            QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
                            cntr += 1
                        else:
                            QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
                    if not sub_themen is None:
                        for sub_thema in sub_themen:
                            if VRP_DEBUG is True: QgsMessageLog.logMessage(u'drucke SubThema:{0}'.format(sub_thema.name), DLG_CAPTION)
                            layers = self.__add_layers(sub_thema)
                            self.__calculate_statistics(thema, sub_thema, layers)
                            #no qml -> not visible -> means no map
                            if self.__at_least_one_visible(layers) is True:
                                if cntr > 0:
                                    printer.newPage()
                                self.__reorder_layers()
                                self.__update_composer_items(thema.name, subthema=sub_thema.name, layers=layers)
                                composition.renderPage(pdf_painter, 0)
                                QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
                                cntr += 1
                            else:
                                QgsMapLayerRegistry.instance().removeMapLayers([lyr.id() for lyr in layers])
            #output statistics
            if len(self.statistics) > 0:
                printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
                tabelle = self.__get_item_byid(self.comp_textinfo, 'TABELLE')
                if tabelle is None:
                    self.iface.messageBar().pushMessage(u'Layout (Textinfo): Kein Textelement mit ID "TABELLE" vorhanden.', QgsMessageBar.CRITICAL)
                else:
                    try:
                        str_flaechen = ''
                        idx = 0
                        for gnr, stats in self.statistics.iteritems():
                            comma = ', ' if idx > 0 else ''
                            str_flaechen += u'{0}{1} ({2:.2f}m²)'.format(comma, gnr, stats[0].flaeche)
                            idx += 1
                        lbls = self.__get_items(QgsComposerLabel, self.comp_textinfo)
                        self.__update_composer_items('', labels=lbls, gnrflaeche=str_flaechen)
                        html = tabelle.text()
                        html += u'<table>'
                        #gnrcnt = 0
                        for gnr, stats in self.statistics.iteritems():
                            #if gnrcnt > 0:
                            #    html += u'<tr class="abstand"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>'
                            html += u'<tr><th class="gnr"></th><th class="gnr">{0}</th><th class="gnr"></th></tr>'.format(gnr)
                            #html += u'<tr class="abstand"><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>'
                            curr_thema = ''
                            for stat in stats:
                                if stat.thema != curr_thema:
                                    html += u'<tr><th class="thema"></th><th class="thema">{0}</th><th class="thema"></th></tr>'.format(stat.thema)
                                curr_thema = stat.thema
                                for thema, subthema in stat.subthemen.iteritems():
                                    for quelle in subthema:
                                        html += u'<tr><td class="col1">{0}</td>'.format(quelle.name)
                                        attr_val = ''
                                        attr_area = ''
                                        for text, area in quelle.txt_area.iteritems():
                                            attr_val += u'{0}<br />'.format(text)
                                            attr_area += u'{0:.2f}m² <br />'.format(area)
                                        html += u'<td class="col2">{0}</td><td class="col3">{1}</td></tr>'.format(attr_val, attr_area)
                            #gnrcnt += 1
                        html += u'</table>'
                        tabelle.setText(html)
                        printer.newPage()
                        self.comp_textinfo.renderPage(pdf_painter, 0)
                    except:
                        msg = 'Statistikausgabe:\n\n{0}'.format(traceback.format_exc())
                        QgsMessageLog.logMessage(msg, DLG_CAPTION)
                        self.iface.messageBar().pushMessage(msg, QgsMessageBar.CRITICAL)
        except:
            msg = 'export pdf (catch all):\n\n{0}'.format(traceback.format_exc())
            QgsMessageLog.logMessage(msg, DLG_CAPTION)
            self.iface.messageBar().pushMessage(msg.replace(u'\n', u''), QgsMessageBar.CRITICAL)
            return msg
        finally:
            #end pdf
            if not pdf_painter is None:
                pdf_painter.end()
        return None

    def __at_least_one_visible(self, layers):
        """
        check, if at least one layer is visible
        layers are not visible, if they don't have a qml
        if there is no visible layer, there must not be an extra plot page
        """
        one_visible = False
        for lyr in layers:
            if self.legiface.isLayerVisible(lyr):
                one_visible = True
        return one_visible

    def __calculate_statistics(self, thema, subthema, layers):
        #features = processing.features(self.coverage_layer)
        features = self.coverage_layer.selectedFeatures()
        for gstk in features:
            try:
                gnr = gstk[self.settings.fld_gnr()]
                flaeche = gstk.geometry().area()
                gstk_stats = VRPStatistik(gnr, thema.name, flaeche, self.gem_name)
                #pyqtRemoveInputHook()
                #pdb.set_trace()
                #go thru all data sources of subthema
                for quelle in subthema.quellen:
                    if VRP_DEBUG is True: QgsMessageLog.logMessage(u'quelle:{0}'.format(quelle.name), DLG_CAPTION)
                    #only use those with statistik == True
                    if quelle.statistik is False:
                        continue
                    lyr_curr_quelle = None
                    for lyr in layers:
                        if quelle.name == lyr.name():
                            lyr_curr_quelle = lyr
                    if lyr_curr_quelle is None:
                        continue
                    text_flaeche = self.__get_text_flaeche(quelle, gstk, lyr_curr_quelle, quelle.attribut)
                    sub_stat = VRPStatistikSubThema(quelle.name, text_flaeche)
                    gstk_stats.add_subthema(thema.name, sub_stat)
                if gnr in self.statistics:
                    self.statistics[gnr].append(gstk_stats)
                else:
                    self.statistics[gnr] = [gstk_stats]
            except:
                msg = '__calculate_statistics:\n\n{0}'.format(traceback.format_exc())
                QgsMessageLog.logMessage(msg, DLG_CAPTION, QgsMessageLog.CRITICAL)
                msg = msg.replace('\n', '')
                self.iface.messageBar().pushMessage(msg,  QgsMessageBar.CRITICAL)
                return

    def __get_text_flaeche(self, quelle, gstk, layer, fld_name):
        text = {}
        #performance! filter by bb of gstk first
        feat_req = QgsFeatureRequest()
        feat_req.setFilterRect(gstk.geometry().boundingBox())
        for feat in layer.getFeatures(feat_req):
            if feat.geometry().intersects(gstk.geometry()):
                #no fld_name defined: means yes/no only
                if fld_name is None:
                    attr_val = u'Ja'
                else:
                    attr_val = feat[fld_name]
                    #convert everything to string
                    #JSON only allows for string keys -> settingsfile
                    if isinstance( attr_val, (int, long)):
                        attr_val = unicode(attr_val)
                    elif isinstance(attr_val, float):
                        attr_val = u'{0:.0f}'.format(attr_val)
                #replace attribute values with mapping text from settings file
                if not quelle.text is None:
                    if attr_val in quelle.text:
                        attr_val = quelle.text[attr_val]
                flaeche = feat.geometry().intersection(gstk.geometry()).area()
                if fld_name in text:
                    text[attr_val] += flaeche
                else:
                    text[attr_val] = flaeche
        if len(text) < 1 and fld_name is None:
            text[u'Nein'] = 0
        elif len(text) < 1 and not fld_name is None:
            text[u'Nein'] = 0
        return text

    def __get_thema_by_layername(self, lyrname):
        for thema in self.themen:
            if thema.name == lyrname:
                return thema
            for subthema in thema.subthemen:
                if subthema.name == lyrname:
                    return subthema
                for quelle in subthema.quellen:
                    if quelle.name == lyrname:
                        return subthema
        return None

    def __update_composer_items(self, oberthema, subthema=None, labels=None, gnrflaeche=None, layers=None):
        if labels is None:
            labels = self.comp_lbl
        for leg in self.comp_leg:
            #if not layers is None:
                #leg_model = leg.model()
#                QgsMessageLog.logMessage(u'layerSet:{0}'.format(leg.composerMap().layerSet()), DLG_CAPTION)
#                for lyr in layers:
#                    QgsMessageLog.logMessage(u'LYR(ID):{0}'.format(lyr.id()), DLG_CAPTION)
#                    leg_model.removeLayer(lyr.id())
                #QgsMessageLog.logMessage(u'maprenderer layerSet:{0}'.format(self.map_renderer.layerSet()), DLG_CAPTION)
                #QgsMessageLog.logMessage(u'legmodel:{0}'.format(dir(leg_model)), DLG_CAPTION)
                #leg_model.setLayerSet([lyr.id() for lyr in layers])
                #for lyr in layers:
                #    leg_model.addLayer(lyr)
            leg.updateLegend()
            if not layers is None:
                lyr_names = [lyr.name() for lyr in layers]
                leg_model = leg.model()
                row_count = leg_model.rowCount() - 1
                for idx in xrange(row_count, -1, -1):
                    leg_row = leg_model.item(idx)
                    if not leg_row.text() in lyr_names:
                        leg_model.removeRow(idx)
                leg.adjustBoxSize()
        for lbl in labels:
            txt = lbl[1].replace('[Gemeindename]', self.gem_name)
            txt = txt.replace('[Oberthema]', oberthema)
            if not subthema is None:
                txt = txt.replace('[Subthema]', subthema)
            txt = txt.replace('[GNR]', ', '.join(self.gnrs))
            if not gnrflaeche is None:
                txt = txt.replace('[GNRFLAECHE]', gnrflaeche)
            txt = txt.replace('[TODAY]', strftime("%d.%m.%Y"))
            txt = txt.replace('[DATE]', self.settings.dkm_stand())
            lbl[0].setText(txt)
        #self.composermap.updateItem()
        #self.composermap.updateCachedImage()
        #self.composermap.mapRenderer().updateFullExtent ()


    def __get_items(self, typ, composition=None):
        if composition is None:
            composition = self.composition
        items = []
        for item in composition.items():
            if isinstance(item, typ):
                #if label keep original text for placeholders
                if isinstance(item, QgsComposerLabel):
                    items.append([item, item.text()])
                else:
                    items.append(item)
        return items

    def __get_item_byid(self, composition, item_id):
        return composition.getComposerItemById(item_id)

    def __reorder_layers(self):
        #move ortho to bottom
        if not self.ortho_lyr is None:
            for idx in range(0, self.toc.topLevelItemCount()):
                if self.toc.topLevelItem(idx).text(0) == self.lyrname_ortho:
                    if VRP_DEBUG is True: QgsMessageLog.logMessage(u'idx {0}:{1}'.format(self.lyrname_ortho, idx), DLG_CAPTION)
                    item = self.toc.takeTopLevelItem(idx)
                    if VRP_DEBUG is True: QgsMessageLog.logMessage(u'topLevelItemCount:{0}'.format(self.toc.topLevelItemCount()), DLG_CAPTION)
                    self.toc.insertTopLevelItem(self.toc.topLevelItemCount(), item)
                    #legiface.refreshLayerSymbology(self.ortho_lyr)
                    #self.canvas.setDirty(True)
                    #self.canvas.refresh()
                    #!!!!HACK TO REFRESH DRAWING ORDER
                    #With Python there is no possibility to change oder of layer
                    #in legend (=TOC)
                    #http://gis.stackexchange.com/a/42007
                    #Currently, using Python, there is limited functionality
                    #for manipulating the QgsLegend. There is the QgsLegendInterface
                    #but this does not have all the goodies that are present
                    #in the QgsLegend, QgsLegendLayer, the inherited QgsLegendItem,
                    #or any of the other classes associated with QgsLegend.
                    self.legiface.setLayerVisible(self.ortho_lyr, False)
                    self.legiface.setLayerVisible(self.ortho_lyr, True)
                    if VRP_DEBUG is True:
                        tmp = [lyr.name() for lyr in self.canvas.layers()]
                        QgsMessageLog.logMessage(u'layers:{0}'.format(tmp), DLG_CAPTION)
                    break
            #o = self.toc.findItems(self.lyrname_ortho, Qt.MatchExactly)[0]
            #QgsMessageLog.logMessage('toc:{0}'.format(dir(o)), DLG_CAPTION)
            #toc.sortItems(0, Qt.AscendingOrder)
        #move dkm to top
        lyr_dkm_gst = self.__get_layer(self.lyrname_dkm_gst)
        lyr_dkm_gnr = self.__get_layer(self.lyrname_dkm_gnr)
        if lyr_dkm_gst is None or lyr_dkm_gnr is None:
            return
        #gst to top
        for idx in range(0, self.toc.topLevelItemCount()):
            if self.toc.topLevelItem(idx).text(0) == self.lyrname_dkm_gst:
                item = self.toc.takeTopLevelItem(idx)
                self.toc.insertTopLevelItem(0, item)
                self.legiface.setLayerVisible(lyr_dkm_gst, False)
                self.legiface.setLayerVisible(lyr_dkm_gst, True)
                break
        #move gnr to top
        for idx in range(0, self.toc.topLevelItemCount()):
            if self.toc.topLevelItem(idx).text(0) == self.lyrname_dkm_gnr:
                item = self.toc.takeTopLevelItem(idx)
                self.toc.insertTopLevelItem(0, item)
                self.legiface.setLayerVisible(lyr_dkm_gnr, False)
                self.legiface.setLayerVisible(lyr_dkm_gnr, True)
                break

    def __get_layer(self, lyrname):
        for lyr in self.legiface.layers():
            if lyr.name() == lyrname:
                return lyr
        return None

    def __add_raster_layer(self, rasterfile, legend_name=None):
        try:
            if VRP_DEBUG is True: QgsMessageLog.logMessage('export pdf (__add_raster_layer): {0}'.format(rasterfile), DLG_CAPTION)
            if legend_name is None:
                fileinfo = QFileInfo(rasterfile)
                basename = fileinfo.baseName()
            else:
                basename = legend_name
            lyr = QgsRasterLayer(rasterfile, basename)
            if not lyr.isValid():
                QgsMessageLog.logMessage( u'Raster [{0}] konnte nicht geladen werden!'.format(rasterfile), DLG_CAPTION)
                return None
            QgsMapLayerRegistry.instance().addMapLayer(lyr)
            return lyr
        except:
            msg = 'export pdf (__add_raster_layer): {0}'.format(traceback.format_exc())
            QgsMessageLog.logMessage(msg, DLG_CAPTION)
            return None

    def __add_layers(self, thema):
        try:
            layers = []
            for quelle in thema.quellen:
                pfad = quelle.pfad.replace('{gem_name}', self.gem_name)
                qml = None
                if not quelle.qml is None:
                    qml = quelle.qml.replace('{gem_name}', self.gem_name)
                if VRP_DEBUG is True: QgsMessageLog.logMessage('adding lyr:\n{0}\n{1}'.format(pfad, qml), DLG_CAPTION)
                if pfad.lower().endswith('.shp') is True:
                    lyr = QgsVectorLayer(pfad, quelle.name, 'ogr')
                    if not quelle.filter is None:
                        if VRP_DEBUG is True: QgsMessageLog.logMessage('{0}'.format(quelle.filter), DLG_CAPTION)
                        #exp = QgsExpression(quelle.filter)
                        #if exp.hasParserError():
                        #    QgsMessageLog.logMessage( u'Filter ungültig!\nQuelle:[{0}]\nFilter:{1}'.format(quelle.name, quelle.filter), DLG_CAPTION)
                        #else:
                        #    exp.prepare(lyr.pendingFields())
                        lyr.setSubsetString(quelle.filter)
                else:
                    fileinfo = QFileInfo(pfad)
                    basename = fileinfo.baseName()
                    lyr = QgsRasterLayer(pfad, basename)
                    if not lyr.isValid():
                        QgsMessageLog.logMessage( u'Raster [{0}] konnte nicht geladen werden:\n{1}'.format(thema.name, pfad), DLG_CAPTION)
                        continue
                if not qml is None:
                    lyr.loadNamedStyle(qml)
                QgsMapLayerRegistry.instance().addMapLayer(lyr)
                #turn off layer, if no qml present
                #for layer that should not be displayed but should be
                #used for statistics
                if qml is None:
                    self.legiface.setLayerVisible(lyr, False)
                layers.append(lyr)
            return layers
        except:
            msg = 'export pdf (__add_layers): {0}'.format(sys.exc_info()[0])
            QgsMessageLog.logMessage(msg, DLG_CAPTION)
            return None

    def __delete_pdf(self):
        if os.path.isfile(self.pdf_map):
            try:
                os.remove(self.pdf_map)
            except:
                if VRP_DEBUG is True: QgsMessageLog.logMessage(u'delete error: {0}'.format(self.pdf_map), DLG_CAPTION)
                return u'Konnte Ausgabedatei nicht ĺöschen!\n{0}'.format(self.pdf_map)
        return None

    def __read_template(self, textinfo=False):
        if textinfo:
            filename = self.settings.textinfo_layout()
        else:
            filename = self.template_qpt
        if VRP_DEBUG is True: QgsMessageLog.logMessage(u'reading template: {0}'.format(filename), DLG_CAPTION)
        xml_file = QFile(filename)
        #if xml_file.exists() is False:
        #    return u'\nTemplate ist nicht vorhanden!\n\n{0}'.format(self.template_qpt), None
        if xml_file.open(QIODevice.ReadOnly) is False:
            return u'\nKonnte Template nicht öffnen!\n\n{0}\n\n{1}: {2}'.format(filename, xml_file.error(), xml_file.errorString()), None
        xml_doc = QDomDocument('mydoc')
        xml_doc.setContent(xml_file)
        return None, xml_doc
示例#33
0
class Map():
    """A class for creating a map."""
    def __init__(self, iface):
        """Constructor for the Map class.

        :param iface: Reference to the QGIS iface object.
        :type iface: QgsAppInterface
        """
        LOGGER.debug('InaSAFE Map class initialised')
        self.iface = iface
        self.layer = iface.activeLayer()
        self.keyword_io = KeywordIO()
        self.printer = None
        self.composition = None
        self.extent = iface.mapCanvas().extent()
        self.logo = ':/plugins/inasafe/bnpb_logo.png'
        self.template = ':/plugins/inasafe/inasafe.qpt'
        self.page_width = 0  # width in mm
        self.page_height = 0  # height in mm
        self.page_dpi = 300.0
        self.show_frames = False  # intended for debugging use only

    @staticmethod
    def tr(string):
        """We implement this since we do not inherit QObject.

        :param string: String for translation.
        :type string: QString, str

        :returns: Translated version of theString.
        :rtype: QString
        """
        # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
        return QtCore.QCoreApplication.translate('Map', string)

    def set_impact_layer(self, layer):
        """Set the layer that will be used for stats, legend and reporting.

        :param layer: Layer that will be used for stats, legend and reporting.
        :type layer: QgsMapLayer, QgsRasterLayer, QgsVectorLayer
        """
        self.layer = layer

    def set_logo(self, logo):
        """Set image that will be used as logo in reports.

        :param logo: Path to image file
        :type logo: str
        """
        self.logo = logo

    def set_template(self, template):
        """Set template that will be used for report generation.

        :param template: Path to composer template
        :type template: str
        """
        self.template = template

    def set_extent(self, extent):
        """Set extent or the report map

        :param extent: Extent of the report map
        :type extent: QgsRectangle

        """
        self.extent = extent

    def setup_composition(self):
        """Set up the composition ready for drawing elements onto it."""
        LOGGER.debug('InaSAFE Map setupComposition called')
        canvas = self.iface.mapCanvas()
        renderer = canvas.mapRenderer()
        self.composition = QgsComposition(renderer)
        self.composition.setPlotStyle(QgsComposition.Print)  # or preview
        self.composition.setPrintResolution(self.page_dpi)
        self.composition.setPrintAsRaster(True)

    def render(self):
        """Render the map composition to an image and save that to disk.

        :returns: A three-tuple of:
            * str: image_path - absolute path to png of rendered map
            * QImage: image - in memory copy of rendered map
            * QRectF: target_area - dimensions of rendered map
        :rtype: tuple
        """
        LOGGER.debug('InaSAFE Map renderComposition called')
        # NOTE: we ignore self.composition.printAsRaster() and always rasterize
        width = int(self.page_dpi * self.page_width / 25.4)
        height = int(self.page_dpi * self.page_height / 25.4)
        image = QtGui.QImage(
            QtCore.QSize(width, height),
            QtGui.QImage.Format_ARGB32)
        image.setDotsPerMeterX(dpi_to_meters(self.page_dpi))
        image.setDotsPerMeterY(dpi_to_meters(self.page_dpi))

        # Only works in Qt4.8
        #image.fill(QtGui.qRgb(255, 255, 255))
        # Works in older Qt4 versions
        image.fill(55 + 255 * 256 + 255 * 256 * 256)
        image_painter = QtGui.QPainter(image)
        source_area = QtCore.QRectF(
            0, 0, self.page_width,
            self.page_height)
        target_area = QtCore.QRectF(0, 0, width, height)
        self.composition.render(image_painter, target_area, source_area)
        image_painter.end()
        image_path = unique_filename(
            prefix='mapRender_',
            suffix='.png',
            dir=temp_dir())
        image.save(image_path)
        return image_path, image, target_area

    def make_pdf(self, filename):
        """Generate the printout for our final map.

        :param filename: Path on the file system to which the pdf should be
            saved. If None, a generated file name will be used.
        :type filename: str

        :returns: File name of the output file (equivalent to filename if
                provided).
        :rtype: str
        """
        LOGGER.debug('InaSAFE Map printToPdf called')
        if filename is None:
            map_pdf_path = unique_filename(
                prefix='report', suffix='.pdf', dir=temp_dir())
        else:
            # We need to cast to python string in case we receive a QString
            map_pdf_path = str(filename)

        self.load_template()

        resolution = self.composition.printResolution()
        self.printer = setup_printer(map_pdf_path, resolution=resolution)
        _, image, rectangle = self.render()
        painter = QtGui.QPainter(self.printer)
        painter.drawImage(rectangle, image, rectangle)
        painter.end()
        return map_pdf_path

    def map_title(self):
        """Get the map title from the layer keywords if possible.

        :returns: None on error, otherwise the title.
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapTitle called')
        try:
            title = self.keyword_io.read_keywords(self.layer, 'map_title')
            return title
        except KeywordNotFoundError:
            return None
        except Exception:
            return None

    def map_legend_attributes(self):
        """Get the map legend attribute from the layer keywords if possible.

        :returns: None on error, otherwise the attributes (notes and units).
        :rtype: None, str
        """
        LOGGER.debug('InaSAFE Map getMapLegendAtributes called')
        legend_attribute_list = [
            'legend_notes',
            'legend_units',
            'legend_title']
        legend_attribute_dict = {}
        for myLegendAttribute in legend_attribute_list:
            try:
                legend_attribute_dict[myLegendAttribute] = \
                    self.keyword_io.read_keywords(
                        self.layer, myLegendAttribute)
            except KeywordNotFoundError:
                pass
            except Exception:
                pass
        return legend_attribute_dict

    def load_template(self):
        """Load a QgsComposer map from a template.
        """
        self.setup_composition()

        template_file = QtCore.QFile(self.template)
        template_file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()

        document = QtXml.QDomDocument()
        document.setContent(template_content)

        # get information for substitutions
        # date, time and plugin version
        date_time = self.keyword_io.read_keywords(self.layer, 'time_stamp')
        if date_time is None:
            date = ''
            time = ''
        else:
            tokens = date_time.split('_')
            date = tokens[0]
            time = tokens[1]
        long_version = get_version()
        tokens = long_version.split('.')
        version = '%s.%s.%s' % (tokens[0], tokens[1], tokens[2])

        title = self.map_title()
        if not title:
            title = ''

        substitution_map = {
            'impact-title': title,
            'date': date,
            'time': time,
            'safe-version': version
        }
        LOGGER.debug(substitution_map)
        load_ok = self.composition.loadFromTemplate(document,
                                                    substitution_map)
        if not load_ok:
            raise ReportCreationError(
                self.tr('Error loading template %s') %
                self.template)

        self.page_width = self.composition.paperWidth()
        self.page_height = self.composition.paperHeight()

        # set logo
        image = self.composition.getComposerItemById('safe-logo')
        if image is not None:
            image.setPictureFile(self.logo)
        else:
            raise ReportCreationError(self.tr(
                'Image "safe-logo" could not be found'))

        # Get the main map canvas on the composition and set
        # its extents to the event.
        composer_map = self.composition.getComposerItemById('impact-map')
        if composer_map is not None:
            # Recenter the composer map on the center of the extent
            # Note that since the composer map is square and the canvas may be
            # arbitrarily shaped, we center based on the longest edge
            canvas_extent = self.extent
            width = canvas_extent.width()
            height = canvas_extent.height()
            longest_width = width
            if width < height:
                longest_width = height
            half_length = longest_width / 2
            center = canvas_extent.center()
            min_x = center.x() - half_length
            max_x = center.x() + half_length
            min_y = center.y() - half_length
            max_y = center.y() + half_length
            square_extent = QgsRectangle(min_x, min_y, max_x, max_y)
            composer_map.setNewExtent(square_extent)

            # calculate intervals for grid
            split_count = 5
            x_interval = square_extent.width() / split_count
            composer_map.setGridIntervalX(x_interval)
            y_interval = square_extent.height() / split_count
            composer_map.setGridIntervalY(y_interval)
        else:
            raise ReportCreationError(self.tr(
                'Map "impact-map" could not be found'))

        legend = self.composition.getComposerItemById('impact-legend')
        legend_attributes = self.map_legend_attributes()
        LOGGER.debug(legend_attributes)
        #legend_notes = mapLegendAttributes.get('legend_notes', None)
        #legend_units = mapLegendAttributes.get('legend_units', None)
        legend_title = legend_attributes.get('legend_title', None)
        if legend_title is None:
            legend_title = ""
        legend.setTitle(legend_title)
        legend.updateLegend()
示例#34
0
    def run(self, *args, **kwargs):
        """
        :param templatePath: The file path to the user-defined template.
        :param entityFieldName: The name of the column for the specified entity which
        must exist in the data source view or table.
        :param entityFieldValue: The value for filtering the records in the data source
        view or table.
        :param outputMode: Whether the output composition should be an image or PDF.
        :param filePath: The output file where the composition will be written to. Applies
        to single mode output generation.
        :param dataFields: List containing the field names whose values will be used to name the files.
        This is used in multiple mode configuration.
        :param fileExtension: The output file format. Used in multiple mode configuration.
        :param data_source: Name of the data source table or view whose
        row values will be used to name output files if the options has been
        specified by the user.
        """
        templatePath = args[0]
        entityFieldName = args[1]
        entityFieldValue = args[2]
        outputMode = args[3]
        filePath = kwargs.get("filePath", None)
        dataFields = kwargs.get("dataFields", [])
        fileExtension = kwargs.get("fileExtension", "")
        data_source = kwargs.get("data_source", "")
        
        templateFile = QFile(templatePath)
        
        if not templateFile.open(QIODevice.ReadOnly):
            return False, QApplication.translate("DocumentGenerator",
                                            "Cannot read template file.")

        templateDoc = QDomDocument()
        
        if templateDoc.setContent(templateFile):
            composerDS = ComposerDataSource.create(templateDoc)
            spatialFieldsConfig = SpatialFieldsConfiguration.create(templateDoc)
            composerDS.setSpatialFieldsConfig(spatialFieldsConfig)

            #Check if data source exists and return if it doesn't
            if not self.data_source_exists(composerDS):
                msg = QApplication.translate("DocumentGenerator",
                                             u"'{0}' data source does not exist in the database."
                                             u"\nPlease contact your database "
                                             u"administrator.".format(composerDS.name()))
                return False, msg

            #TODO: Need to automatically register custom configuration collections
            #Photo config collection
            ph_config_collection = PhotoConfigurationCollection.create(templateDoc)

            #Table configuration collection
            table_config_collection = TableConfigurationCollection.create(templateDoc)

            #Create chart configuration collection object
            chart_config_collection = ChartConfigurationCollection.create(templateDoc)

            #Load the layers required by the table composer items
            self._table_mem_layers = load_table_layers(table_config_collection)
            
            #Execute query
            dsTable,records = self._exec_query(composerDS.name(), entityFieldName, entityFieldValue)

            if records is None or len(records) == 0:
                return False, QApplication.translate("DocumentGenerator",
                                                    "No matching records in the database")
            
            """
            Iterate through records where a single file output will be generated for each matching record.
            """
            for rec in records:
                composition = QgsComposition(self._map_renderer)
                composition.loadFromTemplate(templateDoc)
                
                #Set value of composer items based on the corresponding db values
                for composerId in composerDS.dataFieldMappings().reverse:
                    #Use composer item id since the uuid is stripped off
                    composerItem = composition.getComposerItemById(composerId)
                    
                    if not composerItem is None:
                        fieldName = composerDS.dataFieldName(composerId)
                        fieldValue = getattr(rec,fieldName)
                        self._composeritem_value_handler(composerItem, fieldValue)

                #Extract photo information
                self._extract_photo_info(composition, ph_config_collection, rec)

                #Set table item values based on configuration information
                self._set_table_data(composition, table_config_collection, rec)

                #Refresh non-custom map composer items
                self._refresh_composer_maps(composition,
                                            spatialFieldsConfig.spatialFieldsMapping().keys())
                            
                #Create memory layers for spatial features and add them to the map
                for mapId,spfmList in spatialFieldsConfig.spatialFieldsMapping().iteritems():
                    map_item = composition.getComposerItemById(mapId)
                    
                    if not map_item is None:
                        #Clear any previous map memory layer
                        self.clear_temporary_map_layers()
                        
                        for spfm in spfmList:
                            #Use the value of the label field to name the layer
                            lbl_field = spfm.labelField()
                            spatial_field = spfm.spatialField()

                            if not spatial_field:
                                continue

                            if lbl_field:
                                if hasattr(rec, spfm.labelField()):
                                    layerName = getattr(rec, spfm.labelField())

                                else:
                                    layerName = self._random_feature_layer_name(spatial_field)
                            else:
                                layerName = self._random_feature_layer_name(spatial_field)
                            
                            #Extract the geometry using geoalchemy spatial capabilities
                            geom_value = getattr(rec, spatial_field)
                            if geom_value is None:
                                continue

                            geom_func = geom_value.ST_AsText()
                            geomWKT = self._dbSession.scalar(geom_func)

                            #Get geometry type
                            geom_type, srid = geometryType(composerDS.name(),
                                                          spatial_field)
                            
                            #Create reference layer with feature
                            ref_layer = self._build_vector_layer(layerName, geom_type, srid)

                            if ref_layer is None or not ref_layer.isValid():
                                continue
                            
                            #Add feature
                            bbox = self._add_feature_to_layer(ref_layer, geomWKT)
                            bbox.scale(spfm.zoomLevel())

                            #Workaround for zooming to single point extent
                            if ref_layer.wkbType() == QGis.WKBPoint:
                                canvas_extent = self._iface.mapCanvas().fullExtent()
                                cnt_pnt = bbox.center()
                                canvas_extent.scale(1.0/32, cnt_pnt)
                                bbox = canvas_extent

                            #Style layer based on the spatial field mapping symbol layer
                            symbol_layer = spfm.symbolLayer()
                            if not symbol_layer is None:
                                ref_layer.rendererV2().symbols()[0].changeSymbolLayer(0,spfm.symbolLayer())

                            '''
                            Add layer to map and ensure its always added at the top
                            '''
                            QgsMapLayerRegistry.instance().addMapLayer(ref_layer, False)
                            QgsProject.instance().layerTreeRoot().insertLayer(0, ref_layer)
                            self._iface.mapCanvas().setExtent(bbox)
                            self._iface.mapCanvas().refresh()

                            #Add layer to map memory layer list
                            self._map_memory_layers.append(ref_layer)

                        '''
                        Use root layer tree to get the correct ordering of layers
                        in the legend
                        '''
                        self._refresh_map_item(map_item)

                #Extract chart information and generate chart
                self._generate_charts(composition, chart_config_collection, rec)

                #Build output path and generate composition
                if not filePath is None and len(dataFields) == 0:
                    self._write_output(composition, outputMode, filePath)
                    
                elif filePath is None and len(dataFields) > 0:
                    docFileName = self._build_file_name(data_source, entityFieldName,
                                                      entityFieldValue, dataFields, fileExtension)

                    if not docFileName:
                        return (False, QApplication.translate("DocumentGenerator",
                                    "File name could not be generated from the data fields."))
                        
                    outputDir = self._composer_output_path()
                    if outputDir is None:
                        return (False, QApplication.translate("DocumentGenerator",
                            "System could not read the location of the output directory in the registry."))
                    
                    qDir = QDir()
                    if not qDir.exists(outputDir):
                        return (False, QApplication.translate("DocumentGenerator",
                                "Output directory does not exist"))
                    
                    absDocPath = u"{0}/{1}".format(outputDir, docFileName)
                    self._write_output(composition, outputMode, absDocPath)
            
            #Clear temporary layers
            self.clear_temporary_layers()
            
            return True, "Success"
        
        return False, "Document composition could not be generated"
示例#35
0
    def run(self, *args, **kwargs):
        """
        :param templatePath: The file path to the user-defined template.
        :param entityFieldName: The name of the column for the specified entity which
        must exist in the data source view or table.
        :param entityFieldValue: The value for filtering the records in the data source
        view or table.
        :param outputMode: Whether the output composition should be an image or PDF.
        :param filePath: The output file where the composition will be written to. Applies
        to single mode output generation.
        :param dataFields: List containing the field names whose values will be used to name the files.
        This is used in multiple mode configuration.
        :param fileExtension: The output file format. Used in multiple mode configuration.
        :param data_source: Name of the data source table or view whose
        row values will be used to name output files if the options has been
        specified by the user.
        """
        templatePath = args[0]
        entityFieldName = args[1]
        entityFieldValue = args[2]
        outputMode = args[3]
        filePath = kwargs.get("filePath", None)
        dataFields = kwargs.get("dataFields", [])
        fileExtension = kwargs.get("fileExtension", "")
        data_source = kwargs.get("data_source", "")

        templateFile = QFile(templatePath)

        if not templateFile.open(QIODevice.ReadOnly):
            return False, QApplication.translate("DocumentGenerator",
                                                 "Cannot read template file.")

        templateDoc = QDomDocument()

        if templateDoc.setContent(templateFile):
            composerDS = ComposerDataSource.create(templateDoc)
            spatialFieldsConfig = SpatialFieldsConfiguration.create(
                templateDoc)
            composerDS.setSpatialFieldsConfig(spatialFieldsConfig)

            #Check if data source exists and return if it doesn't
            if not self.data_source_exists(composerDS):
                msg = QApplication.translate(
                    "DocumentGenerator",
                    u"'{0}' data source does not exist in the database."
                    u"\nPlease contact your database "
                    u"administrator.".format(composerDS.name()))
                return False, msg

            #Set file name value formatter
            self._file_name_value_formatter = EntityValueFormatter(
                name=data_source)

            #Register field names to be used for file naming
            self._file_name_value_formatter.register_columns(dataFields)

            #TODO: Need to automatically register custom configuration collections
            #Photo config collection
            ph_config_collection = PhotoConfigurationCollection.create(
                templateDoc)

            #Table configuration collection
            table_config_collection = TableConfigurationCollection.create(
                templateDoc)

            #Create chart configuration collection object
            chart_config_collection = ChartConfigurationCollection.create(
                templateDoc)

            #Load the layers required by the table composer items
            self._table_mem_layers = load_table_layers(table_config_collection)

            #Execute query
            dsTable, records = self._exec_query(composerDS.name(),
                                                entityFieldName,
                                                entityFieldValue)

            if records is None or len(records) == 0:
                return False, QApplication.translate(
                    "DocumentGenerator", "No matching records in the database")
            """
            Iterate through records where a single file output will be generated for each matching record.
            """

            for rec in records:
                composition = QgsComposition(self._map_renderer)
                composition.loadFromTemplate(templateDoc)
                ref_layer = None
                #Set value of composer items based on the corresponding db values
                for composerId in composerDS.dataFieldMappings().reverse:
                    #Use composer item id since the uuid is stripped off
                    composerItem = composition.getComposerItemById(composerId)
                    if not composerItem is None:
                        fieldName = composerDS.dataFieldName(composerId)
                        fieldValue = getattr(rec, fieldName)
                        self._composeritem_value_handler(
                            composerItem, fieldValue)

                # Extract photo information
                self._extract_photo_info(composition, ph_config_collection,
                                         rec)

                # Set table item values based on configuration information
                self._set_table_data(composition, table_config_collection, rec)

                # Refresh non-custom map composer items
                self._refresh_composer_maps(
                    composition,
                    spatialFieldsConfig.spatialFieldsMapping().keys())

                # Create memory layers for spatial features and add them to the map
                for mapId, spfmList in spatialFieldsConfig.spatialFieldsMapping(
                ).iteritems():

                    map_item = composition.getComposerItemById(mapId)

                    if not map_item is None:
                        # #Clear any previous map memory layer
                        #self.clear_temporary_map_layers()

                        for spfm in spfmList:
                            #Use the value of the label field to name the layer
                            lbl_field = spfm.labelField()
                            spatial_field = spfm.spatialField()

                            if not spatial_field:
                                continue

                            if lbl_field:
                                if hasattr(rec, spfm.labelField()):
                                    layerName = getattr(rec, spfm.labelField())

                                else:
                                    layerName = self._random_feature_layer_name(
                                        spatial_field)
                            else:
                                layerName = self._random_feature_layer_name(
                                    spatial_field)

                            #Extract the geometry using geoalchemy spatial capabilities
                            geom_value = getattr(rec, spatial_field)
                            if geom_value is None:
                                continue

                            geom_func = geom_value.ST_AsText()
                            geomWKT = self._dbSession.scalar(geom_func)

                            #Get geometry type
                            geom_type, srid = geometryType(
                                composerDS.name(), spatial_field)

                            #Create reference layer with feature
                            ref_layer = self._build_vector_layer(
                                layerName, geom_type, srid)

                            if ref_layer is None or not ref_layer.isValid():
                                continue
                            #Add feature
                            bbox = self._add_feature_to_layer(
                                ref_layer, geomWKT)
                            bbox.scale(spfm.zoomLevel())

                            #Workaround for zooming to single point extent
                            if ref_layer.wkbType() == QGis.WKBPoint:
                                canvas_extent = self._iface.mapCanvas(
                                ).fullExtent()
                                cnt_pnt = bbox.center()
                                canvas_extent.scale(1.0 / 32, cnt_pnt)
                                bbox = canvas_extent

                            #Style layer based on the spatial field mapping symbol layer
                            symbol_layer = spfm.symbolLayer()
                            if not symbol_layer is None:
                                ref_layer.rendererV2().symbols(
                                )[0].changeSymbolLayer(0, spfm.symbolLayer())
                            '''
                            Add layer to map and ensure its always added at the top
                            '''
                            self.map_registry.addMapLayer(ref_layer)
                            self._iface.mapCanvas().setExtent(bbox)
                            self._iface.mapCanvas().refresh()
                            # Add layer to map memory layer list
                            self._map_memory_layers.append(ref_layer.id())
                            self._hide_layer(ref_layer)
                        '''
                        Use root layer tree to get the correct ordering of layers
                        in the legend
                        '''
                        self._refresh_map_item(map_item)

                #Extract chart information and generate chart
                self._generate_charts(composition, chart_config_collection,
                                      rec)

                #Build output path and generate composition
                if not filePath is None and len(dataFields) == 0:
                    self._write_output(composition, outputMode, filePath)

                elif filePath is None and len(dataFields) > 0:
                    docFileName = self._build_file_name(
                        data_source, entityFieldName, entityFieldValue,
                        dataFields, fileExtension)

                    # Replace unsupported characters in Windows file naming
                    docFileName = docFileName.replace('/', '_').replace \
                        ('\\', '_').replace(':', '_').strip('*?"<>|')

                    if not docFileName:
                        return (
                            False,
                            QApplication.translate(
                                "DocumentGenerator",
                                "File name could not be generated from the data fields."
                            ))

                    outputDir = self._composer_output_path()
                    if outputDir is None:
                        return (
                            False,
                            QApplication.translate(
                                "DocumentGenerator",
                                "System could not read the location of the output directory in the registry."
                            ))

                    qDir = QDir()
                    if not qDir.exists(outputDir):
                        return (False,
                                QApplication.translate(
                                    "DocumentGenerator",
                                    "Output directory does not exist"))

                    absDocPath = u"{0}/{1}".format(outputDir, docFileName)
                    self._write_output(composition, outputMode, absDocPath)

            return True, "Success"

        return False, "Document composition could not be generated"
    def load_template(self, renderer):
        """Load composer template for merged report.

        Validate it as well. The template needs to have:
        1. QgsComposerMap with id 'impact-map' for merged impact map.
        2. QgsComposerPicture with id 'safe-logo' for InaSAFE logo.
        3. QgsComposerLabel with id 'summary-report' for a summary of two
        impacts.
        4. QgsComposerLabel with id 'aggregation-area' to indicate the area
        of aggregation.
        5. QgsComposerScaleBar with id 'map-scale' for impact map scale.
        6. QgsComposerLegend with id 'map-legend' for impact map legend.
        7. QgsComposerPicture with id 'organisation-logo' for organisation
        logo.
        8. QgsComposerLegend with id 'impact-legend' for map legend.
        9. QgsComposerHTML with id 'merged-report-table' for the merged report.

        :param renderer: Map renderer
        :type renderer: QgsMapRenderer

        """
        # Create Composition
        composition = QgsComposition(renderer)

        template_file = QtCore.QFile(self.template_path)
        template_file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()

        # Create a dom document containing template content
        document = QtXml.QDomDocument()
        document.setContent(template_content)

        # Prepare Map Substitution
        impact_title = '%s and %s' % (self.first_impact['map_title'],
                                      self.second_impact['map_title'])
        substitution_map = {
            'impact-title': impact_title,
            'hazard-title': self.first_impact['hazard_title'],
            'disclaimer': self.disclaimer
        }

        # Load template
        load_status = composition.loadFromTemplate(document, substitution_map)
        if not load_status:
            raise ReportCreationError(
                self.tr('Error loading template %s') % self.template_path)

        # Validate all needed composer components
        component_ids = [
            'impact-map', 'safe-logo', 'summary-report', 'aggregation-area',
            'map-scale', 'map-legend', 'organisation-logo',
            'merged-report-table'
        ]
        for component_id in component_ids:
            component = composition.getComposerItemById(component_id)
            if component is None:
                raise ReportCreationError(
                    self.tr('Component %s could not be found' % component_id))

        # Set InaSAFE logo
        safe_logo = composition.getComposerItemById('safe-logo')
        safe_logo.setPictureFile(self.safe_logo_path)

        # set organisation logo
        org_logo = composition.getComposerItemById('organisation-logo')
        org_logo.setPictureFile(self.organisation_logo_path)

        # Set Map Legend
        legend = composition.getComposerItemById('map-legend')
        legend.updateLegend()

        return composition
示例#37
0
    def load_template(self, renderer):
        """Load composer template for merged report.

        Validate it as well. The template needs to have:
        1. QgsComposerMap with id 'impact-map' for merged impact map.
        2. QgsComposerPicture with id 'safe-logo' for InaSAFE logo.
        3. QgsComposerLabel with id 'summary-report' for a summary of two
        impacts.
        4. QgsComposerLabel with id 'aggregation-area' to indicate the area
        of aggregation.
        5. QgsComposerScaleBar with id 'map-scale' for impact map scale.
        6. QgsComposerLegend with id 'map-legend' for impact map legend.
        7. QgsComposerPicture with id 'organisation-logo' for organisation
        logo.
        8. QgsComposerLegend with id 'impact-legend' for map legend.
        9. QgsComposerHTML with id 'merged-report-table' for the merged report.

        :param renderer: Map renderer
        :type renderer: QgsMapRenderer

        """
        # Create Composition
        composition = QgsComposition(renderer)

        template_file = QtCore.QFile(self.template_path)
        template_file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text)
        template_content = template_file.readAll()
        template_file.close()

        # Create a dom document containing template content
        document = QtXml.QDomDocument()
        document.setContent(template_content)

        # Prepare Map Substitution
        impact_title = '%s and %s' % (
            self.first_impact['map_title'],
            self.second_impact['map_title'])
        substitution_map = {
            'impact-title': impact_title,
            'hazard-title': self.first_impact['hazard_title'],
            'disclaimer': self.disclaimer
        }

        # Load template
        load_status = composition.loadFromTemplate(document, substitution_map)
        if not load_status:
            raise ReportCreationError(
                self.tr('Error loading template %s') %
                self.template_path)

        # Validate all needed composer components
        component_ids = ['impact-map', 'safe-logo', 'summary-report',
                         'aggregation-area', 'map-scale', 'map-legend',
                         'organisation-logo', 'merged-report-table']
        for component_id in component_ids:
            component = composition.getComposerItemById(component_id)
            if component is None:
                raise ReportCreationError(self.tr(
                    'Component %s could not be found' % component_id))

        # Set InaSAFE logo
        safe_logo = composition.getComposerItemById('safe-logo')
        safe_logo.setPictureFile(self.safe_logo_path)

        # set organisation logo
        org_logo = composition.getComposerItemById('organisation-logo')
        org_logo.setPictureFile(self.organisation_logo_path)

        # Set Map Legend
        legend = composition.getComposerItemById('map-legend')
        legend.updateLegend()

        return composition
示例#38
0
    def generate_report(self):
        # Generate pdf report from impact/hazard
        if not self.impact_exists:
            # Cannot generate report when no impact layer present
            return

        project_path = os.path.join(self.report_path,
                                    'project-%s.qgs' % self.locale)
        project_instance = QgsProject.instance()
        project_instance.setFileName(project_path)
        project_instance.read()

        # Set up the map renderer that will be assigned to the composition
        map_renderer = CANVAS.mapRenderer()
        # Set the labelling engine for the canvas
        labelling_engine = QgsPalLabeling()
        map_renderer.setLabelingEngine(labelling_engine)

        # Enable on the fly CRS transformations
        map_renderer.setProjectionsEnabled(True)

        default_crs = map_renderer.destinationCrs()
        crs = QgsCoordinateReferenceSystem('EPSG:4326')
        map_renderer.setDestinationCrs(crs)

        # get layer registry
        layer_registry = QgsMapLayerRegistry.instance()
        layer_registry.removeAllMapLayers()
        # add impact layer
        population_affected_layer = read_qgis_layer(
            self.population_aggregate_path, self.tr('People Affected'))
        layer_registry.addMapLayer(population_affected_layer, True)
        # add boundary mask
        boundary_mask = read_qgis_layer(
            self.flood_fixtures_dir('boundary-mask.shp'))
        layer_registry.addMapLayer(boundary_mask, False)
        # add hazard layer
        hazard_layer = read_qgis_layer(self.hazard_path,
                                       self.tr('Flood Depth (cm)'))
        layer_registry.addMapLayer(hazard_layer, True)
        # add boundary layer
        boundary_layer = read_qgis_layer(
            self.flood_fixtures_dir('boundary-5.shp'))
        layer_registry.addMapLayer(boundary_layer, False)
        CANVAS.setExtent(boundary_layer.extent())
        CANVAS.refresh()
        # add basemap layer
        # this code uses OpenlayersPlugin
        base_map = QgsRasterLayer(self.flood_fixtures_dir('jakarta.jpg'))
        layer_registry.addMapLayer(base_map, False)
        CANVAS.refresh()

        template_path = self.flood_fixtures_dir('realtime-flood.qpt')

        with open(template_path) as f:
            template_content = f.read()

        document = QDomDocument()
        document.setContent(template_content)

        # set destination CRS to Jakarta CRS
        # EPSG:32748
        # This allows us to use the scalebar in meter unit scale
        crs = QgsCoordinateReferenceSystem('EPSG:32748')
        map_renderer.setDestinationCrs(crs)

        # Now set up the composition
        composition = QgsComposition(map_renderer)

        subtitution_map = self.event_dict()
        LOGGER.debug(subtitution_map)

        # load composition object from template
        result = composition.loadFromTemplate(document, subtitution_map)
        if not result:
            LOGGER.exception('Error loading template %s with keywords\n %s',
                             template_path, subtitution_map)
            raise MapComposerError

        # get main map canvas on the composition and set extent
        map_canvas = composition.getComposerItemById('map-canvas')
        if map_canvas:
            map_canvas.setNewExtent(map_canvas.currentMapExtent())
            map_canvas.renderModeUpdateCachedImage()
        else:
            LOGGER.exception('Map canvas could not be found in template %s',
                             template_path)
            raise MapComposerError

        # get map legend on the composition
        map_legend = composition.getComposerItemById('map-legend')
        if map_legend:
            # show only legend for Flood Depth
            # ''.star
            # showed_legend = [layer_id for layer_id in map_renderer.layerSet()
            #                  if layer_id.startswith('Flood_Depth')]
            # LOGGER.info(showed_legend)
            # LOGGER.info(map_renderer.layerSet())
            # LOGGER.info(hazard_layer.id())

            # map_legend.model().setLayerSet(showed_legend)
            # map_legend.modelV2().clear()
            # print dir(map_legend.modelV2())
            # map_legend.setLegendFilterByMapEnabled(True)

            map_legend.model().setLayerSet(
                [hazard_layer.id(),
                 population_affected_layer.id()])

        else:
            LOGGER.exception('Map legend could not be found in template %s',
                             template_path)
            raise MapComposerError

        content_analysis = composition.getComposerItemById(
            'content-analysis-result')
        if not content_analysis:
            message = 'Content analysis composer item could not be found'
            LOGGER.exception(message)
            raise MapComposerError(message)
        content_analysis_html = content_analysis.multiFrame()
        if content_analysis_html:
            # set url to generated html
            analysis_html_path = self.generate_analysis_result_html()
            # We're using manual HTML to avoid memory leak and segfault
            # happened when using Url Mode
            content_analysis_html.setContentMode(QgsComposerHtml.ManualHtml)
            with open(analysis_html_path) as f:
                content_analysis_html.setHtml(f.read())
                content_analysis_html.loadHtml()
        else:
            message = 'Content analysis HTML not found in template'
            LOGGER.exception(message)
            raise MapComposerError(message)

        # save a pdf
        composition.exportAsPDF(self.map_report_path)

        project_instance.write(QFileInfo(project_path))

        layer_registry.removeAllMapLayers()
        map_renderer.setDestinationCrs(default_crs)
        map_renderer.setProjectionsEnabled(False)