示例#1
0
def download(feature_type, output_base_path, extent, progress_dialog=None):
    """Download shapefiles from Kartoza server.

    .. versionadded:: 3.2

    :param feature_type: What kind of features should be downloaded.
        Currently 'buildings', 'building-points' or 'roads' are supported.
    :type feature_type: str

    :param output_base_path: The base path of the shape file.
    :type output_base_path: str

    :param extent: A list in the form [xmin, ymin, xmax, ymax] where all
    coordinates provided are in Geographic / EPSG:4326.
    :type extent: list

    :param progress_dialog: A progress dialog.
    :type progress_dialog: QProgressDialog

    :raises: ImportDialogError, CanceledImportDialogError
    """
    # preparing necessary data
    min_longitude = extent[0]
    min_latitude = extent[1]
    max_longitude = extent[2]
    max_latitude = extent[3]

    box = (
        '{min_longitude},{min_latitude},{max_longitude},'
        '{max_latitude}').format(
            min_longitude=min_longitude,
            min_latitude=min_latitude,
            max_longitude=max_longitude,
            max_latitude=max_latitude
    )

    url = (
        '{url_osm_prefix}'
        '{feature_type}'
        '{url_osm_suffix}?'
        'bbox={box}&'
        'qgis_version={qgis}&'
        'lang={lang}&'
        'inasafe_version={inasafe_version}'.format(
            url_osm_prefix=URL_OSM_PREFIX,
            feature_type=feature_type,
            url_osm_suffix=URL_OSM_SUFFIX,
            box=box,
            qgis=qgis_version(),
            lang=locale(),
            inasafe_version=get_version()))

    path = tempfile.mktemp('.shp.zip')

    # download and extract it
    fetch_zip(url, path, feature_type, progress_dialog)
    extract_zip(path, output_base_path)

    if progress_dialog:
        progress_dialog.done(QDialog.Accepted)
    def test_print_default_template(self):
        """Test printing report to pdf using default template works."""
        impact_layer_path = test_data_path(
            'impact', 'population_affected_entire_area.shp')
        layer, _ = load_layer(impact_layer_path)
        # noinspection PyUnresolvedReferences
        QgsMapLayerRegistry.instance().addMapLayer(layer)
        # noinspection PyCallingNonCallable
        rect = QgsRectangle(106.8194, -6.2108, 106.8201, -6.1964)
        CANVAS.setExtent(rect)
        CANVAS.refresh()

        template = resources_path(
            'qgis-composer-templates', 'inasafe-portrait-a4.qpt')
        report = ImpactReport(IFACE, template, layer)
        out_path = unique_filename(
            prefix='map_default_template_test',
            suffix='.pdf',
            dir=temp_dir('test'))
        report.print_map_to_pdf(out_path)

        # Check the file exists
        message = 'Rendered output does not exist: %s' % out_path
        self.assertTrue(os.path.exists(out_path), message)

        # Check the file is not corrupt
        message = 'The output file %s is corrupt' % out_path
        out_size = os.stat(out_path).st_size
        self.assertTrue(out_size > 0, message)

        # Check the components in composition are default components
        if qgis_version() < 20500:
            safe_logo = report.composition.getComposerItemById(
                'safe-logo').pictureFile()
            north_arrow = report.composition.getComposerItemById(
                'north-arrow').pictureFile()
            org_logo = report.composition.getComposerItemById(
                'organisation-logo').pictureFile()
        else:
            safe_logo = report.composition.getComposerItemById(
                'safe-logo').picturePath()
            north_arrow = report.composition.getComposerItemById(
                'north-arrow').picturePath()
            org_logo = report.composition.getComposerItemById(
                'organisation-logo').picturePath()

        expected_safe_logo = resources_path(
            'img', 'logos', 'inasafe-logo-url.svg')
        expected_north_arrow = resources_path(
            'img', 'north_arrows', 'simple_north_arrow.png')
        expected_org_logo = resources_path('img', 'logos', 'supporters.png')

        message = 'The safe logo path is not the default one'
        self.assertEqual(expected_safe_logo, safe_logo, message)

        message = 'The north arrow path is not the default one'
        self.assertEqual(expected_north_arrow, north_arrow, message)

        message = 'The organisation logo path is not the default one'
        self.assertEqual(expected_org_logo, org_logo, message)
示例#3
0
def _run_tests(test_suite, package_name, with_coverage=False):
    """Core function to test a test suite."""
    count = test_suite.countTestCases()
    print('########')
    print('%s tests has been discovered in %s' % (count, package_name))
    print('QGIS : %s' % qgis_version())
    print('Python GDAL : %s' % gdal.VersionInfo('VERSION_NUM'))
    print('QT : %s' % Qt.QT_VERSION_STR)
    print('Run slow tests : %s' % (not os.environ.get('ON_TRAVIS', False)))
    print('########')
    if with_coverage:
        cov = coverage.Coverage(
            source=['safe/'],
            omit=['*/test/*', 'safe/definitions/*'],
        )
        cov.start()

    unittest.TextTestRunner(verbosity=3, stream=sys.stdout).run(test_suite)

    if with_coverage:
        cov.stop()
        cov.save()
        report = tempfile.NamedTemporaryFile(delete=False)
        cov.report(file=report)
        # Produce HTML reports in the `htmlcov` folder and open index.html
        # cov.html_report()
        report.close()
        with open(report.name, 'r') as fin:
            print(fin.read())
示例#4
0
    def __init__(self, iface, template, layer):
        """Constructor for the Composition Report class.

        :param iface: Reference to the QGIS iface object.
        :type iface: QgsAppInterface

        :param template: The QGIS template path.
        :type template: str
        """
        LOGGER.debug('InaSAFE Impact Report class initialised')
        self._iface = iface
        self._template = template
        self._layer = layer
        self._extent = self._iface.mapCanvas().extent()
        self._page_dpi = 300.0
        self._safe_logo = resources_path(
            'img', 'logos', 'inasafe-logo-url.svg')
        self._organisation_logo = default_organisation_logo_path()
        self._north_arrow = default_north_arrow_path()
        self._disclaimer = disclaimer()

        # For QGIS < 2.4 compatibility
        # QgsMapSettings is added in 2.4
        if qgis_version() < 20400:
            map_settings = self._iface.mapCanvas().mapRenderer()
        else:
            map_settings = self._iface.mapCanvas().mapSettings()

        self._template_composition = TemplateComposition(
            template_path=self.template,
            map_settings=map_settings)
        self._keyword_io = KeywordIO()
示例#5
0
    def request(self, request_url):
        """Mock handler for an http request.
        :param request_url: Url being requested.
        """
        url = str(request_url.url().toString())
        reply = MockQNetworkReply()

        if url == 'http://hot-export.geofabrik.de/newjob':
            reply.content = read_all('test-importdlg-newjob.html')
        elif url == 'http://hot-export.geofabrik.de/wizard_area':
            reply.content = read_all('test-importdlg-wizardarea.html')
        elif url == 'http://hot-export.geofabrik.de/tagupload':
            reply.content = read_all('test-importdlg-job.html')
            reply._url = 'http://hot-export.geofabrik.de/jobs/1990'
        elif url == 'http://hot-export.geofabrik.de/jobs/1990':
            reply.content = read_all('test-importdlg-job.html')
        elif url == ('http://osm.inasafe.org/buildings-shp?'
                     'bbox=20.389938354492188,-34.10782492987083'
                     ',20.712661743164062,'
                     '-34.008273470938335&qgis_version=%s'
                     '&inasafe_version=%s'
                     '&lang=en' % (qgis_version(), get_version())):
            reply.content = read_all("test-importdlg-extractzip.zip")

        return reply
示例#6
0
    def accept(self):
        """Upload the layer to Geonode."""
        enable_busy_cursor()
        self.button_box.setEnabled(False)
        layer = layer_from_combo(self.layers)

        login = self.login.text()
        if self.save_login.isChecked():
            set_setting(GEONODE_USER, login)
        else:
            set_setting(GEONODE_USER, '')

        password = self.password.text()
        if self.save_password.isChecked():
            set_setting(GEONODE_PASSWORD, password)
        else:
            set_setting(GEONODE_PASSWORD, '')

        url = self.url.text()
        if self.save_url.isChecked():
            set_setting(GEONODE_URL, url)
        else:
            set_setting(GEONODE_URL, '')

        geonode_session = login_user(url, login, password)

        try:
            result = upload(url, geonode_session, layer.source())
        except GeoNodeLayerUploadError as e:
            result = {
                'success': False,
                'message': str(e)
            }
        finally:
            self.button_box.setEnabled(True)
            disable_busy_cursor()

        if result['success']:
            self.done(QDialog.Accepted)
            layer_url = urljoin(url, result['url'])
            # Link is not working in QGIS 2.
            # It's gonna work in QGIS 3.
            if qgis_version() >= 29900:
                external_link = '<a href=\"{url}\">{url}</a>'.format(
                    url=layer_url)
            else:
                external_link = layer_url

            display_success_message_bar(
                tr('Uploading done'),
                tr('Successfully uploaded to {external_link}').format(
                    external_link=external_link),
            )
        else:
            display_warning_message_box(
                self,
                tr('Error while uploading the layer.'),
                str(result)
            )
示例#7
0
    def test_issue71(self):
        """Test issue #71 in github - cbo changes should update ok button."""
        # See https://github.com/AIFDR/inasafe/issues/71
        # Push OK with the left mouse button
        print 'Using QGIS: %s' % qgis_version()
        self.tearDown()
        button = DOCK.pbnRunStop
        # First part of scenario should have enabled run
        file_list = [
            join(HAZDATA, 'Flood_Current_Depth_Jakarta_geographic.asc'),
            join(TESTDATA, 'Population_Jakarta_geographic.asc')]
        hazard_layer_count, exposure_layer_count = load_layers(file_list)

        message = (
            'Incorrect number of Hazard layers: expected 1 got %s'
            % hazard_layer_count)
        self.assertTrue(hazard_layer_count == 1, message)

        message = (
            'Incorrect number of Exposure layers: expected 1 got %s'
            % exposure_layer_count)
        self.assertTrue(exposure_layer_count == 1, message)

        message = 'Run button was not enabled'
        self.assertTrue(button.isEnabled(), message)

        # Second part of scenario - run disabled when adding invalid layer
        # and select it - run should be disabled
        path = os.path.join(TESTDATA, 'issue71.tif')
        file_list = [path]  # This layer has incorrect keywords
        clear_flag = False
        _, _ = load_layers(file_list, clear_flag)
        # set exposure to : Population Count (5kmx5km)
        # by moving one down
        DOCK.cboExposure.setCurrentIndex(DOCK.cboExposure.currentIndex() + 1)
        actual_dict = get_ui_state(DOCK)
        expected_dict = {
            'Run Button Enabled': False,
            'Impact Function Id': '',
            'Impact Function Title': '',
            'Hazard': 'A flood in Jakarta like in 2007',
            'Exposure': 'Population Count (5kmx5km)'}
        message = ((
            'Run button was not disabled when exposure set to \n%s'
            '\nUI State: \n%s\nExpected State:\n%s\n%s') % (
                DOCK.cboExposure.currentText(),
                actual_dict,
                expected_dict,
                combos_to_string(DOCK)))

        self.assertTrue(expected_dict == actual_dict, message)

        # Now select again a valid layer and the run button
        # should be enabled
        DOCK.cboExposure.setCurrentIndex(DOCK.cboExposure.currentIndex() - 1)
        message = (
            'Run button was not enabled when exposure set to \n%s' %
            DOCK.cboExposure.currentText())
        self.assertTrue(button.isEnabled(), message)
示例#8
0
def _run_tests(test_suite, package_name):
    """Core function to test a test suite."""
    count = test_suite.countTestCases()
    print '########'
    print '%s tests has been discovered in %s' % (count, package_name)
    print 'QGIS : %s' % qgis_version()
    print 'Run slow tests : %s' % (not os.environ.get('ON_TRAVIS', False))
    print '########'
    unittest.TextTestRunner(verbosity=3, stream=sys.stdout).run(test_suite)
示例#9
0
    def test_save_scenario(self):
        """Test saving Current scenario."""
        result, message = setup_scenario(
            DOCK,
            hazard='Classified Flood',
            exposure='Population',
            function='Be affected by each hazard class',
            function_id='ClassifiedRasterHazardPopulationFunction')
        self.assertTrue(result, message)

        # Enable on-the-fly reprojection
        set_canvas_crs(GEOCRS, True)
        set_jakarta_extent(dock=DOCK)

        # create unique file
        scenario_file = unique_filename(
            prefix='scenarioTest', suffix='.txt', dir=temp_dir('test'))

        self.save_scenario_dialog.save_scenario(
            scenario_file_path=scenario_file)
        with open(scenario_file) as f:
            data = f.readlines()
        title = data[0][:-1]
        exposure = data[1][:-1]
        hazard = data[2][:-1]
        function = data[3][:-1]
        extent = data[4][:-1]
        self.assertTrue(
            os.path.exists(scenario_file),
            'File %s does not exist' % scenario_file)
        self.assertTrue(
            title == '[Classified Flood]',
            'Title is not the same')
        self.assertTrue(
            exposure.startswith('exposure =') and exposure.endswith(
                'pop_binary_raster_20_20.asc'),
            'Exposure is not the same')
        self.assertTrue(
            hazard.startswith('hazard =') and hazard.endswith(
                'classified_flood_20_20.asc'),
            'Hazard is not the same')
        self.assertTrue(
            function == (
                'function = ClassifiedRasterHazardPopulationFunction'),
            'Impact function is not same')

        # TODO: figure out why this changed between releases
        if qgis_version() < 20400:
            # For QGIS 2.0
            expected_extent = (
                'extent = 106.313333, -6.380000, 107.346667, -6.070000')
            self.assertEqual(expected_extent, extent)
        else:
            # for QGIS 2.4
            expected_extent = (
                'extent = 106.287500, -6.380000, 107.372500, -6.070000')
            self.assertEqual(expected_extent, expected_extent)
示例#10
0
def download(feature_type, output_base_path, extent, progress_dialog=None):
    """Download shapefiles from Kartoza server.

    .. versionadded:: 3.2

    :param feature_type: What kind of features should be downloaded.
        Currently 'buildings', 'building-points' or 'roads' are supported.
    :type feature_type: str

    :param output_base_path: The base path of the shape file.
    :type output_base_path: str

    :param extent: A list in the form [xmin, ymin, xmax, ymax] where all
    coordinates provided are in Geographic / EPSG:4326.
    :type extent: list

    :param progress_dialog: A progress dialog.
    :type progress_dialog: QProgressDialog

    :raises: ImportDialogError, CanceledImportDialogError
    """

    # preparing necessary data
    min_longitude = extent[0]
    min_latitude = extent[1]
    max_longitude = extent[2]
    max_latitude = extent[3]

    box = (
        '{min_longitude},{min_latitude},{max_longitude},'
        '{max_latitude}').format(
            min_longitude=min_longitude,
            min_latitude=min_latitude,
            max_longitude=max_longitude,
            max_latitude=max_latitude
    )

    url = URL_OSM_PREFIX + feature_type + URL_OSM_SUFFIX
    url = '{url}?bbox={box}&qgis_version={qgis}'.format(
        url=url, box=box, qgis=qgis_version())

    url += '&inasafe_version=%s' % get_version()

    if 'LANG' in os.environ:
        # Get the language only : eg 'en_US' -> 'en'
        env_lang = os.environ['LANG'].split('_')[0]
        url += '&lang=%s' % env_lang

    path = tempfile.mktemp('.shp.zip')

    # download and extract it
    fetch_zip(url, path, feature_type, progress_dialog)
    extract_zip(path, output_base_path)

    if progress_dialog:
        progress_dialog.done(QDialog.Accepted)
示例#11
0
def create_profile_layer(profiling):
    """Create a tabular layer with the profiling.

    :param profiling: A dict containing benchmarking data.
    :type profiling: safe.messaging.message.Message

    :return: A tabular layer.
    :rtype: QgsVectorLayer
    """
    fields = [
        create_field_from_definition(profiling_function_field),
        create_field_from_definition(profiling_time_field)
    ]
    if setting(key='memory_profile', expected_type=bool):
        fields.append(create_field_from_definition(profiling_memory_field))
    tabular = create_memory_layer(
        'profiling',
        QgsWkbTypes.NullGeometry,
        fields=fields)

    # Generate profiling keywords
    tabular.keywords['layer_purpose'] = layer_purpose_profiling['key']
    tabular.keywords['title'] = layer_purpose_profiling['name']
    if qgis_version() >= 21800:
        tabular.setName(tabular.keywords['title'])
    else:
        tabular.setLayerName(tabular.keywords['title'])
    tabular.keywords['inasafe_fields'] = {
        profiling_function_field['key']:
            profiling_function_field['field_name'],
        profiling_time_field['key']:
            profiling_time_field['field_name'],
    }
    if setting(key='memory_profile', expected_type=bool):
        tabular.keywords['inasafe_fields'][
            profiling_memory_field['key']] = profiling_memory_field[
            'field_name']
    tabular.keywords[inasafe_keyword_version_key] = (
        inasafe_keyword_version)

    table = profiling.to_text().splitlines()[3:]
    tabular.startEditing()
    for line in table:
        feature = QgsFeature()
        items = line.split(', ')
        time = items[1].replace('-', '')
        if setting(key='memory_profile', expected_type=bool):
            memory = items[2].replace('-', '')
            feature.setAttributes([items[0], time, memory])
        else:
            feature.setAttributes([items[0], time])
        tabular.addFeature(feature)

    tabular.commitChanges()
    return tabular
示例#12
0
def add_impact_layers_to_canvas(impact_function, group=None, iface=None):
    """Helper method to add impact layer to QGIS from impact function.

    :param impact_function: The impact function used.
    :type impact_function: ImpactFunction

    :param group: An existing group as a parent, optional.
    :type group: QgsLayerTreeGroup

    :param iface: QGIS QgisAppInterface instance.
    :type iface: QgisAppInterface
    """
    layers = impact_function.outputs
    name = impact_function.name

    if group:
        group_analysis = group
    else:
        # noinspection PyArgumentList
        root = QgsProject.instance().layerTreeRoot()
        group_analysis = root.insertGroup(0, name)
        group_analysis.setItemVisibilityChecked(True)

    group_analysis.setExpanded(group is None)

    for layer in layers:
        # noinspection PyArgumentList
        QgsProject.instance().addMapLayer(layer, False)
        layer_node = group_analysis.addLayer(layer)

        # set layer title if any
        try:
            title = layer.keywords['title']
            if qgis_version() >= 21800:
                layer.setName(title)
            else:
                layer.setLayerName(title)
        except KeyError:
            pass

        visible_layers = [impact_function.impact.id()]
        print_atlas = setting('print_atlas_report', False, bool)
        if print_atlas:
            visible_layers.append(impact_function.aggregation_summary.id())
        # Let's enable only the more detailed layer. See #2925
        if layer.id() in visible_layers:
            layer_node.setItemVisibilityChecked(True)
        else:
            layer_node.setItemVisibilityChecked(False)

        # we need to set analysis_impacted as an active layer because we need
        # to get all qgis variables that we need from this layer for
        # infographic.
        if iface:
            iface.setActiveLayer(impact_function.analysis_impacted)
示例#13
0
    def test_clip_geometry(self):
        """Test that we can clip a geometry using another geometry."""
        geometry = QgsGeometry.fromPolyline([
            QgsPoint(10, 10), QgsPoint(20, 20), QgsPoint(30, 30),
            QgsPoint(40, 40)])

        clip_polygon = QgsGeometry.fromPolygon([
            [QgsPoint(20, 20),
             QgsPoint(20, 30),
             QgsPoint(30, 30),
             QgsPoint(30, 20),
             QgsPoint(20, 20)]])

        result = clip_geometry(clip_polygon, geometry)

        expected_wkt = 'LINESTRING(20.0 20.0, 30.0 30.0)'
        # There should only be one feature that intersects this clip
        # poly so this assertion should work.
        assert compare_wkt(expected_wkt, str(result.exportToWkt()))

        # Now poly on poly clip test
        clip_polygon = QgsGeometry.fromWkt(
            'POLYGON((106.8218 -6.1842,106.8232 -6.1842,'
            '106.82304963 -6.18317148,106.82179736 -6.18314774,'
            '106.8218 -6.1842))')
        geometry = QgsGeometry.fromWkt(
            'POLYGON((106.8216869 -6.1852067,106.8213233 -6.1843051,'
            '106.8212891 -6.1835559,106.8222566 -6.1835184,'
            '106.8227557 -6.1835076,106.8228588 -6.1851204,'
            '106.8216869 -6.1852067))')
        result = clip_geometry(clip_polygon, geometry)

        expected_wkt = (
            'POLYGON((106.82179833 -6.18353616,106.8222566 -6.1835184,'
            '106.8227557 -6.1835076,106.82279996 -6.1842,'
            '106.8218 -6.1842,106.82179833 -6.18353616))')
        # There should only be one feature that intersects this clip
        # poly so this assertion should work.
        assert compare_wkt(expected_wkt, str(result.exportToWkt()))

        # Now point on poly test clip

        geometry = QgsGeometry.fromWkt('POINT(106.82241 -6.18369)')
        result = clip_geometry(clip_polygon, geometry)

        if qgis_version() > 10800:
            expected_wkt = 'POINT(106.82241 -6.18369)'
        else:
            expected_wkt = 'POINT(106.822410 -6.183690)'
            # There should only be one feature that intersects this clip
        # poly so this assertion should work.
        assert compare_wkt(expected_wkt, str(result.exportToWkt()))
def make_keywordless_layer():
    """Helper function that returns a single predefined keywordless layer."""
    path = 'keywordless_layer.tif'
    base_path = test_data_path('hazard')
    full_path = os.path.abspath(os.path.join(base_path, path))
    title = 'Keywordless Layer'
    # noinspection PyCallingNonCallable
    layer = QgsRasterLayer(full_path, title)
    if qgis_version() >= 10800:  # 1.8 or newer
        # noinspection PyArgumentList,PyUnresolvedReferences
        QgsMapLayerRegistry.instance().addMapLayers([layer])
    else:
        # noinspection PyArgumentList,PyUnresolvedReferences
        QgsMapLayerRegistry.instance().addMapLayers([layer])
    return layer
示例#15
0
def add_layer_to_canvas(layer, name):
    """Helper method to add layer to QGIS.

    :param layer: The layer.
    :type layer: QgsMapLayer

    :param name: Layer name.
    :type name: str

    """
    if qgis_version() >= 21800:
        layer.setName(name)
    else:
        layer.setLayerName(name)

    QgsProject.instance().addMapLayer(layer, False)
    def load_shapefile(self, feature_type, base_path):
        """Load downloaded shape file to QGIS Main Window.

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param base_path: The base path of the shape file (without extension).
        :type base_path: str

        :raises: FileMissingError - when buildings.shp not exist
        """

        path = '%s.shp' % base_path

        if not os.path.exists(path):
            message = self.tr(
                '%s does not exist. The server does not have any data for '
                'this extent.' % path)
            raise FileMissingError(message)

        layer = self.iface.addVectorLayer(path, feature_type, 'ogr')

        # Check if it's a building layer and if it's QGIS 2.14 about the 2.5D
        if qgis_version() >= 21400 and feature_type == 'buildings':
            layer_scope = QgsExpressionContextUtils.layerScope(layer)
            if not layer_scope.variable('qgis_25d_height'):
                QgsExpressionContextUtils.setLayerVariable(
                    layer, 'qgis_25d_height', 0.0002)
            if not layer_scope.variable('qgis_25d_angle'):
                QgsExpressionContextUtils.setLayerVariable(
                    layer, 'qgis_25d_angle', 70)

        canvas_srid = self.canvas.mapSettings().destinationCrs().srsid()
        on_the_fly_projection = self.canvas.hasCrsTransformEnabled()
        if canvas_srid != 4326 and not on_the_fly_projection:
            if QGis.QGIS_VERSION_INT >= 20400:
                self.canvas.setCrsTransformEnabled(True)
            else:
                display_warning_message_bar(
                    self.iface,
                    self.tr('Enable \'on the fly\''),
                    self.tr(
                        'Your current projection is different than EPSG:4326. '
                        'You should enable \'on the fly\' to display '
                        'correctly your layers')
                )
示例#17
0
    def test_custom_logo(self):
        """Test that setting user-defined logo works."""
        LOGGER.info('Testing custom_logo')
        impact_layer_path = test_data_path(
            'impact', 'population_affected_entire_area.shp')
        layer, _ = load_layer(impact_layer_path)
        # noinspection PyUnresolvedReferences,PyArgumentList
        QgsMapLayerRegistry.instance().addMapLayer(layer)
        # noinspection PyCallingNonCallable
        rect = QgsRectangle(106.8194, -6.2108, 106.8201, -6.1964)
        CANVAS.setExtent(rect)
        CANVAS.refresh()

        template = resources_path(
            'qgis-composer-templates', 'a4-portrait-blue.qpt')
        report = ImpactReport(IFACE, template, layer)

        # Set custom logo
        custom_logo_path = resources_path('img', 'logos', 'supporters.png')
        report.organisation_logo = custom_logo_path

        out_path = unique_filename(
            prefix='map_custom_logo_test', suffix='.pdf', dir=temp_dir('test'))
        report.print_map_to_pdf(out_path)

        # Check the file exists
        message = 'Rendered output does not exist: %s' % out_path
        self.assertTrue(os.path.exists(out_path), message)

        # Check the file is not corrupt
        message = 'The output file %s is corrupt' % out_path
        out_size = os.stat(out_path).st_size
        self.assertTrue(out_size > 0, message)

        # Check the organisation logo in composition sets correctly to
        # logo-flower
        if qgis_version() < 20500:
            custom_img_path = report.composition.getComposerItemById(
                'organisation-logo').pictureFile()
        else:
            custom_img_path = report.composition.getComposerItemById(
                'organisation-logo').picturePath()

        message = 'The custom logo path is not set correctly'
        self.assertEqual(custom_logo_path, custom_img_path, message)
示例#18
0
def add_impact_layers_to_canvas(impact_function, iface):
    """Helper method to add impact layer to QGIS from impact function.

    :param impact_function: The impact function used.
    :type impact_function: ImpactFunction

    :param iface: QGIS QGisAppInterface instance.
    :type iface: QGisAppInterface
    """
    layers = impact_function.outputs
    name = impact_function.name

    # noinspection PyArgumentList
    root = QgsProject.instance().layerTreeRoot()
    group_analysis = root.insertGroup(0, name)
    group_analysis.setVisible(Qt.Checked)
    for layer in layers:
        # noinspection PyArgumentList
        QgsMapLayerRegistry.instance().addMapLayer(layer, False)
        layer_node = group_analysis.addLayer(layer)

        # set layer title if any
        try:
            title = layer.keywords['title']
            if qgis_version() >= 21800:
                layer.setName(title)
            else:
                layer.setLayerName(title)
        except KeyError:
            pass

        # Let's enable only the more detailed layer. See #2925
        if layer.id() == impact_function.impact.id():
            layer_node.setVisible(Qt.Checked)
            iface.setActiveLayer(layer)
        elif is_raster_layer(layer):
            layer_node.setVisible(Qt.Checked)
        else:
            layer_node.setVisible(Qt.Unchecked)
示例#19
0
    def __init__(self, iface, template, layer, extra_layers=[]):
        """Constructor for the Composition Report class.

        :param iface: Reference to the QGIS iface object.
        :type iface: QgsAppInterface

        :param template: The QGIS template path.
        :type template: str
        """
        LOGGER.debug('InaSAFE Impact Report class initialised')
        self._iface = iface
        self._template = None
        self.template = template
        self._layer = layer
        self._extra_layers = extra_layers
        self._extent = self._iface.mapCanvas().extent()
        self._page_dpi = 300.0
        self._black_inasafe_logo = black_inasafe_logo_path()
        self._white_inasafe_logo = white_inasafe_logo_path()
        # User can change this path in preferences
        self._organisation_logo = supporters_logo_path()
        self._supporters_logo = supporters_logo_path()
        self._north_arrow = default_north_arrow_path()
        self._disclaimer = disclaimer()

        # For QGIS < 2.4 compatibility
        # QgsMapSettings is added in 2.4
        if qgis_version() < 20400:
            map_settings = self._iface.mapCanvas().mapRenderer()
        else:
            map_settings = self._iface.mapCanvas().mapSettings()

        self._template_composition = TemplateComposition(
            template_path=self.template,
            map_settings=map_settings)
        self._keyword_io = KeywordIO()
示例#20
0
 def test_get_qgis_version(self):
     """Test we can get the version of QGIS"""
     version = qgis_version()
     message = 'Got version %s of QGIS, but at least 107000 is needed'
     assert version > 10700, message
示例#21
0
def add_layers_to_canvas_with_custom_orders(order,
                                            impact_function,
                                            iface=None):
    """Helper to add layers to the map canvas following a specific order.

    From top to bottom in the legend:
        [
            ('FromCanvas', layer name, full layer URI, QML),
            ('FromAnalysis', layer purpose, layer group, None),
            ...
        ]

        The full layer URI is coming from our helper.

    :param order: Special structure the list of layers to add.
    :type order: list

    :param impact_function: The multi exposure impact function used.
    :type impact_function: MultiExposureImpactFunction

    :param iface: QGIS QgisAppInterface instance.
    :type iface: QgisAppInterface
    """
    root = QgsProject.instance().layerTreeRoot()
    # Make all layers hidden.
    for child in root.children():
        child.setItemVisibilityChecked(False)

    group_analysis = root.insertGroup(0, impact_function.name)
    group_analysis.setItemVisibilityChecked(True)
    group_analysis.setCustomProperty(MULTI_EXPOSURE_ANALYSIS_FLAG, True)

    # Insert layers in the good order in the group.
    for layer_definition in order:
        if layer_definition[0] == FROM_CANVAS['key']:
            style = QDomDocument()
            style.setContent(layer_definition[3])
            layer = load_layer(layer_definition[2], layer_definition[1])[0]
            layer.importNamedStyle(style)
            QgsProject.instance().addMapLayer(layer, False)
            layer_node = group_analysis.addLayer(layer)
            layer_node.setItemVisibilityChecked(True)
        else:
            if layer_definition[2] == impact_function.name:
                for layer in impact_function.outputs:
                    if layer.keywords['layer_purpose'] == layer_definition[1]:
                        QgsProject.instance().addMapLayer(layer, False)
                        layer_node = group_analysis.addLayer(layer)
                        layer_node.setItemVisibilityChecked(True)
                        try:
                            title = layer.keywords['title']
                            if qgis_version() >= 21800:
                                layer.setName(title)
                            else:
                                layer.setLayerName(title)
                        except KeyError:
                            pass
                        break
            else:
                for sub_impact_function in impact_function.impact_functions:
                    # Iterate over each sub impact function used in the
                    # multi exposure analysis.
                    if sub_impact_function.name == layer_definition[2]:
                        for layer in sub_impact_function.outputs:
                            purpose = layer_definition[1]
                            if layer.keywords['layer_purpose'] == purpose:
                                QgsProject.instance().addMapLayer(layer, False)
                                layer_node = group_analysis.addLayer(layer)
                                layer_node.setItemVisibilityChecked(True)
                                try:
                                    title = layer.keywords['title']
                                    if qgis_version() >= 21800:
                                        layer.setName(title)
                                    else:
                                        layer.setLayerName(title)
                                except KeyError:
                                    pass
                                break

    if iface:
        iface.setActiveLayer(impact_function.analysis_impacted)
示例#22
0
def analysis_summary(aggregate_hazard, analysis, callback=None):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | analysis_id |

    Output layer :
    | analysis_id | count_hazard_class | affected_count | total |

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param analysis: The target vector layer where to write statistics.
    :type analysis: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new target layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_3_analysis_steps['output_layer_name']
    processing_step = summary_3_analysis_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = analysis.keywords['inasafe_fields']

    target_compulsory_fields = [
        analysis_id_field,
        analysis_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field,
        total_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    absolute_values = create_absolute_values_structure(
        aggregate_hazard, ['all'])

    hazard_class = source_fields[hazard_class_field['key']]
    hazard_class_index = aggregate_hazard.fieldNameIndex(hazard_class)
    unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index)

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    total = source_fields[total_field['key']]

    flat_table = FlatTable('hazard_class')

    # First loop over the aggregate_hazard layer
    request = QgsFeatureRequest()
    request.setSubsetOfAttributes(
        [hazard_class, total], aggregate_hazard.fields())
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregate_hazard.getFeatures():
        hazard_value = area[hazard_class_index]
        value = area[total]
        if not value or isinstance(value, QPyNullVariant) or isnan(value):
            # For isnan, see ticket #3812
            value = 0
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = 'NULL'
        flat_table.add_value(
            value,
            hazard_class=hazard_value
        )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                all='all'
            )

    analysis.startEditing()

    shift = analysis.fields().count()

    counts = [
        total_affected_field,
        total_not_affected_field,
        total_not_exposed_field,
        total_field]

    add_fields(
        analysis,
        absolute_values,
        counts,
        unique_hazard,
        hazard_count_field)

    affected_sum = 0
    not_affected_sum = 0
    not_exposed_sum = 0

    for area in analysis.getFeatures(request):
        total = 0
        for i, val in enumerate(unique_hazard):
            if not val or isinstance(val, QPyNullVariant):
                val = 'NULL'
            sum = flat_table.get_value(hazard_class=val)
            total += sum
            analysis.changeAttributeValue(area.id(), shift + i, sum)

            affected = post_processor_affected_function(
                    classification=classification, hazard_class=val)
            if affected == not_exposed_class['key']:
                not_exposed_sum += sum
            elif affected:
                affected_sum += sum
            else:
                not_affected_sum += sum

        # Affected field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard), affected_sum)

        # Not affected field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 1, not_affected_sum)

        # Not exposed field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 2, not_exposed_sum)

        # Total field
        analysis.changeAttributeValue(
            area.id(), shift + len(unique_hazard) + 3, total)

        # Any absolute postprocessors
        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                all='all'
            )
            analysis.changeAttributeValue(
                area.id(), shift + len(unique_hazard) + 4 + i, value)

    # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is not
    # enough. ET 13/02/17
    # total_computed = (
    #     affected_sum + not_affected_sum + not_exposed_sum)
    # if not -1 < (total_computed - total) < 1:
    #     raise ComputationError

    analysis.commitChanges()

    analysis.keywords['title'] = layer_purpose_analysis_impacted['name']
    if qgis_version() >= 21600:
        analysis.setName(analysis.keywords['title'])
    else:
        analysis.setLayerName(analysis.keywords['title'])
    analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key']

    check_layer(analysis)
    return analysis
示例#23
0
    def _create_exposure_combos(self):
        """Create one combobox for each exposure and insert them in the UI."""
        # Map registry may be invalid if QGIS is shutting down
        project = QgsProject.instance()
        canvas_layers = self.iface.mapCanvas().layers()
        # MapLayers returns a QMap<QString id, QgsMapLayer layer>
        layers = list(project.mapLayers().values())
        # Sort by name for tests
        layers.sort(key=lambda x: x.name())

        show_only_visible_layers = setting('visibleLayersOnlyFlag',
                                           expected_type=bool)

        # For issue #618
        if len(layers) == 0:
            # self.message_viewer.setHtml(getting_started_message())
            return

        for one_exposure in exposure_all:
            label = QLabel(one_exposure['name'])
            combo = QComboBox()
            combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
            combo.addItem(tr('Do not use'), None)
            self.form_layout.addRow(label, combo)
            self.combos_exposures[one_exposure['key']] = combo

        for layer in layers:
            if (show_only_visible_layers and (layer not in canvas_layers)):
                continue

            try:
                layer_purpose = self.keyword_io.read_keywords(
                    layer, 'layer_purpose')
                keyword_version = str(
                    self.keyword_io.read_keywords(layer,
                                                  inasafe_keyword_version_key))
                if not is_keyword_version_supported(keyword_version):
                    continue
            except BaseException:  # pylint: disable=W0702
                # continue ignoring this layer
                continue

            # See if there is a title for this layer, if not,
            # fallback to the layer's filename
            # noinspection PyBroadException
            try:
                title = self.keyword_io.read_keywords(layer, 'title')
            except (NoKeywordsFoundError, KeywordNotFoundError,
                    MetadataReadError):
                # Skip if there are no keywords at all, or missing keyword
                continue
            except BaseException:  # pylint: disable=W0702
                pass
            else:
                # Lookup internationalised title if available
                title = self.tr(title)

            # Register title with layer
            set_layer_from_title = setting('set_layer_from_title_flag', True,
                                           bool)
            if title and set_layer_from_title:
                if qgis_version() >= 21800:
                    layer.setName(title)
                else:
                    # QGIS 2.14
                    layer.setLayerName(title)

            source = layer.id()

            icon = layer_icon(layer)

            if layer_purpose == layer_purpose_hazard['key']:
                add_ordered_combo_item(self.cbx_hazard,
                                       title,
                                       source,
                                       icon=icon)
            elif layer_purpose == layer_purpose_aggregation['key']:
                if self.use_selected_only:
                    count_selected = layer.selectedFeatureCount()
                    if count_selected > 0:
                        add_ordered_combo_item(self.cbx_aggregation,
                                               title,
                                               source,
                                               count_selected,
                                               icon=icon)
                    else:
                        add_ordered_combo_item(self.cbx_aggregation, title,
                                               source, None, icon)
                else:
                    add_ordered_combo_item(self.cbx_aggregation, title, source,
                                           None, icon)
            elif layer_purpose == layer_purpose_exposure['key']:

                # fetching the exposure
                try:
                    exposure_type = self.keyword_io.read_keywords(
                        layer, layer_purpose_exposure['key'])
                except BaseException:  # pylint: disable=W0702
                    # continue ignoring this layer
                    continue

                for key, combo in list(self.combos_exposures.items()):
                    if key == exposure_type:
                        add_ordered_combo_item(combo, title, source, icon=icon)

        self.cbx_aggregation.addItem(entire_area_item_aggregation, None)
        for combo in list(self.combos_exposures.values()):
            combo.currentIndexChanged.connect(self.validate_impact_function)
示例#24
0
    def test_print_default_template(self):
        """Test printing report to pdf using default template works."""
        impact_layer_path = standard_data_path(
            'impact', 'population_affected_entire_area.shp')
        layer, _ = load_layer(impact_layer_path)
        # noinspection PyUnresolvedReferences,PyArgumentList
        QgsMapLayerRegistry.instance().addMapLayer(layer)
        # noinspection PyCallingNonCallable
        rect = QgsRectangle(106.8194, -6.2108, 106.8201, -6.1964)
        CANVAS.setExtent(rect)
        CANVAS.refresh()

        template = resources_path(
            'qgis-composer-templates', 'a4-portrait-blue.qpt')
        report = ImpactReport(IFACE, template, layer)
        out_path = unique_filename(
            prefix='map_default_template_test',
            suffix='.pdf',
            dir=temp_dir('test'))
        report.print_map_to_pdf(out_path)

        # Check the file exists
        message = 'Rendered output does not exist: %s' % out_path
        self.assertTrue(os.path.exists(out_path), message)

        # Check the file is not corrupt
        message = 'The output file %s is corrupt' % out_path
        out_size = os.stat(out_path).st_size
        self.assertTrue(out_size > 0, message)

        # Check the components in composition are default components
        if qgis_version() < 20500:
            safe_logo = report.composition.getComposerItemById(
                'inasafe-logo').pictureFile()
            north_arrow = report.composition.getComposerItemById(
                'north-arrow').pictureFile()
            org_logo = report.composition.getComposerItemById(
                'organisation-logo').pictureFile()
        else:
            safe_logo = report.composition.getComposerItemById(
                'white-inasafe-logo').picturePath()
            north_arrow = report.composition.getComposerItemById(
                'north-arrow').picturePath()
            org_logo = report.composition.getComposerItemById(
                'organisation-logo').picturePath()

        expected_safe_logo = resources_path(
            'img', 'logos', 'inasafe-logo-url-white.svg')
        expected_north_arrow = resources_path(
            'img', 'north_arrows', 'simple_north_arrow.png')
        expected_org_logo = resources_path('img', 'logos', 'supporters.png')

        message = (
            'The safe logo path is not the default one: %s isn\'t %s' %
            (expected_safe_logo, safe_logo))
        self.assertEqual(expected_safe_logo, safe_logo, message)

        message = 'The north arrow path is not the default one'
        self.assertEqual(expected_north_arrow, north_arrow, message)

        message = 'The organisation logo path is not the default one'
        self.assertEqual(expected_org_logo, org_logo, message)
示例#25
0
    def print_impact_table(self, output_path):
        """Pint summary from impact layer to PDF.

        ..note:: The order of the report:
            1. Summary table
            2. Aggregation table
            3. Attribution table

        :param output_path: Output path.
        :type output_path: str

        :return: Path to generated pdf file.
        :rtype: str

        :raises: None
        """
        keywords = self._keyword_io.read_keywords(self.layer)

        if output_path is None:
            output_path = unique_filename(suffix='.pdf', dir=temp_dir())

        summary_table = keywords.get('impact_summary', None)
        full_table = keywords.get('impact_table', None)
        aggregation_table = keywords.get('postprocessing_report', None)
        attribution_table = impact_attribution(keywords)

        # (AG) We will not use impact_table as most of the IF use that as:
        # impact_table = impact_summary + some information intended to be
        # shown on screen (see FloodOsmBuilding)
        # Unless the impact_summary is None, we will use impact_table as the
        # alternative
        html = LOGO_ELEMENT.to_html()
        html += m.Heading(tr('Analysis Results'), **INFO_STYLE).to_html()
        if summary_table is None:
            html += full_table
        else:
            html += summary_table

        if aggregation_table is not None:
            html += aggregation_table

        if attribution_table is not None:
            html += attribution_table.to_html()

        html = html_header() + html + html_footer()

        # Print HTML using composition
        # For QGIS < 2.4 compatibility
        # QgsMapSettings is added in 2.4
        if qgis_version() < 20400:
            map_settings = QgsMapRenderer()
        else:
            map_settings = QgsMapSettings()

        # A4 Portrait
        paper_width = 210
        paper_height = 297

        # noinspection PyCallingNonCallable
        composition = QgsComposition(map_settings)
        # noinspection PyUnresolvedReferences
        composition.setPlotStyle(QgsComposition.Print)
        composition.setPaperSize(paper_width, paper_height)
        composition.setPrintResolution(300)

        # Add HTML Frame
        # noinspection PyCallingNonCallable
        html_item = QgsComposerHtml(composition, False)
        margin_left = 10
        margin_top = 10

        # noinspection PyCallingNonCallable
        html_frame = QgsComposerFrame(
            composition,
            html_item,
            margin_left,
            margin_top,
            paper_width - 2 * margin_left,
            paper_height - 2 * margin_top)
        html_item.addFrame(html_frame)

        # Set HTML
        # From QGIS 2.6, we can set composer HTML with manual HTML
        if qgis_version() < 20600:
            html_path = unique_filename(
                prefix='report', suffix='.html', dir=temp_dir())
            html_to_file(html, file_path=html_path)
            html_url = QUrl.fromLocalFile(html_path)
            html_item.setUrl(html_url)
        else:
            # noinspection PyUnresolvedReferences
            html_item.setContentMode(QgsComposerHtml.ManualHtml)
            # noinspection PyUnresolvedReferences
            html_item.setResizeMode(QgsComposerHtml.RepeatUntilFinished)
            html_item.setHtml(html)
            html_item.loadHtml()

        composition.exportAsPDF(output_path)
        return output_path
示例#26
0
    def get_layer_description_from_browser(self, category):
        """Obtain the description of the browser layer selected by user.

        :param category: The category of the layer to get the description.
        :type category: string

        :returns: Tuple of boolean and string. Boolean is true if layer is
            validated as compatible for current role (impact function and
            category) and false otherwise. String contains a description
            of the selected layer or an error message.
        :rtype: tuple
        """

        if category == 'hazard':
            browser = self.tvBrowserHazard
        elif category == 'exposure':
            browser = self.tvBrowserExposure
        elif category == 'aggregation':
            browser = self.tvBrowserAggregation
        else:
            raise InaSAFEError

        index = browser.selectionModel().currentIndex()
        if not index:
            return False, ''

        # Map the proxy model index to the source model index
        index = browser.model().mapToSource(index)
        item = browser.model().sourceModel().dataItem(index)
        if not item:
            return False, ''

        item_class_name = item.metaObject().className()
        # if not itemClassName.endswith('LayerItem'):
        if not item.type() == QgsDataItem.Layer:
            if item_class_name == 'QgsPGRootItem' and not item.children():
                return False, create_postGIS_connection_first
            else:
                return False, ''

        if item_class_name not in [
                'QgsOgrLayerItem', 'QgsGdalLayerItem', 'QgsPGLayerItem',
                'QgsLayerItem', ]:
            return False, ''

        path = item.path()

        if item_class_name in ['QgsOgrLayerItem', 'QgsGdalLayerItem',
                               'QgsLayerItem'] and not os.path.exists(path):
            return False, ''

        # try to create the layer
        if item_class_name == 'QgsOgrLayerItem':
            layer = QgsVectorLayer(path, '', 'ogr')
        elif item_class_name == 'QgsPGLayerItem':
            uri = self.postgis_path_to_uri(path)
            if uri:
                layer = QgsVectorLayer(uri.uri(), uri.table(), 'postgres')
            else:
                layer = None
        else:
            layer = QgsRasterLayer(path, '', 'gdal')

        if not layer or not layer.isValid():
            return False, self.tr('Not a valid layer.')

        try:
            keywords = self.keyword_io.read_keywords(layer)
            if ('layer_purpose' not in keywords and
                    'impact_summary' not in keywords):
                keywords = None
        except (HashNotFoundError,
                OperationalError,
                NoKeywordsFoundError,
                KeywordNotFoundError,
                InvalidParameterError,
                UnsupportedProviderError,
                MissingMetadata):
            keywords = None

        # set the layer name for further use in the step_fc_summary
        if keywords:
            if qgis_version() >= 21800:
                layer.setName(keywords.get('title'))
            else:
                layer.setLayerName(keywords.get('title'))

        if not self.parent.is_layer_compatible(layer, category, keywords):
            label_text = '%s<br/>%s' % (
                self.tr(
                    'This layer\'s keywords or type are not suitable:'),
                self.unsuitable_layer_description_html(
                    layer, category, keywords))
            return False, label_text

        # set the current layer (e.g. for the keyword creation sub-thread
        #                          or for adding the layer to mapCanvas)
        self.parent.layer = layer
        if category == 'hazard':
            self.parent.hazard_layer = layer
        elif category == 'exposure':
            self.parent.exposure_layer = layer
        else:
            self.parent.aggregation_layer = layer

        # Check if the layer is keywordless
        if keywords and 'keyword_version' in keywords:
            kw_ver = str(keywords['keyword_version'])
            self.parent.is_selected_layer_keywordless = (
                not is_keyword_version_supported(kw_ver))
        else:
            self.parent.is_selected_layer_keywordless = True

        desc = layer_description_html(layer, keywords)
        return True, desc
def multi_exposure_analysis_summary(analysis, intermediate_analysis):
    """Merge intermediate analysis into one analysis summary.

    List of analysis layers like:
    | analysis_id | count_hazard_class | affected_count | total |

    Target layer :
    | analysis_id |

    Output layer :
    | analysis_id | count_hazard_class | affected_count | total |

    :param analysis: The target vector layer where to write statistics.
    :type analysis: QgsVectorLayer

    :param intermediate_analysis: List of analysis layer for a single exposure.
    :type intermediate_analysis: list

    :return: The new target layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.3
    """
    analysis.startEditing()

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    target_id = next(analysis.getFeatures(request)).id()

    for analysis_result in intermediate_analysis:
        exposure = analysis_result.keywords['exposure_keywords']['exposure']

        iterator = analysis_result.getFeatures(request)
        feature = next(iterator)

        source_fields = analysis_result.keywords['inasafe_fields']

        # Dynamic fields
        hazards = read_dynamic_inasafe_field(source_fields, hazard_count_field)
        for hazard_zone in hazards:
            field = create_field_from_definition(exposure_hazard_count_field,
                                                 exposure, hazard_zone)
            analysis.addAttribute(field)
            index = analysis.fieldNameIndex(field.name())
            value = feature[analysis_result.fieldNameIndex(
                hazard_count_field['field_name'] % hazard_zone)]
            analysis.changeAttributeValue(target_id, index, value)
            # keywords
            key = exposure_hazard_count_field['key'] % (exposure, hazard_zone)
            value = exposure_hazard_count_field['field_name'] % (exposure,
                                                                 hazard_zone)
            analysis.keywords['inasafe_fields'][key] = value

        # total affected
        source_index = analysis_result.fieldNameIndex(
            total_affected_field['field_name'])
        field = create_field_from_definition(exposure_total_affected_field,
                                             exposure)
        analysis.addAttribute(field)
        index = analysis.fieldNameIndex(field.name())
        analysis.changeAttributeValue(target_id, index, feature[source_index])
        # keywords
        key = exposure_total_affected_field['key'] % exposure
        value = exposure_total_affected_field['field_name'] % exposure
        analysis.keywords['inasafe_fields'][key] = value

        # total not affected
        source_index = analysis_result.fieldNameIndex(
            total_not_affected_field['field_name'])
        field = create_field_from_definition(exposure_total_not_affected_field,
                                             exposure)
        analysis.addAttribute(field)
        index = analysis.fieldNameIndex(field.name())
        analysis.changeAttributeValue(target_id, index, feature[source_index])
        # keywords
        key = exposure_total_not_affected_field['key'] % exposure
        value = exposure_total_not_affected_field['field_name'] % exposure
        analysis.keywords['inasafe_fields'][key] = value

        # total exposed
        source_index = analysis_result.fieldNameIndex(
            total_exposed_field['field_name'])
        field = create_field_from_definition(exposure_total_exposed_field,
                                             exposure)
        analysis.addAttribute(field)
        index = analysis.fieldNameIndex(field.name())
        analysis.changeAttributeValue(target_id, index, feature[source_index])
        # keywords
        key = exposure_total_exposed_field['key'] % exposure
        value = exposure_total_exposed_field['field_name'] % exposure
        analysis.keywords['inasafe_fields'][key] = value

        # total not exposed
        source_index = analysis_result.fieldNameIndex(
            total_not_exposed_field['field_name'])
        field = create_field_from_definition(exposure_total_not_exposed_field,
                                             exposure)
        analysis.addAttribute(field)
        index = analysis.fieldNameIndex(field.name())
        analysis.changeAttributeValue(target_id, index, feature[source_index])
        # keywords
        key = exposure_total_not_exposed_field['key'] % exposure
        value = exposure_total_not_exposed_field['field_name'] % exposure
        analysis.keywords['inasafe_fields'][key] = value

        # total
        source_index = analysis_result.fieldNameIndex(
            total_field['field_name'])
        field = create_field_from_definition(exposure_total_field, exposure)
        analysis.addAttribute(field)
        index = analysis.fieldNameIndex(field.name())
        analysis.changeAttributeValue(target_id, index, feature[source_index])
        # keywords
        key = exposure_total_field['key'] % exposure
        value = exposure_total_field['field_name'] % exposure
        analysis.keywords['inasafe_fields'][key] = value

    analysis.commitChanges()
    analysis.keywords['title'] = (
        layer_purpose_analysis_impacted['multi_exposure_name'])
    analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key']

    # Set up the extra keywords so everyone knows it's a
    # multi exposure analysis result.
    extra_keywords = {
        extra_keyword_analysis_type['key']: MULTI_EXPOSURE_ANALYSIS_FLAG
    }
    analysis.keywords['extra_keywords'] = extra_keywords

    if qgis_version() >= 21600:
        analysis.setName(analysis.keywords['title'])
    else:
        analysis.setLayerName(analysis.keywords['title'])
    return analysis
示例#28
0
    def load_template(self, map_settings):
        """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 map_settings: Map settings.
        :type map_settings: QgsMapSettings, QgsMapRenderer

        """
        # Create Composition
        template_composition = TemplateComposition(self.template_path,
                                                   map_settings)

        # Validate the component in the template
        component_ids = [
            'impact-map', 'safe-logo', 'summary-report', 'aggregation-area',
            'map-scale', 'map-legend', 'organisation-logo',
            'merged-report-table'
        ]
        template_composition.component_ids = component_ids
        if len(template_composition.missing_elements) > 0:
            raise ReportCreationError(
                self.tr('Components: %s could not be found' %
                        ', '.join(template_composition.missing_elements)))

        # Prepare map substitution and set to composition
        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
        }
        template_composition.substitution = substitution_map

        # Load Template
        try:
            template_composition.load_template()
        except TemplateLoadingError:
            raise

        # Draw Composition
        # Set InaSAFE logo
        composition = template_composition.composition
        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')

        if qgis_version() < 20400:
            layers = map_settings.layerSet()
        else:
            layers = map_settings.layers()

        if qgis_version() < 20600:
            legend.model().setLayerSet(layers)
            legend.synchronizeWithModel()
        else:
            root_group = legend.modelV2().rootGroup()

            layer_ids = map_settings.layers()
            for layer_id in layer_ids:
                # noinspection PyUnresolvedReferences
                layer = QgsMapLayerRegistry.instance().mapLayer(layer_id)
                root_group.addLayer(layer)
            legend.synchronizeWithModel()

        return composition
示例#29
0
def download(feature_type,
             output_base_path,
             extent,
             progress_dialog=None,
             server_url=None):
    """Download shapefiles from Kartoza server.

    .. versionadded:: 3.2

    :param feature_type: What kind of features should be downloaded.
        Currently 'buildings', 'building-points' or 'roads' are supported.
    :type feature_type: str

    :param output_base_path: The base path of the shape file.
    :type output_base_path: str

    :param extent: A list in the form [xmin, ymin, xmax, ymax] where all
    coordinates provided are in Geographic / EPSG:4326.
    :type extent: list

    :param progress_dialog: A progress dialog.
    :type progress_dialog: QProgressDialog

    :param server_url: The server URL to use.
    :type: basestring

    :raises: ImportDialogError, CanceledImportDialogError
    """
    if not server_url:
        server_url = PRODUCTION_SERVER

    # preparing necessary data
    min_longitude = extent[0]
    min_latitude = extent[1]
    max_longitude = extent[2]
    max_latitude = extent[3]

    box = ('{min_longitude},{min_latitude},{max_longitude},'
           '{max_latitude}').format(min_longitude=min_longitude,
                                    min_latitude=min_latitude,
                                    max_longitude=max_longitude,
                                    max_latitude=max_latitude)

    url = ('{url_osm_prefix}'
           '{feature_type}'
           '{url_osm_suffix}?'
           'bbox={box}&'
           'qgis_version={qgis}&'
           'lang={lang}&'
           'inasafe_version={inasafe_version}'.format(
               url_osm_prefix=server_url,
               feature_type=feature_type,
               url_osm_suffix=URL_OSM_SUFFIX,
               box=box,
               qgis=qgis_version(),
               lang=locale(),
               inasafe_version=get_version()))

    path = tempfile.mktemp('.shp.zip')

    # download and extract it
    fetch_zip(url, path, feature_type, progress_dialog)
    extract_zip(path, output_base_path)

    if progress_dialog:
        progress_dialog.done(QDialog.Accepted)
def exposure_summary_table(aggregate_hazard,
                           exposure_summary=None,
                           callback=None):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | exposure_count |

    Output layer :
    | exp_type | count_hazard_class | total |

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param exposure_summary: The layer impact layer.
    :type exposure_summary: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new tabular table, without geometry.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_4_exposure_summary_table_steps[
        'output_layer_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']

    source_compulsory_fields = [
        aggregation_id_field, aggregation_name_field, hazard_id_field,
        hazard_class_field, affected_field, total_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    absolute_values = create_absolute_values_structure(aggregate_hazard,
                                                       ['all'])

    hazard_class = source_fields[hazard_class_field['key']]
    hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class)
    unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index)

    unique_exposure = read_dynamic_inasafe_field(source_fields,
                                                 exposure_count_field)

    flat_table = FlatTable('hazard_class', 'exposure_class')

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregate_hazard.getFeatures():
        hazard_value = area[hazard_class_index]
        for exposure in unique_exposure:
            key_name = exposure_count_field['key'] % exposure
            field_name = source_fields[key_name]
            exposure_count = area[field_name]
            if not exposure_count:
                exposure_count = 0

            flat_table.add_value(exposure_count,
                                 hazard_class=hazard_value,
                                 exposure_class=exposure)

        # We summarize every absolute values.
        for field, field_definition in list(absolute_values.items()):
            value = area[field]
            if not value:
                value = 0
            field_definition[0].add_value(value, all='all')

    tabular = create_memory_layer(output_layer_name, QgsWkbTypes.NullGeometry)
    tabular.startEditing()

    field = create_field_from_definition(exposure_type_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][exposure_type_field['key']] = (
        exposure_type_field['field_name'])

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    hazard = hazard_keywords['hazard']
    classification = hazard_keywords['classification']

    exposure_keywords = aggregate_hazard.keywords['exposure_keywords']
    exposure = exposure_keywords['exposure']

    hazard_affected = {}
    for hazard_class in unique_hazard:
        if (hazard_class == '' or hazard_class is None or
            (hasattr(hazard_class, 'isNull') and hazard_class.isNull())):
            hazard_class = 'NULL'
        field = create_field_from_definition(hazard_count_field, hazard_class)
        tabular.addAttribute(field)
        key = hazard_count_field['key'] % hazard_class
        value = hazard_count_field['field_name'] % hazard_class
        tabular.keywords['inasafe_fields'][key] = value

        hazard_affected[hazard_class] = post_processor_affected_function(
            exposure=exposure,
            hazard=hazard,
            classification=classification,
            hazard_class=hazard_class)

    field = create_field_from_definition(total_affected_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_affected_field['key']] = (
        total_affected_field['field_name'])

    # essentially have the same value as NULL_hazard_count
    # but with this, make sure that it exists in layer so it can be used for
    # reporting, and can be referenced to fields.py to take the label.
    field = create_field_from_definition(total_not_affected_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_not_affected_field['key']] = (
        total_not_affected_field['field_name'])

    field = create_field_from_definition(total_not_exposed_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_not_exposed_field['key']] = (
        total_not_exposed_field['field_name'])

    field = create_field_from_definition(total_field)
    tabular.addAttribute(field)
    tabular.keywords['inasafe_fields'][total_field['key']] = (
        total_field['field_name'])

    summarization_dicts = {}
    if exposure_summary:
        summarization_dicts = summarize_result(exposure_summary)

    sorted_keys = sorted(summarization_dicts.keys())

    for key in sorted_keys:
        summary_field = summary_rules[key]['summary_field']
        field = create_field_from_definition(summary_field)
        tabular.addAttribute(field)
        tabular.keywords['inasafe_fields'][summary_field['key']] = (
            summary_field['field_name'])

    # Only add absolute value if there is no summarization / no exposure
    # classification
    if not summarization_dicts:
        # For each absolute values
        for absolute_field in list(absolute_values.keys()):
            field_definition = definition(absolute_values[absolute_field][1])
            field = create_field_from_definition(field_definition)
            tabular.addAttribute(field)
            key = field_definition['key']
            value = field_definition['field_name']
            tabular.keywords['inasafe_fields'][key] = value

    for exposure_type in unique_exposure:
        feature = QgsFeature()
        attributes = [exposure_type]
        total_affected = 0
        total_not_affected = 0
        total_not_exposed = 0
        total = 0
        for hazard_class in unique_hazard:
            if hazard_class == '' or hazard_class is None:
                hazard_class = 'NULL'
            value = flat_table.get_value(hazard_class=hazard_class,
                                         exposure_class=exposure_type)
            attributes.append(value)

            if hazard_affected[hazard_class] == not_exposed_class['key']:
                total_not_exposed += value
            elif hazard_affected[hazard_class]:
                total_affected += value
            else:
                total_not_affected += value

            total += value

        attributes.append(total_affected)
        attributes.append(total_not_affected)
        attributes.append(total_not_exposed)
        attributes.append(total)

        if summarization_dicts:
            for key in sorted_keys:
                attributes.append(summarization_dicts[key].get(
                    exposure_type, 0))
        else:
            for i, field in enumerate(absolute_values.values()):
                value = field[0].get_value(all='all')
                attributes.append(value)

        feature.setAttributes(attributes)
        tabular.addFeature(feature)

        # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is
        # not enough. ET 13/02/17
        # total_computed = (
        #     total_affected + total_not_affected + total_not_exposed)
        # if not -1 < (total_computed - total) < 1:
        #     raise ComputationError

    tabular.commitChanges()

    tabular.keywords['title'] = layer_purpose_exposure_summary_table['name']
    if qgis_version() >= 21800:
        tabular.setName(tabular.keywords['title'])
    else:
        tabular.setLayerName(tabular.keywords['title'])
    tabular.keywords['layer_purpose'] = layer_purpose_exposure_summary_table[
        'key']

    check_layer(tabular, has_geometry=False)
    return tabular
示例#31
0
def analysis_summary(aggregate_hazard, analysis, callback=None):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | analysis_id |

    Output layer :
    | analysis_id | count_hazard_class | affected_count | total |

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param analysis: The target vector layer where to write statistics.
    :type analysis: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new target layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_3_analysis_steps['output_layer_name']
    processing_step = summary_3_analysis_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = analysis.keywords['inasafe_fields']

    target_compulsory_fields = [
        analysis_id_field,
        analysis_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        aggregation_id_field, aggregation_name_field, hazard_id_field,
        hazard_class_field, total_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    absolute_values = create_absolute_values_structure(aggregate_hazard,
                                                       ['all'])

    hazard_class = source_fields[hazard_class_field['key']]
    hazard_class_index = aggregate_hazard.fieldNameIndex(hazard_class)
    unique_hazard = aggregate_hazard.uniqueValues(hazard_class_index)

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    total = source_fields[total_field['key']]

    flat_table = FlatTable('hazard_class')

    # First loop over the aggregate_hazard layer
    request = QgsFeatureRequest()
    request.setSubsetOfAttributes([hazard_class, total],
                                  aggregate_hazard.fields())
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregate_hazard.getFeatures():
        hazard_value = area[hazard_class_index]
        value = area[total]
        if not value or isinstance(value, QPyNullVariant) or isnan(value):
            # For isnan, see ticket #3812
            value = 0
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = 'NULL'
        flat_table.add_value(value, hazard_class=hazard_value)

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(value, all='all')

    analysis.startEditing()

    shift = analysis.fields().count()

    counts = [
        total_affected_field, total_not_affected_field,
        total_not_exposed_field, total_field
    ]

    add_fields(analysis, absolute_values, counts, unique_hazard,
               hazard_count_field)

    affected_sum = 0
    not_affected_sum = 0
    not_exposed_sum = 0

    for area in analysis.getFeatures(request):
        total = 0
        for i, val in enumerate(unique_hazard):
            if not val or isinstance(val, QPyNullVariant):
                val = 'NULL'
            sum = flat_table.get_value(hazard_class=val)
            total += sum
            analysis.changeAttributeValue(area.id(), shift + i, sum)

            affected = post_processor_affected_function(
                classification=classification, hazard_class=val)
            if affected == not_exposed_class['key']:
                not_exposed_sum += sum
            elif affected:
                affected_sum += sum
            else:
                not_affected_sum += sum

        # Affected field
        analysis.changeAttributeValue(area.id(), shift + len(unique_hazard),
                                      affected_sum)

        # Not affected field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 1,
                                      not_affected_sum)

        # Not exposed field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 2,
                                      not_exposed_sum)

        # Total field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 3, total)

        # Any absolute postprocessors
        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(all='all')
            analysis.changeAttributeValue(area.id(),
                                          shift + len(unique_hazard) + 4 + i,
                                          value)

    # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is not
    # enough. ET 13/02/17
    # total_computed = (
    #     affected_sum + not_affected_sum + not_exposed_sum)
    # if not -1 < (total_computed - total) < 1:
    #     raise ComputationError

    analysis.commitChanges()

    analysis.keywords['title'] = layer_purpose_analysis_impacted['name']
    if qgis_version() >= 21600:
        analysis.setName(analysis.keywords['title'])
    else:
        analysis.setLayerName(analysis.keywords['title'])
    analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key']

    check_layer(analysis)
    return analysis
示例#32
0
 def test_get_qgis_version(self):
     """Test we can get the version of QGIS."""
     version = qgis_version()
     message = 'Got version %s of QGIS, but at least 214000 is needed'
     self.assertTrue(version > 21400, message)
示例#33
0
文件: tools.py 项目: timlinux/inasafe
def load_layer(full_layer_uri_string, name=None, provider=None):
    """Helper to load and return a single QGIS layer based on our layer URI.

    :param provider: The provider name to use if known to open the layer.
        Default to None, we will try to guess it, but it's much better if you
        can provide it.
    :type provider:

    :param name: The name of the layer. If not provided, it will be computed
        based on the URI.
    :type name: basestring

    :param full_layer_uri_string: Layer URI, with provider type.
    :type full_layer_uri_string: str

    :returns: tuple containing layer and its layer_purpose.
    :rtype: (QgsMapLayer, str)
    """
    if provider:
        # Cool !
        layer_path = full_layer_uri_string
    else:
        #  Let's check if the driver is included in the path
        layer_path, provider = decode_full_layer_uri(full_layer_uri_string)

        if not provider:
            # Let's try to check if it's file based and look for a extension
            if '|' in layer_path:
                clean_uri = layer_path.split('|')[0]
            else:
                clean_uri = layer_path
            is_file_based = os.path.exists(clean_uri)
            if is_file_based:
                # Extract basename and absolute path
                file_name = os.path.split(layer_path)[
                    -1]  # If path was absolute
                extension = os.path.splitext(file_name)[1]
                if extension in OGR_EXTENSIONS:
                    provider = 'ogr'
                elif extension in GDAL_EXTENSIONS:
                    provider = 'gdal'
                else:
                    provider = None

    if not provider:
        layer = load_layer_without_provider(layer_path)
    else:
        layer = load_layer_with_provider(layer_path, provider)

    if not layer or not layer.isValid():
        message = 'Layer "%s" is not valid' % layer_path
        LOGGER.debug(message)
        raise InvalidLayerError(message)

    # Define the name
    if not name:
        source = layer.source()
        if '|' in source:
            clean_uri = source.split('|')[0]
        else:
            clean_uri = source
        is_file_based = os.path.exists(clean_uri)
        if is_file_based:
            # Extract basename and absolute path
            file_name = os.path.split(layer_path)[-1]  # If path was absolute
            name = os.path.splitext(file_name)[0]
        else:
            # Might be a DB, take the DB name
            source = QgsDataSourceUri(source)
            name = source.table()

    if not name:
        name = 'default'

    if qgis_version() >= 21800:
        layer.setName(name)
    else:
        layer.setLayerName(name)

    # update the layer keywords
    monkey_patch_keywords(layer)
    layer_purpose = layer.keywords.get('layer_purpose')

    return layer, layer_purpose
    def test_multi_exposure(self):
        """Test we can run a multi exposure analysis."""
        hazard_layer = load_test_vector_layer('gisv4', 'hazard',
                                              'classified_vector.geojson')
        building_layer = load_test_vector_layer('gisv4', 'exposure',
                                                'building-points.geojson')
        population_layer = load_test_vector_layer('gisv4', 'exposure',
                                                  'population.geojson')
        roads_layer = load_test_vector_layer('gisv4', 'exposure',
                                             'roads.geojson')
        aggregation_layer = load_test_vector_layer('gisv4', 'aggregation',
                                                   'small_grid.geojson')

        impact_function = MultiExposureImpactFunction()
        impact_function.hazard = hazard_layer
        impact_function.exposures = [
            building_layer, population_layer, roads_layer
        ]
        impact_function.aggregation = aggregation_layer

        code, message = impact_function.prepare()
        self.assertEqual(code, PREPARE_SUCCESS, message)

        code, message, exposure = impact_function.run()
        self.assertEqual(code, ANALYSIS_SUCCESS, message)

        # Test provenance
        hazard = definition(hazard_layer.keywords['hazard'])
        # exposure = definition(exposure_layer.keywords['exposure'])
        hazard_category = definition(hazard_layer.keywords['hazard_category'])

        expected_provenance = {
            provenance_gdal_version['provenance_key']:
            gdal.__version__,
            provenance_host_name['provenance_key']:
            gethostname(),
            provenance_user['provenance_key']:
            getpass.getuser(),
            provenance_os['provenance_key']:
            readable_os_version(),
            provenance_pyqt_version['provenance_key']:
            PYQT_VERSION_STR,
            provenance_qgis_version['provenance_key']:
            qgis_version(),
            provenance_qt_version['provenance_key']:
            QT_VERSION_STR,
            provenance_inasafe_version['provenance_key']:
            get_version(),
            provenance_aggregation_layer['provenance_key']:
            aggregation_layer.source(),
            provenance_aggregation_layer_id['provenance_key']:
            aggregation_layer.id(),
            # provenance_exposure_layer['provenance_key']:
            #     exposure_layer.source(),
            # provenance_exposure_layer_id['provenance_key']:
            #     exposure_layer.id(),
            provenance_hazard_layer['provenance_key']:
            hazard_layer.source(),
            provenance_hazard_layer_id['provenance_key']:
            hazard_layer.id(),
            provenance_aggregation_keywords['provenance_key']:
            deepcopy(aggregation_layer.keywords),
            # provenance_exposure_keywords['provenance_key']:
            #     deepcopy(exposure_layer.keywords),
            provenance_hazard_keywords['provenance_key']:
            deepcopy(hazard_layer.keywords),
        }

        self.maxDiff = None

        expected_provenance.update({
            provenance_analysis_extent['provenance_key']:
            impact_function.analysis_extent.exportToWkt(),
            provenance_impact_function_name['provenance_key']:
            impact_function.name,
            # provenance_requested_extent['provenance_key']: impact_function.
            #     requested_extent,
            provenance_data_store_uri['provenance_key']:
            impact_function.datastore.uri_path,
            provenance_start_datetime['provenance_key']:
            impact_function.start_datetime,
            provenance_end_datetime['provenance_key']:
            impact_function.end_datetime,
            provenance_duration['provenance_key']:
            impact_function.duration
        })

        self.assertDictContainsSubset(expected_provenance,
                                      impact_function.provenance)

        output_layer_provenance_keys = [
            provenance_layer_aggregation_summary['provenance_key'],
            provenance_layer_analysis_impacted['provenance_key'],
        ]

        for key in output_layer_provenance_keys:
            self.assertIn(key, impact_function.provenance.keys())

        # Test serialization/deserialization
        output_metadata = impact_function.aggregation_summary.keywords
        new_impact_function = MultiExposureImpactFunction. \
            load_from_output_metadata(output_metadata)
        self.assertEqualImpactFunction(impact_function, new_impact_function)

        # Check the analysis layer id equal with the actual layer
        old_analysis_layer_id = impact_function.provenance[
            provenance_layer_analysis_impacted['provenance_key']]
        new_analysis_layer_id = new_impact_function.provenance[
            provenance_layer_analysis_impacted['provenance_key']]
        self.assertEqual(old_analysis_layer_id, new_analysis_layer_id)
def multi_exposure_aggregation_summary(aggregation, intermediate_layers):
    """Merge intermediate aggregations into one aggregation summary.

    Source layer :
    | aggr_id | aggr_name | count of affected features per exposure type

    Target layer :
    | aggregation_id | aggregation_name |

    Output layer :
    | aggr_id | aggr_name | count of affected per exposure type for each

    :param aggregation: The target vector layer where to write statistics.
    :type aggregation: QgsVectorLayer

    :param intermediate_layers: List of aggregation layer for a single exposure
    :type intermediate_layers: list

    :return: The new target layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.3
    """
    target_index_field_name = (
        aggregation.keywords['inasafe_fields'][aggregation_id_field['key']])

    aggregation.startEditing()

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for layer in intermediate_layers:
        source_fields = layer.keywords['inasafe_fields']
        exposure = layer.keywords['exposure_keywords']['exposure']
        unique_exposure = read_dynamic_inasafe_field(
            source_fields, affected_exposure_count_field,
            [total_affected_field])
        field_map = {}

        for exposure_class in unique_exposure:
            field = create_field_from_definition(
                exposure_affected_exposure_type_count_field,
                name=exposure,
                sub_name=exposure_class)
            aggregation.addAttribute(field)
            source_field_index = layer.fieldNameIndex(
                affected_exposure_count_field['field_name'] % exposure_class)
            target_field_index = aggregation.fieldNameIndex(field.name())
            field_map[source_field_index] = target_field_index

        # Total affected field
        field = create_field_from_definition(exposure_total_not_affected_field,
                                             exposure)
        aggregation.addAttribute(field)
        source_field_index = layer.fieldNameIndex(
            total_affected_field['field_name'])
        target_field_index = aggregation.fieldNameIndex(field.name())
        field_map[source_field_index] = target_field_index

        # Get Aggregation ID from original feature
        index = (layer.fieldNameIndex(
            source_fields[aggregation_id_field['key']]))

        for source_feature in layer.getFeatures(request):
            target_expression = QgsFeatureRequest()
            target_expression.setFlags(QgsFeatureRequest.NoGeometry)
            expression = '\"{field_name}\" = {id_value}'.format(
                field_name=target_index_field_name,
                id_value=source_feature[index])
            target_expression.setFilterExpression(expression)
            iterator = aggregation.getFeatures(target_expression)
            target_feature = next(iterator)  # It must return only 1 feature.

            for source_field, target_field in field_map.iteritems():
                aggregation.changeAttributeValue(target_feature.id(),
                                                 target_field,
                                                 source_feature[source_field])

            try:
                next(iterator)
            except StopIteration:
                # Everything is fine, it's normal.
                pass
            else:
                # This should never happen ! IDs are duplicated in the
                # aggregation layer.
                raise Exception(
                    'Aggregation IDs are duplicated in the aggregation layer. '
                    'We can\'t make any joins.')

    aggregation.commitChanges()
    aggregation.keywords['title'] = (
        layer_purpose_aggregation_summary['multi_exposure_name'])
    aggregation.keywords['layer_purpose'] = (
        layer_purpose_aggregation_summary['key'])

    # Set up the extra keywords so everyone knows it's a
    # multi exposure analysis result.
    extra_keywords = {
        extra_keyword_analysis_type['key']: MULTI_EXPOSURE_ANALYSIS_FLAG
    }
    aggregation.keywords['extra_keywords'] = extra_keywords

    if qgis_version() >= 21600:
        aggregation.setName(aggregation.keywords['title'])
    else:
        aggregation.setLayerName(aggregation.keywords['title'])
    return aggregation
示例#36
0
def zonal_stats(raster, vector):
    """Reclassify a continuous raster layer.

    Issue https://github.com/inasafe/inasafe/issues/3190

    The algorithm will take care about projections.
    We don't want to reproject the raster layer.
    So if CRS are different, we reproject the vector layer and then we do a
    lookup from the reprojected layer to the original vector layer.

    :param raster: The raster layer.
    :type raster: QgsRasterLayer

    :param vector: The vector layer.
    :type vector: QgsVectorLayer

    :return: The output of the zonal stats.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = zonal_stats_steps['output_layer_name']

    exposure = raster.keywords['exposure']
    if raster.crs().authid() != vector.crs().authid():
        layer = reproject(vector, raster.crs())

        # We prepare the copy
        output_layer = create_memory_layer(output_layer_name,
                                           vector.geometryType(), vector.crs(),
                                           vector.fields())
        copy_layer(vector, output_layer)
    else:
        layer = create_memory_layer(output_layer_name, vector.geometryType(),
                                    vector.crs(), vector.fields())
        copy_layer(vector, layer)

    input_band = layer.keywords.get('active_band', 1)
    analysis = QgsZonalStatistics(layer, raster, 'exposure_', input_band,
                                  QgsZonalStatistics.Sum)
    result = analysis.calculateStatistics(None)
    LOGGER.debug(tr('Zonal stats on %s : %s' % (raster.source(), result)))

    output_field = exposure_count_field['field_name'] % exposure
    if raster.crs().authid() != vector.crs().authid():
        output_layer.startEditing()
        field = create_field_from_definition(exposure_count_field, exposure)

        output_layer.addAttribute(field)
        new_index = output_layer.fields().lookupField(field.name())
        old_index = layer.fields().lookupField('exposure_sum')
        for feature_input, feature_output in zip(layer.getFeatures(),
                                                 output_layer.getFeatures()):
            output_layer.changeAttributeValue(feature_input.id(), new_index,
                                              feature_input[old_index])
        output_layer.commitChanges()
        layer = output_layer
    else:
        fields_to_rename = {'exposure_sum': output_field}
        if qgis_version() >= 21600:
            rename_fields(layer, fields_to_rename)
        else:
            copy_fields(layer, fields_to_rename)
            remove_fields(layer, list(fields_to_rename.keys()))
        layer.commitChanges()

    # The zonal stats is producing some None values. We need to fill these
    # with 0. See issue : #3778
    # We should start a new editing session as previous fields need to be
    # committed first.
    layer.startEditing()
    request = QgsFeatureRequest()
    expression = '\"%s\" is None' % output_field
    request.setFilterExpression(expression)
    request.setFlags(QgsFeatureRequest.NoGeometry)
    index = layer.fields().lookupField(output_field)
    for feature in layer.getFeatures():
        if feature[output_field] is None:
            layer.changeAttributeValue(feature.id(), index, 0)
    layer.commitChanges()

    layer.keywords = raster.keywords.copy()
    layer.keywords['inasafe_fields'] = vector.keywords['inasafe_fields'].copy()
    layer.keywords['inasafe_default_values'] = (
        raster.keywords['inasafe_default_values'].copy())

    key = exposure_count_field['key'] % raster.keywords['exposure']

    # Special case here, one field is the exposure count and the total.
    layer.keywords['inasafe_fields'][key] = output_field
    layer.keywords['inasafe_fields'][total_field['key']] = output_field

    layer.keywords['exposure_keywords'] = raster.keywords.copy()
    layer.keywords['hazard_keywords'] = vector.keywords[
        'hazard_keywords'].copy()
    layer.keywords['aggregation_keywords'] = (
        vector.keywords['aggregation_keywords'])
    layer.keywords['layer_purpose'] = (
        layer_purpose_aggregate_hazard_impacted['key'])

    layer.keywords['title'] = output_layer_name

    check_layer(layer)
    return layer
示例#37
0
    def generate_reports(self):
        """Generate PDF reports for each aggregation unit using map composer.

        First the report template is loaded with the renderer from two
        impact layers. After it's loaded, if it is not aggregated then
        we just use composition to produce report. Since there are two
        impact maps here, we need to set a new extent for these impact maps
        by a simple calculation.

        If it is not aggregated then we use a powerful QGIS atlas generation
        on composition. Since we save each report table representing each
        aggregated area on self.html_report (which is a dictionary with the
        aggregation area name as a key and its path as a value), and we set
        the aggregation area name as current filename on atlas generation,
        we can match these two so that we have the right report table for
        each report.

        For those two cases, we use the same template. The report table is
        basically an HTML frame. Of course after the merging process is done,
        we delete each report table on self.html_reports physically on disk.
        """
        # Set the layer set
        layer_set = [
            self.first_impact['layer'].id(), self.second_impact['layer'].id()
        ]

        # If aggregated, append chosen aggregation layer
        if not self.entire_area_mode:
            layer_set.append(self.aggregation['layer'].id())

        # Instantiate Map Settings for Composition
        if qgis_version() < 20400:
            map_settings = QgsMapRenderer()
            map_settings.setLayerSet(layer_set)
        else:
            map_settings = QgsMapSettings()
            map_settings.setLayers(layer_set)

        # Create composition
        composition = self.load_template(map_settings)

        # Get Map
        composer_map = composition.getComposerItemById('impact-map')

        # Get HTML Report Frame
        html_report_item = \
            composition.getComposerItemById('merged-report-table')
        html_report_frame = composition.getComposerHtmlByItem(html_report_item)

        if self.entire_area_mode:
            # Get composer map size
            composer_map_width = composer_map.boundingRect().width()
            composer_map_height = composer_map.boundingRect().height()

            # Set the extent from two impact layers to fit into composer map
            composer_size_ratio = float(composer_map_height /
                                        composer_map_width)

            # The extent of two impact layers
            min_x = min(self.first_impact['layer'].extent().xMinimum(),
                        self.second_impact['layer'].extent().xMinimum())
            min_y = min(self.first_impact['layer'].extent().yMinimum(),
                        self.second_impact['layer'].extent().yMinimum())
            max_x = max(self.first_impact['layer'].extent().xMaximum(),
                        self.second_impact['layer'].extent().xMaximum())
            max_y = max(self.first_impact['layer'].extent().yMaximum(),
                        self.second_impact['layer'].extent().yMaximum())
            max_width = max_x - min_x
            max_height = max_y - min_y
            layers_size_ratio = float(max_height / max_width)
            center_x = min_x + float(max_width / 2.0)
            center_y = min_y + float(max_height / 2.0)

            # The extent should fit the composer map size
            new_width = max_width
            new_height = max_height

            # QgsComposerMap only overflows to height, so if it overflows,
            # the extent of the width should be widened
            if layers_size_ratio > composer_size_ratio:
                new_width = max_height / composer_size_ratio

            # Set new extent
            fit_min_x = center_x - (new_width / 2.0)
            fit_max_x = center_x + (new_width / 2.0)
            fit_min_y = center_y - (new_height / 2.0)
            fit_max_y = center_y + (new_height / 2.0)

            # Create the extent and set it to the map
            # noinspection PyCallingNonCallable
            map_extent = QgsRectangle(fit_min_x, fit_min_y, fit_max_x,
                                      fit_max_y)
            composer_map.setNewExtent(map_extent)

            # Add grid to composer map
            split_count = 5
            x_interval = new_width / split_count
            composer_map.setGridIntervalX(x_interval)
            y_interval = new_height / split_count
            composer_map.setGridIntervalY(y_interval)

            # Self.html_reports must have only 1 key value pair
            area_title = list(self.html_reports.keys())[0]

            # Set Report Summary
            summary_report = composition.getComposerItemById('summary-report')
            summary_report.setText(self.summary_report[area_title])

            # Set Aggregation Area Label
            area_label = composition.getComposerItemById('aggregation-area')
            area_label.setText(area_title.title())

            # Set merged-report-table
            html_report_path = self.html_reports[area_title]
            # noinspection PyArgumentList
            html_frame_url = QUrl.fromLocalFile(html_report_path)
            html_report_frame.setUrl(html_frame_url)

            # Export composition to PDF file
            file_name = '_'.join(area_title.split())
            file_path = '%s.pdf' % file_name
            path = os.path.join(self.out_dir, file_path)
            composition.exportAsPDF(path)
        else:
            # Create atlas composition:
            # noinspection PyCallingNonCallable
            atlas = QgsAtlasComposition(composition)

            # Set coverage layer
            # Map will be clipped by features from this layer:
            atlas.setCoverageLayer(self.aggregation['layer'])

            # Add grid to composer map from coverage layer
            split_count = 5
            map_width = self.aggregation['layer'].extent().width()
            map_height = self.aggregation['layer'].extent().height()
            x_interval = map_width / split_count
            composer_map.setGridIntervalX(x_interval)
            y_interval = map_height / split_count
            composer_map.setGridIntervalY(y_interval)

            # Set composer map that will be used for printing atlas
            atlas.setComposerMap(composer_map)

            # set output filename pattern
            atlas.setFilenamePattern(self.aggregation['aggregation_attribute'])

            # Start rendering
            atlas.beginRender()

            # Iterate all aggregation unit in aggregation layer
            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(self.out_dir, file_path)

                # Only print the area that has the report
                area_title = current_filename.lower()
                if area_title in self.summary_report:
                    # Set Report Summary
                    summary_report = composition.getComposerItemById(
                        'summary-report')
                    summary_report.setText(self.summary_report[area_title])

                    # Set Aggregation Area Label
                    area_label = composition.getComposerItemById(
                        'aggregation-area')
                    area_label.setText(area_title.title())

                    # Set merged-report-table
                    html_report_path = self.html_reports[area_title]
                    # noinspection PyArgumentList
                    html_frame_url = QUrl.fromLocalFile(html_report_path)
                    html_report_frame.setUrl(html_frame_url)

                    # Export composition to PDF file
                    composition.exportAsPDF(path)

            # End of rendering
            atlas.endRender()
示例#38
0
    def test_issue71(self):
        """Test issue #71 in github - cbo changes should update ok button."""
        # See https://github.com/AIFDR/inasafe/issues/71
        # Push OK with the left mouse button
        print 'Using QGIS: %s' % qgis_version()
        settings = QtCore.QSettings()
        settings.setValue(
            'inasafe/analysis_extents_mode', 'HazardExposure')
        self.tearDown()
        button = self.dock.pbnRunStop
        # First part of scenario should have enabled run
        file_list = [
            test_data_path('hazard', 'continuous_flood_20_20.asc'),
            test_data_path('exposure', 'pop_binary_raster_20_20.asc')]
        hazard_layer_count, exposure_layer_count = load_layers(file_list)

        message = (
            'Incorrect number of Hazard layers: expected 1 got %s'
            % hazard_layer_count)
        self.assertTrue(hazard_layer_count == 1, message)

        message = (
            'Incorrect number of Exposure layers: expected 1 got %s'
            % exposure_layer_count)
        self.assertTrue(exposure_layer_count == 1, message)

        message = 'Run button was not enabled'
        self.assertTrue(button.isEnabled(), message)

        # Second part of scenario - run disabled when adding invalid layer
        # and select it - run should be disabled
        path = os.path.join(TESTDATA, 'issue71.tif')
        file_list = [path]  # This layer has incorrect keywords
        clear_flag = False
        _, _ = load_layers(file_list, clear_flag)
        # set exposure to : Population Count (5kmx5km)
        # by moving one down
        self.dock.cboExposure.setCurrentIndex(
            self.dock.cboExposure.currentIndex() + 1)
        actual_dict = get_ui_state(self.dock)
        expected_dict = {
            'Run Button Enabled': False,
            'Impact Function Id': '',
            'Impact Function Title': '',
            'Hazard': 'Continuous Flood',
            'Exposure': 'Population Count (5kmx5km)'}
        message = ((
            'Run button was not disabled when exposure set to \n%s'
            '\nUI State: \n%s\nExpected State:\n%s\n%s') % (
                self.dock.cboExposure.currentText(),
                actual_dict,
                expected_dict,
                combos_to_string(self.dock)))

        self.assertTrue(expected_dict == actual_dict, message)

        # Now select again a valid layer and the run button
        # should be enabled
        self.dock.cboExposure.setCurrentIndex(
            self.dock.cboExposure.currentIndex() - 1)
        message = (
            'Run button was not enabled when exposure set to \n%s' %
            self.dock.cboExposure.currentText())
        self.assertTrue(button.isEnabled(), message)
    def generate_reports(self):
        """Generate PDF reports for each aggregation unit using map composer.

        First the report template is loaded with the renderer from two
        impact layers. After it's loaded, if it is not aggregated then
        we just use composition to produce report. Since there are two
        impact maps here, we need to set a new extent for these impact maps
        by a simple calculation.

        If it is not aggregated then we use a powerful QGIS atlas generation
        on composition. Since we save each report table representing each
        aggregated area on self.html_report (which is a dictionary with the
        aggregation area name as a key and its path as a value), and we set
        the aggregation area name as current filename on atlas generation,
        we can match these two so that we have the right report table for
        each report.

        For those two cases, we use the same template. The report table is
        basically an HTML frame. Of course after the merging process is done,
        we delete each report table on self.html_reports physically on disk.
        """
        # Set the layer set
        layer_set = [self.first_impact['layer'].id(),
                     self.second_impact['layer'].id()]

        # If aggregated, append chosen aggregation layer
        if not self.entire_area_mode:
            layer_set.append(self.aggregation['layer'].id())

        # Instantiate Map Settings for Composition
        if qgis_version() < 20400:
            map_settings = QgsMapRenderer()
            map_settings.setLayerSet(layer_set)
        else:
            map_settings = QgsMapSettings()
            map_settings.setLayers(layer_set)

        # Create composition
        composition = self.load_template(map_settings)

        # Get Map
        composer_map = composition.getComposerItemById('impact-map')

        # Get HTML Report Frame
        html_report_item = \
            composition.getComposerItemById('merged-report-table')
        html_report_frame = composition.getComposerHtmlByItem(html_report_item)

        if self.entire_area_mode:
            # Get composer map size
            composer_map_width = composer_map.boundingRect().width()
            composer_map_height = composer_map.boundingRect().height()

            # Set the extent from two impact layers to fit into composer map
            composer_size_ratio = float(
                composer_map_height / composer_map_width)

            # The extent of two impact layers
            min_x = min(self.first_impact['layer'].extent().xMinimum(),
                        self.second_impact['layer'].extent().xMinimum())
            min_y = min(self.first_impact['layer'].extent().yMinimum(),
                        self.second_impact['layer'].extent().yMinimum())
            max_x = max(self.first_impact['layer'].extent().xMaximum(),
                        self.second_impact['layer'].extent().xMaximum())
            max_y = max(self.first_impact['layer'].extent().yMaximum(),
                        self.second_impact['layer'].extent().yMaximum())
            max_width = max_x - min_x
            max_height = max_y - min_y
            layers_size_ratio = float(max_height / max_width)
            center_x = min_x + float(max_width / 2.0)
            center_y = min_y + float(max_height / 2.0)

            # The extent should fit the composer map size
            new_width = max_width
            new_height = max_height

            # QgsComposerMap only overflows to height, so if it overflows,
            # the extent of the width should be widened
            if layers_size_ratio > composer_size_ratio:
                new_width = max_height / composer_size_ratio

            # Set new extent
            fit_min_x = center_x - (new_width / 2.0)
            fit_max_x = center_x + (new_width / 2.0)
            fit_min_y = center_y - (new_height / 2.0)
            fit_max_y = center_y + (new_height / 2.0)

            # Create the extent and set it to the map
            # noinspection PyCallingNonCallable
            map_extent = QgsRectangle(
                fit_min_x, fit_min_y, fit_max_x, fit_max_y)
            composer_map.setNewExtent(map_extent)

            # Add grid to composer map
            split_count = 5
            x_interval = new_width / split_count
            composer_map.setGridIntervalX(x_interval)
            y_interval = new_height / split_count
            composer_map.setGridIntervalY(y_interval)

            # Self.html_reports must have only 1 key value pair
            # Rizky: If layer components were aggregated, html_reports will
            # have several key value pairs. So we get the last value because
            # it is for the total/entire area
            area_title = list(self.html_reports.keys())[-1]

            # Set Report Summary
            summary_report = composition.getComposerItemById('summary-report')
            summary_report.setText(self.summary_report[area_title])

            # Set Aggregation Area Label
            area_label = composition.getComposerItemById('aggregation-area')
            area_label.setText(area_title.title())

            # Set merged-report-table
            html_report_path = self.html_reports[area_title]
            # noinspection PyArgumentList
            html_frame_url = QUrl.fromLocalFile(html_report_path)
            html_report_frame.setUrl(html_frame_url)

            # Export composition to PDF file
            file_name = '_'.join(area_title.split())
            file_path = '%s.pdf' % file_name
            path = os.path.join(self.out_dir, file_path)
            composition.exportAsPDF(path)
        else:
            # Create atlas composition:
            # noinspection PyCallingNonCallable
            atlas = QgsAtlasComposition(composition)

            # Set coverage layer
            # Map will be clipped by features from this layer:
            atlas.setCoverageLayer(self.aggregation['layer'])

            # Add grid to composer map from coverage layer
            split_count = 5
            map_width = self.aggregation['layer'].extent().width()
            map_height = self.aggregation['layer'].extent().height()
            x_interval = map_width / split_count
            composer_map.setGridIntervalX(x_interval)
            y_interval = map_height / split_count
            composer_map.setGridIntervalY(y_interval)

            # Set composer map that will be used for printing atlas
            atlas.setComposerMap(composer_map)

            # set output filename pattern
            atlas.setFilenamePattern(
                self.aggregation['aggregation_attribute'])

            # Start rendering
            atlas.beginRender()

            # Iterate all aggregation unit in aggregation layer
            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(self.out_dir, file_path)

                # Only print the area that has the report
                area_title = current_filename.lower()
                if area_title in self.summary_report:
                    # Set Report Summary
                    summary_report = composition.getComposerItemById(
                        'summary-report')
                    summary_report.setText(self.summary_report[area_title])

                    # Set Aggregation Area Label
                    area_label = composition.getComposerItemById(
                        'aggregation-area')
                    area_label.setText(area_title.title())

                    # Set merged-report-table
                    html_report_path = self.html_reports[area_title]
                    # noinspection PyArgumentList
                    html_frame_url = QUrl.fromLocalFile(html_report_path)
                    html_report_frame.setUrl(html_frame_url)

                    # Export composition to PDF file
                    composition.exportAsPDF(path)

            # End of rendering
            atlas.endRender()
示例#40
0
    def draw_composition(self):
        """Draw all the components in the composition."""
        safe_logo = self.composition.getComposerItemById('safe-logo')
        north_arrow = self.composition.getComposerItemById('north-arrow')
        organisation_logo = self.composition.getComposerItemById(
            'organisation-logo')

        if qgis_version() < 20600:
            if safe_logo is not None:
                safe_logo.setPictureFile(self.safe_logo)
            if north_arrow is not None:
                north_arrow.setPictureFile(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPictureFile(self.organisation_logo)
        else:
            if safe_logo is not None:
                safe_logo.setPicturePath(self.safe_logo)
            if north_arrow is not None:
                north_arrow.setPicturePath(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPicturePath(self.organisation_logo)

        # 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)

        # 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
            # noinspection PyCallingNonCallable
            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)

        legend = self.composition.getComposerItemById('impact-legend')
        if legend is not None:
            legend_attributes = self.map_legend_attributes
            legend_title = legend_attributes.get('legend_title', None)

            symbol_count = 1
            # noinspection PyUnresolvedReferences
            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)

            # Set Legend
            # Since QGIS 2.6, legend.model() is obsolete
            if qgis_version() < 20600:
                legend.model().setLayerSet([self.layer.id()])
                legend.synchronizeWithModel()
            else:
                root_group = legend.modelV2().rootGroup()
                root_group.addLayer(self.layer)
                legend.synchronizeWithModel()
示例#41
0
    def draw_composition(self):
        """Draw all the components in the composition."""
        safe_logo = self.composition.getComposerItemById('safe-logo')
        north_arrow = self.composition.getComposerItemById('north-arrow')
        organisation_logo = self.composition.getComposerItemById(
            'organisation-logo')

        if qgis_version() < 20600:
            if safe_logo is not None:
                safe_logo.setPictureFile(self.safe_logo)
            if north_arrow is not None:
                north_arrow.setPictureFile(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPictureFile(self.organisation_logo)
        else:
            if safe_logo is not None:
                safe_logo.setPicturePath(self.safe_logo)
            if north_arrow is not None:
                north_arrow.setPicturePath(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPicturePath(self.organisation_logo)

        # 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)

        # 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
            # noinspection PyCallingNonCallable
            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)

        legend = self.composition.getComposerItemById('impact-legend')
        if legend is not None:
            legend_attributes = self.map_legend_attributes
            legend_title = legend_attributes.get('legend_title', None)

            symbol_count = 1
            # noinspection PyUnresolvedReferences
            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)

            # Set Legend
            # Since QGIS 2.6, legend.model() is obsolete
            if qgis_version() < 20600:
                legend.model().setLayerSet([self.layer.id()])
                legend.synchronizeWithModel()
            else:
                root_group = legend.modelV2().rootGroup()
                root_group.addLayer(self.layer)
                legend.synchronizeWithModel()
示例#42
0
def aggregation_summary(aggregate_hazard, aggregation, callback=None):
    """Compute the summary from the aggregate hazard to the analysis layer.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | aggr_id | aggr_name |

    Output layer :
    | aggr_id | aggr_name | count of affected features per exposure type

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param aggregation: The aggregation vector layer where to write statistics.
    :type aggregation: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new aggregation layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_2_aggregation_steps['output_layer_name']
    processing_step = summary_2_aggregation_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = aggregation.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    # Missing exposure_count_field
    source_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field,
        affected_field,
    ]
    check_inputs(source_compulsory_fields, source_fields)

    pattern = exposure_count_field['key']
    pattern = pattern.replace('%s', '')
    unique_exposure = read_dynamic_inasafe_field(source_fields,
                                                 exposure_count_field)

    absolute_values = create_absolute_values_structure(aggregate_hazard,
                                                       ['aggregation_id'])

    flat_table = FlatTable('aggregation_id', 'exposure_class')

    aggregation_index = source_fields[aggregation_id_field['key']]

    # We want to loop over affected features only.
    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    expression = '\"%s\" = \'%s\'' % (affected_field['field_name'], tr('True'))
    request.setFilterExpression(expression)
    for area in aggregate_hazard.getFeatures(request):

        for key, name_field in source_fields.iteritems():
            if key.endswith(pattern):
                aggregation_id = area[aggregation_index]
                exposure_class = key.replace(pattern, '')
                value = area[name_field]
                flat_table.add_value(value,
                                     aggregation_id=aggregation_id,
                                     exposure_class=exposure_class)

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                aggregation_id=area[aggregation_index],
            )

    shift = aggregation.fields().count()

    aggregation.startEditing()

    add_fields(aggregation, absolute_values, [total_affected_field],
               unique_exposure, affected_exposure_count_field)

    aggregation_index = target_fields[aggregation_id_field['key']]

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregation.getFeatures(request):
        aggregation_value = area[aggregation_index]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(aggregation_id=aggregation_value,
                                       exposure_class=val)
            total += sum
            aggregation.changeAttributeValue(area.id(), shift + i, sum)

        aggregation.changeAttributeValue(area.id(),
                                         shift + len(unique_exposure), total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(aggregation_id=aggregation_value, )
            target_index = shift + len(unique_exposure) + 1 + i
            aggregation.changeAttributeValue(area.id(), target_index, value)

    aggregation.commitChanges()

    aggregation.keywords['title'] = layer_purpose_aggregation_summary['name']
    if qgis_version() >= 21800:
        aggregation.setName(aggregation.keywords['title'])
    else:
        aggregation.setLayerName(aggregation.keywords['title'])
    aggregation.keywords['layer_purpose'] = (
        layer_purpose_aggregation_summary['key'])

    check_layer(aggregation)
    return aggregation
    def load_template(self, map_settings):
        """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 map_settings: Map settings.
        :type map_settings: QgsMapSettings, QgsMapRenderer

        """
        # Create Composition
        template_composition = TemplateComposition(
            self.template_path, map_settings)

        # Validate the component in the template
        component_ids = ['impact-map', 'safe-logo', 'summary-report',
                         'aggregation-area', 'map-scale', 'map-legend',
                         'organisation-logo', 'merged-report-table']
        template_composition.component_ids = component_ids
        if len(template_composition.missing_elements) > 0:
            raise ReportCreationError(self.tr(
                'Components: %s could not be found' % ', '.join(
                    template_composition.missing_elements)))

        # Prepare map substitution and set to composition
        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
        }
        template_composition.substitution = substitution_map

        # Load Template
        try:
            template_composition.load_template()
        except TemplateLoadingError:
            raise

        # Draw Composition
        # Set InaSAFE logo
        composition = template_composition.composition
        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')

        if qgis_version() < 20400:
            layers = map_settings.layerSet()
        else:
            layers = map_settings.layers()

        if qgis_version() < 20600:
            legend.model().setLayerSet(layers)
            legend.synchronizeWithModel()
        else:
            root_group = legend.modelV2().rootGroup()

            layer_ids = map_settings.layers()
            for layer_id in layer_ids:
                # noinspection PyUnresolvedReferences
                layer = QgsMapLayerRegistry.instance().mapLayer(layer_id)
                root_group.addLayer(layer)
            legend.synchronizeWithModel()

        return composition
示例#44
0
    def draw_composition(self):
        """Draw all the components in the composition."""
        # This is deprecated - use inasafe-logo-<colour> rather
        safe_logo = self.composition.getComposerItemById(
            'safe-logo')
        # Next two options replace safe logo in 3.2
        black_inasafe_logo = self.composition.getComposerItemById(
            'black-inasafe-logo')
        white_inasafe_logo = self.composition.getComposerItemById(
            'white-inasafe-logo')
        north_arrow = self.composition.getComposerItemById(
            'north-arrow')
        organisation_logo = self.composition.getComposerItemById(
            'organisation-logo')
        supporters_logo = self.composition.getComposerItemById(
            'supporters-logo')

        if qgis_version() < 20600:
            if safe_logo is not None:
                # its deprecated so just use black_inasafe_logo
                safe_logo.setPictureFile(self.inasafe_logo)
            if black_inasafe_logo is not None:
                black_inasafe_logo.setPictureFile(self._black_inasafe_logo)
            if white_inasafe_logo is not None:
                white_inasafe_logo.setPictureFile(self._white_inasafe_logo)
            if north_arrow is not None:
                north_arrow.setPictureFile(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPictureFile(self.organisation_logo)
            if supporters_logo is not None:
                supporters_logo.setPictureFile(self.supporters_logo)
        else:
            if safe_logo is not None:
                # its deprecated so just use black_inasafe_logo
                safe_logo.setPicturePath(self.inasafe_logo)
            if black_inasafe_logo is not None:
                black_inasafe_logo.setPicturePath(self._black_inasafe_logo)
            if white_inasafe_logo is not None:
                white_inasafe_logo.setPicturePath(self._white_inasafe_logo)
            if north_arrow is not None:
                north_arrow.setPicturePath(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPicturePath(self.organisation_logo)
            if supporters_logo is not None:
                supporters_logo.setPicturePath(self.supporters_logo)

        # 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)

        # 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 else 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
            # noinspection PyCallingNonCallable
            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)

        legend = self.composition.getComposerItemById('impact-legend')
        if legend is not None:

            symbol_count = ImpactReport.symbol_count(self.layer)

            # add legend symbol count from extra_layers
            for l in self.extra_layers:
                symbol_count += ImpactReport.symbol_count(l)

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

            # Set back to blank to #2409
            legend.setTitle("")

            # Set Legend
            # Since QGIS 2.6, legend.model() is obsolete
            if qgis_version() < 20600:
                layer_set = [self.layer.id()]
                layer_set += [l.id() for l in self.extra_layers]
                legend.model().setLayerSet(layer_set)
                legend.synchronizeWithModel()
            else:
                root_group = legend.modelV2().rootGroup()
                root_group.addLayer(self.layer)
                for l in self.extra_layers:
                    root_group.addLayer(l)
                legend.synchronizeWithModel()
示例#45
0
    def draw_composition(self):
        """Draw all the components in the composition."""
        # This is deprecated - use inasafe-logo-<colour> rather
        safe_logo = self.composition.getComposerItemById(
            'safe-logo')
        # Next two options replace safe logo in 3.2
        black_inasafe_logo = self.composition.getComposerItemById(
            'black-inasafe-logo')
        white_inasafe_logo = self.composition.getComposerItemById(
            'white-inasafe-logo')
        north_arrow = self.composition.getComposerItemById(
            'north-arrow')
        organisation_logo = self.composition.getComposerItemById(
            'organisation-logo')
        supporters_logo = self.composition.getComposerItemById(
            'supporters-logo')

        if qgis_version() < 20600:
            if safe_logo is not None:
                # its deprecated so just use black_inasafe_logo
                safe_logo.setPictureFile(self.inasafe_logo)
            if black_inasafe_logo is not None:
                black_inasafe_logo.setPictureFile(self._black_inasafe_logo)
            if white_inasafe_logo is not None:
                white_inasafe_logo.setPictureFile(self._white_inasafe_logo)
            if north_arrow is not None:
                north_arrow.setPictureFile(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPictureFile(self.organisation_logo)
            if supporters_logo is not None:
                supporters_logo.setPictureFile(self.supporters_logo)
        else:
            if safe_logo is not None:
                # its deprecated so just use black_inasafe_logo
                safe_logo.setPicturePath(self.inasafe_logo)
            if black_inasafe_logo is not None:
                black_inasafe_logo.setPicturePath(self._black_inasafe_logo)
            if white_inasafe_logo is not None:
                white_inasafe_logo.setPicturePath(self._white_inasafe_logo)
            if north_arrow is not None:
                north_arrow.setPicturePath(self.north_arrow)
            if organisation_logo is not None:
                organisation_logo.setPicturePath(self.organisation_logo)
            if supporters_logo is not None:
                supporters_logo.setPicturePath(self.supporters_logo)

        # 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)

        # 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 else 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
            # noinspection PyCallingNonCallable
            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)

        legend = self.composition.getComposerItemById('impact-legend')
        if legend is not None:

            symbol_count = ImpactReport.symbol_count(self.layer)

            # add legend symbol count from extra_layers
            for l in self.extra_layers:
                symbol_count += ImpactReport.symbol_count(l)

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

            # Set back to blank to #2409
            legend.setTitle("")

            # Set Legend
            # Since QGIS 2.6, legend.model() is obsolete
            if qgis_version() < 20600:
                layer_set = [self.layer.id()]
                layer_set += [l.id() for l in self.extra_layers]
                legend.model().setLayerSet(layer_set)
                legend.synchronizeWithModel()
            else:
                root_group = legend.modelV2().rootGroup()
                root_group.addLayer(self.layer)
                for l in self.extra_layers:
                    root_group.addLayer(l)
                legend.synchronizeWithModel()
示例#46
0
def aggregate_hazard_summary(impact, aggregate_hazard):
    """Compute the summary from the source layer to the aggregate_hazard layer.

    Source layer :
    |exp_id|exp_class|haz_id|haz_class|aggr_id|aggr_name|affected|extra*|

    Target layer :
    | aggr_id | aggr_name | haz_id | haz_class | extra* |

    Output layer :
    |aggr_id| aggr_name|haz_id|haz_class|affected|extra*|count ber exposure*|


    :param impact: The layer to aggregate vector layer.
    :type impact: QgsVectorLayer

    :param aggregate_hazard: The aggregate_hazard vector layer where to write
        statistics.
    :type aggregate_hazard: QgsVectorLayer

    :return: The new aggregate_hazard layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    source_fields = impact.keywords['inasafe_fields']
    target_fields = aggregate_hazard.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field, aggregation_name_field, hazard_id_field,
        hazard_class_field
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        exposure_id_field, exposure_class_field, aggregation_id_field,
        aggregation_name_field, hazard_id_field, hazard_class_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    aggregation_id = target_fields[aggregation_id_field['key']]

    hazard_id = target_fields[hazard_id_field['key']]
    hazard_class = target_fields[hazard_class_field['key']]

    exposure_class = source_fields[exposure_class_field['key']]
    exposure_class_index = impact.fields().lookupField(exposure_class)
    unique_exposure = list(impact.uniqueValues(exposure_class_index))

    fields = ['aggregation_id', 'hazard_id']
    absolute_values = create_absolute_values_structure(impact, fields)

    # We need to know what kind of exposure we are going to count.
    # the size, or the number of features or population.
    field_index = report_on_field(impact)

    aggregate_hazard.startEditing()

    shift = aggregate_hazard.fields().count()
    dynamic_structure = [
        [exposure_count_field, unique_exposure],
    ]
    add_fields(
        aggregate_hazard,
        absolute_values,
        [affected_field, total_field],
        dynamic_structure,
    )

    flat_table = FlatTable('aggregation_id', 'hazard_id', 'exposure_class')

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    LOGGER.debug('Computing the aggregate hazard summary.')
    for feature in impact.getFeatures(request):
        # Field_index can be equal to 0.
        if field_index is not None:
            value = feature[field_index]
        else:
            value = 1

        aggregation_value = feature[aggregation_id]
        hazard_value = feature[hazard_id]
        if (hazard_value is None or hazard_value == '' or
            (hasattr(hazard_value, 'isNull') and hazard_value.isNull())):
            hazard_value = not_exposed_class['key']
        exposure_value = feature[exposure_class]
        if (exposure_value is None or exposure_value == '' or
            (hasattr(exposure_value, 'isNull') and exposure_value.isNull())):
            exposure_value = 'NULL'

        flat_table.add_value(value,
                             aggregation_id=aggregation_value,
                             hazard_id=hazard_value,
                             exposure_class=exposure_value)

        # We summarize every absolute values.
        for field, field_definition in list(absolute_values.items()):
            value = feature[field]
            if (value == '' or value is None
                    or (hasattr(value, 'isNull') and value.isNull())):
                value = 0
            field_definition[0].add_value(value,
                                          aggregation_id=aggregation_value,
                                          hazard_id=hazard_value)

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    hazard = hazard_keywords['hazard']
    classification = hazard_keywords['classification']

    exposure_keywords = impact.keywords['exposure_keywords']
    exposure = exposure_keywords['exposure']

    for area in aggregate_hazard.getFeatures(request):
        aggregation_value = area[aggregation_id]
        feature_hazard_id = area[hazard_id]
        if (feature_hazard_id == '' or feature_hazard_id is None
                or (hasattr(feature_hazard_id, 'isNull')
                    and feature_hazard_id.isNull())):
            feature_hazard_id = not_exposed_class['key']
        feature_hazard_value = area[hazard_class]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(aggregation_id=aggregation_value,
                                       hazard_id=feature_hazard_id,
                                       exposure_class=val)
            total += sum
            aggregate_hazard.changeAttributeValue(area.id(), shift + i, sum)

        affected = post_processor_affected_function(
            exposure=exposure,
            hazard=hazard,
            classification=classification,
            hazard_class=feature_hazard_value)
        affected = tr(str(affected))
        aggregate_hazard.changeAttributeValue(area.id(),
                                              shift + len(unique_exposure),
                                              affected)

        aggregate_hazard.changeAttributeValue(area.id(),
                                              shift + len(unique_exposure) + 1,
                                              total)

        for i, field in enumerate(absolute_values.values()):
            value = field[0].get_value(aggregation_id=aggregation_value,
                                       hazard_id=feature_hazard_id)
            aggregate_hazard.changeAttributeValue(
                area.id(), shift + len(unique_exposure) + 2 + i, value)

    aggregate_hazard.commitChanges()

    aggregate_hazard.keywords['title'] = (
        layer_purpose_aggregate_hazard_impacted['name'])
    if qgis_version() >= 21800:
        aggregate_hazard.setName(aggregate_hazard.keywords['title'])
    else:
        aggregate_hazard.setLayerName(aggregate_hazard.keywords['title'])
    aggregate_hazard.keywords['layer_purpose'] = (
        layer_purpose_aggregate_hazard_impacted['key'])
    aggregate_hazard.keywords['exposure_keywords'] = impact.keywords.copy()
    check_layer(aggregate_hazard)
    return aggregate_hazard
示例#47
0
        LOGGER.debug("Parse argument")
        shell_arguments = docopt(usage)
        LOGGER.debug("Parse done")
    except DocoptExit as exc:
        print exc.message

    try:
        arguments = CommandLineArguments(shell_arguments)

        LOGGER.debug(shell_arguments)
        if arguments.show_list is True:
            # setup functions
            register_impact_functions()
            show_impact_function_names(get_impact_function_list(arguments))
        elif arguments.version is True:
            print "QGIS VERSION: " + str(qgis_version()).replace("0", ".")
        # user is only interested in doing a download
        elif arguments.download is True and arguments.exposure is None and arguments.hazard is None:
            print "downloading ..."
            download_exposure(arguments)

        elif (arguments.hazard is not None) and (arguments.output_file is not None):
            # first do download if necessary
            if arguments.exposure is None and arguments.download is True:
                download_exposure(arguments)

            if arguments.exposure is not None:
                run_impact_function(arguments)
            else:
                print "Download unsuccessful"
        elif arguments.report_template is not None and arguments.output_file is not None:
示例#48
0
    def get_layer_description_from_browser(self, category):
        """Obtain the description of the browser layer selected by user.

        :param category: The category of the layer to get the description.
        :type category: string

        :returns: Tuple of boolean and string. Boolean is true if layer is
            validated as compatible for current role (impact function and
            category) and false otherwise. String contains a description
            of the selected layer or an error message.
        :rtype: tuple
        """

        if category == 'hazard':
            browser = self.tvBrowserHazard
        elif category == 'exposure':
            browser = self.tvBrowserExposure
        elif category == 'aggregation':
            browser = self.tvBrowserAggregation
        else:
            raise InaSAFEError

        index = browser.selectionModel().currentIndex()
        if not index:
            return False, ''

        # Map the proxy model index to the source model index
        index = browser.model().mapToSource(index)
        item = browser.model().sourceModel().dataItem(index)
        if not item:
            return False, ''

        item_class_name = item.metaObject().className()
        # if not itemClassName.endswith('LayerItem'):
        if not item.type() == QgsDataItem.Layer:
            if item_class_name == 'QgsPGRootItem' and not item.children():
                return False, create_postGIS_connection_first
            else:
                return False, ''

        if item_class_name not in [
                'QgsOgrLayerItem', 'QgsGdalLayerItem', 'QgsPGLayerItem',
                'QgsLayerItem', ]:
            return False, ''

        path = item.path()

        if item_class_name in ['QgsOgrLayerItem', 'QgsGdalLayerItem',
                               'QgsLayerItem'] and not os.path.exists(path):
            return False, ''

        # try to create the layer
        if item_class_name == 'QgsOgrLayerItem':
            layer = QgsVectorLayer(path, '', 'ogr')
        elif item_class_name == 'QgsPGLayerItem':
            uri = self.postgis_path_to_uri(path)
            if uri:
                layer = QgsVectorLayer(uri.uri(), uri.table(), 'postgres')
            else:
                layer = None
        else:
            layer = QgsRasterLayer(path, '', 'gdal')

        if not layer or not layer.isValid():
            return False, self.tr('Not a valid layer.')

        try:
            keywords = self.keyword_io.read_keywords(layer)
            if 'layer_purpose' not in keywords:
                keywords = None
        except (HashNotFoundError,
                OperationalError,
                NoKeywordsFoundError,
                KeywordNotFoundError,
                InvalidParameterError,
                UnsupportedProviderError,
                MissingMetadata):
            keywords = None

        # set the layer name for further use in the step_fc_summary
        if keywords:
            if qgis_version() >= 21800:
                layer.setName(keywords.get('title'))
            else:
                layer.setLayerName(keywords.get('title'))

        if not self.parent.is_layer_compatible(layer, category, keywords):
            label_text = '%s<br/>%s' % (
                self.tr(
                    'This layer\'s keywords or type are not suitable:'),
                self.unsuitable_layer_description_html(
                    layer, category, keywords))
            return False, label_text

        # set the current layer (e.g. for the keyword creation sub-thread
        #                          or for adding the layer to mapCanvas)
        self.parent.layer = layer
        if category == 'hazard':
            self.parent.hazard_layer = layer
        elif category == 'exposure':
            self.parent.exposure_layer = layer
        else:
            self.parent.aggregation_layer = layer

        # Check if the layer is keywordless
        if keywords and 'keyword_version' in keywords:
            kw_ver = str(keywords['keyword_version'])
            self.parent.is_selected_layer_keywordless = (
                not is_keyword_version_supported(kw_ver))
        else:
            self.parent.is_selected_layer_keywordless = True

        desc = layer_description_html(layer, keywords)
        return True, desc
示例#49
0
    def accept(self):
        """Launch the multi exposure analysis."""
        if not isinstance(self._multi_exposure_if,
                          MultiExposureImpactFunction):
            # This should not happen as the "accept" button must be disabled if
            # the impact function is not ready.
            return ANALYSIS_FAILED_BAD_CODE, None

        self.tab_widget.setCurrentIndex(2)
        self.set_enabled_buttons(False)
        enable_busy_cursor()
        try:
            code, message, exposure = self._multi_exposure_if.run()
            message = basestring_to_message(message)
            if code == ANALYSIS_FAILED_BAD_INPUT:
                LOGGER.warning(
                    tr('The impact function could not run because of the inputs.'
                       ))
                send_error_message(self, message)
                LOGGER.warning(message.to_text())
                disable_busy_cursor()
                self.set_enabled_buttons(True)
                return code, message
            elif code == ANALYSIS_FAILED_BAD_CODE:
                LOGGER.warning(
                    tr('The impact function could not run because of a bug.'))
                LOGGER.exception(message.to_text())
                send_error_message(self, message)
                disable_busy_cursor()
                self.set_enabled_buttons(True)
                return code, message

            if setting('generate_report', True, bool):
                LOGGER.info(
                    'Reports are going to be generated for the multiexposure.')
                # Report for the multi exposure
                report = [standard_multi_exposure_impact_report_metadata_html]
                error_code, message = (
                    self._multi_exposure_if.generate_report(report))
                message = basestring_to_message(message)
                if error_code == ImpactReport.REPORT_GENERATION_FAILED:
                    LOGGER.warning('The impact report could not be generated.')
                    send_error_message(self, message)
                    LOGGER.exception(message.to_text())
                    disable_busy_cursor()
                    self.set_enabled_buttons(True)
                    return error_code, message
            else:
                LOGGER.warning(
                    'Reports are not generated because of your settings.')
                display_warning_message_bar(
                    tr('Reports'),
                    tr('Reports are not going to be generated because of your '
                       'InaSAFE settings.'),
                    duration=10,
                    iface_object=self.iface)

            # We always create the multi exposure group because we need
            # reports to be generated.
            root = QgsProject.instance().layerTreeRoot()

            if len(self.ordered_expected_layers()) == 0:
                group_analysis = root.insertGroup(0,
                                                  self._multi_exposure_if.name)
                group_analysis.setItemVisibilityChecked(True)
                group_analysis.setCustomProperty(MULTI_EXPOSURE_ANALYSIS_FLAG,
                                                 True)

                for layer in self._multi_exposure_if.outputs:
                    QgsProject.instance().addMapLayer(layer, False)
                    layer_node = group_analysis.addLayer(layer)
                    layer_node.setItemVisibilityChecked(False)

                    # set layer title if any
                    try:
                        title = layer.keywords['title']
                        if qgis_version() >= 21800:
                            layer.setName(title)
                        else:
                            layer.setLayerName(title)
                    except KeyError:
                        pass

                for analysis in self._multi_exposure_if.impact_functions:
                    detailed_group = group_analysis.insertGroup(
                        0, analysis.name)
                    detailed_group.setItemVisibilityChecked(True)
                    add_impact_layers_to_canvas(analysis, group=detailed_group)

                if self.iface:
                    self.iface.setActiveLayer(
                        self._multi_exposure_if.analysis_impacted)
            else:
                add_layers_to_canvas_with_custom_orders(
                    self.ordered_expected_layers(), self._multi_exposure_if,
                    self.iface)

            if setting('generate_report', True, bool):
                LOGGER.info(
                    'Reports are going to be generated for each single '
                    'exposure.')
                # Report for the single exposure with hazard
                for analysis in self._multi_exposure_if.impact_functions:
                    # we only want to generate non pdf/qpt report
                    html_components = [standard_impact_report_metadata_html]
                    error_code, message = (
                        analysis.generate_report(html_components))
                    message = basestring_to_message(message)
                    if error_code == (ImpactReport.REPORT_GENERATION_FAILED):
                        LOGGER.info(
                            'The impact report could not be generated.')
                        send_error_message(self, message)
                        LOGGER.info(message.to_text())
                        disable_busy_cursor()
                        self.set_enabled_buttons(True)
                        return error_code, message
            else:
                LOGGER.info(
                    'Reports are not generated because of your settings.')
                display_warning_message_bar(
                    tr('Reports'),
                    tr('Reports are not going to be generated because of your '
                       'InaSAFE settings.'),
                    duration=10,
                    iface_object=self.iface)

            # If zoom to impact is enabled
            if setting('setZoomToImpactFlag', expected_type=bool):
                self.iface.zoomToActiveLayer()

            # If hide exposure layers
            if setting('setHideExposureFlag', expected_type=bool):
                treeroot = QgsProject.instance().layerTreeRoot()
                for combo in list(self.combos_exposures.values()):
                    layer = layer_from_combo(combo)
                    if layer is not None:
                        treelayer = treeroot.findLayer(layer.id())
                        if treelayer:
                            treelayer.setItemVisibilityChecked(False)

            # Set last analysis extent
            self._extent.set_last_analysis_extent(
                self._multi_exposure_if.analysis_extent,
                self._multi_exposure_if.crs)

            self.done(QDialog.Accepted)

        except Exception as e:
            error_message = get_error_message(e)
            send_error_message(self, error_message)
            LOGGER.exception(e)
            LOGGER.debug(error_message.to_text())
        finally:
            disable_busy_cursor()
            self.set_enabled_buttons(True)
示例#50
0
def aggregate_hazard_summary(impact, aggregate_hazard, callback=None):
    """Compute the summary from the source layer to the aggregate_hazard layer.

    Source layer :
    |exp_id|exp_class|haz_id|haz_class|aggr_id|aggr_name|affected|extra*|

    Target layer :
    | aggr_id | aggr_name | haz_id | haz_class | extra* |

    Output layer :
    |aggr_id| aggr_name|haz_id|haz_class|affected|extra*|count ber exposure*|


    :param impact: The layer to aggregate vector layer.
    :type impact: QgsVectorLayer

    :param aggregate_hazard: The aggregate_hazard vector layer where to write
        statistics.
    :type aggregate_hazard: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new aggregate_hazard layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_1_aggregate_hazard_steps['output_layer_name']
    processing_step = summary_1_aggregate_hazard_steps['step_name']

    source_fields = impact.keywords['inasafe_fields']
    target_fields = aggregate_hazard.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field, aggregation_name_field, hazard_id_field,
        hazard_class_field
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        exposure_id_field, exposure_class_field, aggregation_id_field,
        aggregation_name_field, hazard_id_field, hazard_class_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    aggregation_id = target_fields[aggregation_id_field['key']]

    hazard_id = target_fields[hazard_id_field['key']]
    hazard_class = target_fields[hazard_class_field['key']]

    exposure_class = source_fields[exposure_class_field['key']]
    exposure_class_index = impact.fieldNameIndex(exposure_class)
    unique_exposure = impact.uniqueValues(exposure_class_index)

    fields = ['aggregation_id', 'hazard_id']
    absolute_values = create_absolute_values_structure(impact, fields)

    # We need to know what kind of exposure we are going to count.
    # the size, or the number of features or population.
    field_index = report_on_field(impact)

    aggregate_hazard.startEditing()

    shift = aggregate_hazard.fields().count()
    add_fields(aggregate_hazard, absolute_values,
               [affected_field, total_field], unique_exposure,
               exposure_count_field)

    flat_table = FlatTable('aggregation_id', 'hazard_id', 'exposure_class')

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    LOGGER.debug('Computing the aggregate hazard summary.')
    for feature in impact.getFeatures(request):
        # Field_index can be equal to 0.
        if field_index is not None:
            value = feature[field_index]
        else:
            value = 1

        aggregation_value = feature[aggregation_id]
        hazard_value = feature[hazard_id]
        if not hazard_value or isinstance(hazard_value, QPyNullVariant):
            hazard_value = not_exposed_class['key']
        exposure_value = feature[exposure_class]
        if not exposure_value or isinstance(exposure_value, QPyNullVariant):
            exposure_value = 'NULL'

        flat_table.add_value(value,
                             aggregation_id=aggregation_value,
                             hazard_id=hazard_value,
                             exposure_class=exposure_value)

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = feature[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(value,
                                          aggregation_id=aggregation_value,
                                          hazard_id=hazard_value)

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    classification = hazard_keywords['classification']

    for area in aggregate_hazard.getFeatures(request):
        aggregation_value = area[aggregation_id]
        feature_hazard_id = area[hazard_id]
        if not feature_hazard_id or isinstance(feature_hazard_id,
                                               QPyNullVariant):
            feature_hazard_id = not_exposed_class['key']
        feature_hazard_value = area[hazard_class]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(aggregation_id=aggregation_value,
                                       hazard_id=feature_hazard_id,
                                       exposure_class=val)
            total += sum
            aggregate_hazard.changeAttributeValue(area.id(), shift + i, sum)

        affected = post_processor_affected_function(
            classification=classification, hazard_class=feature_hazard_value)
        affected = tr(unicode(affected))
        aggregate_hazard.changeAttributeValue(area.id(),
                                              shift + len(unique_exposure),
                                              affected)

        aggregate_hazard.changeAttributeValue(area.id(),
                                              shift + len(unique_exposure) + 1,
                                              total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(aggregation_id=aggregation_value,
                                       hazard_id=feature_hazard_id)
            aggregate_hazard.changeAttributeValue(
                area.id(), shift + len(unique_exposure) + 2 + i, value)

    aggregate_hazard.commitChanges()

    aggregate_hazard.keywords['title'] = (
        layer_purpose_aggregate_hazard_impacted['name'])
    if qgis_version() >= 21800:
        aggregate_hazard.setName(aggregate_hazard.keywords['title'])
    else:
        aggregate_hazard.setLayerName(aggregate_hazard.keywords['title'])
    aggregate_hazard.keywords['layer_purpose'] = (
        layer_purpose_aggregate_hazard_impacted['key'])

    check_layer(aggregate_hazard)
    return aggregate_hazard
示例#51
0
def add_layers_to_canvas_with_custom_orders(
        order, impact_function, iface=None):
    """Helper to add layers to the map canvas following a specific order.

    From top to bottom in the legend:
        [
            ('FromCanvas', layer name, full layer URI, QML),
            ('FromAnalysis', layer purpose, layer group, None),
            ...
        ]

        The full layer URI is coming from our helper.

    :param order: Special structure the list of layers to add.
    :type order: list

    :param impact_function: The multi exposure impact function used.
    :type impact_function: MultiExposureImpactFunction

    :param iface: QGIS QgisAppInterface instance.
    :type iface: QgisAppInterface
    """
    root = QgsProject.instance().layerTreeRoot()
    # Make all layers hidden.
    for child in root.children():
        child.setItemVisibilityChecked(False)

    group_analysis = root.insertGroup(0, impact_function.name)
    group_analysis.setItemVisibilityChecked(True)
    group_analysis.setCustomProperty(MULTI_EXPOSURE_ANALYSIS_FLAG, True)

    # Insert layers in the good order in the group.
    for layer_definition in order:
        if layer_definition[0] == FROM_CANVAS['key']:
            style = QDomDocument()
            style.setContent(layer_definition[3])
            layer = load_layer(layer_definition[2], layer_definition[1])[0]
            layer.importNamedStyle(style)
            QgsProject.instance().addMapLayer(layer, False)
            layer_node = group_analysis.addLayer(layer)
            layer_node.setItemVisibilityChecked(True)
        else:
            if layer_definition[2] == impact_function.name:
                for layer in impact_function.outputs:
                    if layer.keywords['layer_purpose'] == layer_definition[1]:
                        QgsProject.instance().addMapLayer(
                            layer, False)
                        layer_node = group_analysis.addLayer(layer)
                        layer_node.setItemVisibilityChecked(True)
                        try:
                            title = layer.keywords['title']
                            if qgis_version() >= 21800:
                                layer.setName(title)
                            else:
                                layer.setLayerName(title)
                        except KeyError:
                            pass
                        break
            else:
                for sub_impact_function in impact_function.impact_functions:
                    # Iterate over each sub impact function used in the
                    # multi exposure analysis.
                    if sub_impact_function.name == layer_definition[2]:
                        for layer in sub_impact_function.outputs:
                            purpose = layer_definition[1]
                            if layer.keywords['layer_purpose'] == purpose:
                                QgsProject.instance().addMapLayer(
                                    layer, False)
                                layer_node = group_analysis.addLayer(
                                    layer)
                                layer_node.setItemVisibilityChecked(True)
                                try:
                                    title = layer.keywords['title']
                                    if qgis_version() >= 21800:
                                        layer.setName(title)
                                    else:
                                        layer.setLayerName(title)
                                except KeyError:
                                    pass
                                break

    if iface:
        iface.setActiveLayer(impact_function.analysis_impacted)
def aggregation_summary(aggregate_hazard, aggregation, callback=None):
    """Compute the summary from the aggregate hazard to the analysis layer.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | aggr_id | aggr_name |

    Output layer :
    | aggr_id | aggr_name | count of affected features per exposure type

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param aggregation: The aggregation vector layer where to write statistics.
    :type aggregation: QgsVectorLayer

    :param callback: A function to all to indicate progress. The function
        should accept params 'current' (int), 'maximum' (int) and 'step' (str).
        Defaults to None.
    :type callback: function

    :return: The new aggregation layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = summary_2_aggregation_steps['output_layer_name']
    processing_step = summary_2_aggregation_steps['step_name']

    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = aggregation.keywords['inasafe_fields']

    target_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    # Missing exposure_count_field
    source_compulsory_fields = [
        aggregation_id_field,
        aggregation_name_field,
        hazard_id_field,
        hazard_class_field,
        affected_field,
    ]
    check_inputs(source_compulsory_fields, source_fields)

    pattern = exposure_count_field['key']
    pattern = pattern.replace('%s', '')
    unique_exposure = read_dynamic_inasafe_field(
        source_fields, exposure_count_field)

    absolute_values = create_absolute_values_structure(
        aggregate_hazard, ['aggregation_id'])

    flat_table = FlatTable('aggregation_id', 'exposure_class')

    aggregation_index = source_fields[aggregation_id_field['key']]

    # We want to loop over affected features only.
    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    expression = '\"%s\" = \'%s\'' % (
        affected_field['field_name'], tr('True'))
    request.setFilterExpression(expression)
    for area in aggregate_hazard.getFeatures(request):

        for key, name_field in source_fields.iteritems():
            if key.endswith(pattern):
                aggregation_id = area[aggregation_index]
                exposure_class = key.replace(pattern, '')
                value = area[name_field]
                flat_table.add_value(
                    value,
                    aggregation_id=aggregation_id,
                    exposure_class=exposure_class
                )

        # We summarize every absolute values.
        for field, field_definition in absolute_values.iteritems():
            value = area[field]
            if not value or isinstance(value, QPyNullVariant):
                value = 0
            field_definition[0].add_value(
                value,
                aggregation_id=area[aggregation_index],
            )

    shift = aggregation.fields().count()

    aggregation.startEditing()

    add_fields(
        aggregation,
        absolute_values,
        [total_affected_field],
        unique_exposure,
        affected_exposure_count_field)

    aggregation_index = target_fields[aggregation_id_field['key']]

    request = QgsFeatureRequest()
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregation.getFeatures(request):
        aggregation_value = area[aggregation_index]
        total = 0
        for i, val in enumerate(unique_exposure):
            sum = flat_table.get_value(
                aggregation_id=aggregation_value,
                exposure_class=val
            )
            total += sum
            aggregation.changeAttributeValue(area.id(), shift + i, sum)

        aggregation.changeAttributeValue(
            area.id(), shift + len(unique_exposure), total)

        for i, field in enumerate(absolute_values.itervalues()):
            value = field[0].get_value(
                aggregation_id=aggregation_value,
            )
            target_index = shift + len(unique_exposure) + 1 + i
            aggregation.changeAttributeValue(
                area.id(), target_index, value)

    aggregation.commitChanges()

    aggregation.keywords['title'] = layer_purpose_aggregation_summary['name']
    if qgis_version() >= 21800:
        aggregation.setName(aggregation.keywords['title'])
    else:
        aggregation.setLayerName(aggregation.keywords['title'])
    aggregation.keywords['layer_purpose'] = (
        layer_purpose_aggregation_summary['key'])

    check_layer(aggregation)
    return aggregation
class TestEarthquakeReport(unittest.TestCase):
    """Test Earthquake Report.

    .. versionadded:: 4.0
    """

    maxDiff = None

    @classmethod
    def fixtures_dir(cls, path):
        """Helper to return fixture path."""
        directory_name = os.path.dirname(__file__)
        return os.path.join(directory_name, 'fixtures', path)

    def assert_compare_file_control(self, control_path, actual_path):
        """Helper to compare file."""
        current_directory = resources_path()
        context = {'current_directory': current_directory}
        with open(control_path) as control_file:
            template_string = control_file.read()
            template = Template(template_string)
            control_string = template.render(context).strip()

        with io.open(actual_path, encoding='utf-8') as actual_file:
            actual_string = actual_file.read().strip()
            self.assertEqual(control_string, actual_string)

    @unittest.skipIf(
        qgis_version() < 31000,
        'Skip this in QGIS 3.4 due to stall issue from core qgis thread.')
    def test_earthquake_population_without_aggregation(self):
        """Testing Earthquake in Population without aggregation.

        .. versionadded:: 4.0
        """
        output_folder = self.fixtures_dir('../output/earthquake_population')

        # Classified vector with building-points
        shutil.rmtree(output_folder, ignore_errors=True)

        hazard_layer = load_test_raster_layer('hazard', 'earthquake.tif')
        exposure_layer = load_test_raster_layer('exposure',
                                                'pop_binary_raster_20_20.asc')

        impact_function = ImpactFunction()
        impact_function.exposure = exposure_layer
        impact_function.hazard = hazard_layer
        impact_function.crs = QgsCoordinateReferenceSystem(4326)
        impact_function.prepare()
        return_code, message = impact_function.run()

        self.assertEqual(return_code, ANALYSIS_SUCCESS, message)

        report_metadata = ReportMetadata(
            metadata_dict=standard_impact_report_metadata_html)

        impact_report = ImpactReport(IFACE,
                                     report_metadata,
                                     impact_function=impact_function)
        impact_report.output_folder = output_folder
        return_code, message = impact_report.process_components()

        self.assertEqual(return_code, ImpactReport.REPORT_GENERATION_SUCCESS,
                         message)
        """Checking generated context."""
        empty_component_output_message = 'Empty component output'

        # Check Analysis Summary
        analysis_summary = impact_report.metadata.component_by_key(
            general_report_component['key'])
        """:type: safe.report.report_metadata.Jinja2ComponentsMetadata"""

        expected_context = {
            'component_key':
            'general-report',
            'table_header':
            ('Estimated Number of people affected per MMI intensity'),
            'header':
            'General Report',
            'summary': [{
                'header_label':
                'Hazard Zone',
                'rows': [{
                    'numbers': ['0'],
                    'name': 'X',
                    'key': 'X'
                }, {
                    'numbers': ['0'],
                    'name': 'IX',
                    'key': 'IX'
                }, {
                    'numbers': ['200'],
                    'name': 'VIII',
                    'key': 'VIII'
                }, {
                    'numbers': ['0'],
                    'name': 'VII',
                    'key': 'VII'
                }, {
                    'numbers': ['0'],
                    'name': 'VI',
                    'key': 'VI'
                }, {
                    'numbers': ['0'],
                    'name': 'V',
                    'key': 'V'
                }, {
                    'numbers': ['0'],
                    'name': 'IV',
                    'key': 'IV'
                }, {
                    'numbers': ['0'],
                    'name': 'III',
                    'key': 'III'
                }, {
                    'numbers': ['0'],
                    'name': 'II',
                    'key': 'II'
                }, {
                    'numbers': ['0'],
                    'name': 'I',
                    'key': 'I'
                }, {
                    'as_header': True,
                    'key': 'total_exposed_field',
                    'name': 'Total Exposed',
                    'numbers': ['200']
                }],
                'value_labels': ['Count']
            }, {
                'header_label':
                'Population',
                'rows': [{
                    'numbers': ['200'],
                    'name': 'Affected',
                    'key': 'total_affected_field',
                }, {
                    'key': 'total_not_affected_field',
                    'name': 'Not Affected',
                    'numbers': ['0']
                }, {
                    'key': 'total_not_exposed_field',
                    'name': 'Not Exposed',
                    'numbers': ['0']
                }, {
                    'numbers': ['200'],
                    'name': 'Displaced',
                    'key': 'displaced_field'
                }, {
                    'numbers': ['0 - 100'],
                    'name': 'Fatalities',
                    'key': 'fatalities_field'
                }],
                'value_labels': ['Count']
            }],
            'notes': [
                'Exposed People: People who are present in hazard zones and '
                'are thereby subject to potential losses. In InaSAFE, people '
                'who are exposed are those people who are within the extent '
                'of the hazard.',
                'Affected People: People who are affected by a hazardous '
                'event. People can be affected directly or indirectly. '
                'Affected people may experience short-term or long-term '
                'consequences to their lives, livelihoods or health and in '
                'the economic, physical, social, cultural and environmental '
                'assets. In InaSAFE, people who are killed during the event '
                'are also considered affected.',
                'Displaced People: Displaced people are people who, for '
                'different reasons and circumstances because of risk or '
                'disaster, have to leave their place of residence. '
                'In InaSAFE, demographic and minimum needs reports are based '
                'on displaced / evacuated people.'
            ]
        }
        actual_context = analysis_summary.context

        self.assertDictEqual(expected_context, actual_context)
        self.assertTrue(analysis_summary.output,
                        empty_component_output_message)

        report_metadata = ReportMetadata(metadata_dict=infographic_report)
        infographic_impact_report = ImpactReport(
            IFACE, report_metadata, impact_function=impact_function)

        infographic_impact_report.output_folder = output_folder
        return_code, message = infographic_impact_report.process_components()

        self.assertEqual(return_code, ImpactReport.REPORT_GENERATION_SUCCESS,
                         message)

        # check population pie chart if we have 100% donut slice
        population_chart_svg = (
            infographic_impact_report.metadata.component_by_key(
                population_chart_svg_component['key']))

        expected_slices = [{
            'value':
            200,
            'show_label':
            True,
            'center': (224.0, 128.0),
            'stroke_opacity':
            1,
            'path':
            'M128.000000,0.000000a128.000000,128.000000 0 0 1 '
            '0.000000,256.000000l-0.000000,-64.000000a64.000000,'
            '64.000000 0 0 0 0.000000,-128.000000Z',
            'percentage':
            100,
            'label':
            'VIII',
            'stroke':
            '#ff7000',
            'label_position': (256, 0),
            'fill':
            '#ff7000'
        }, {
            'value':
            100,
            'show_label':
            False,
            'center': (32.0, 128.0),
            'stroke_opacity':
            1,
            'path':
            'M128.000000,256.000000a128.000000,128.000000 0 0 1 '
            '-0.000000,-256.000000l0.000000,64.000000a64.000000,'
            '64.000000 0 0 0 0.000000,128.000000Z',
            'percentage':
            50.0,
            'label':
            '',
            'stroke':
            '#ff7000',
            'label_position': (256, 0),
            'fill':
            '#ff7000'
        }, {
            'value':
            0,
            'show_label':
            False,
            'center': (128.0, 224.0),
            'stroke_opacity':
            1,
            'path':
            'M128.000000,256.000000a128.000000,128.000000 0 0 1 '
            '0.000000,0.000000l-0.000000,-64.000000a64.000000,'
            '64.000000 0 0 0 0.000000,0.000000Z',
            'percentage':
            0.0,
            'label':
            'Total Not Affected',
            'stroke':
            '#fff',
            'label_position': (256, 0),
            'fill':
            '#1a9641'
        }]

        actual_context = population_chart_svg.context['context']
        actual_slices = actual_context.slices

        self.assertEqual(expected_slices, actual_slices)
        self.assertTrue(population_chart_svg.output,
                        empty_component_output_message)

        shutil.rmtree(output_folder, ignore_errors=True)
示例#54
0
    print "inasafe"
    print ""
    try:
        # Parse arguments, use usage.txt as syntax definition.
        LOGGER.debug('Parse argument')
        shell_arguments = docopt(usage)
        LOGGER.debug('Parse done')
    except DocoptExit as exc:
        print exc.message

    try:
        arguments = CommandLineArguments(shell_arguments)

        LOGGER.debug(shell_arguments)
        if arguments.version is True:
            print "QGIS VERSION: " + str(qgis_version()).replace('0', '.')
        # user is only interested in doing a download
        elif arguments.download is True and\
                arguments.exposure is None and\
                arguments.hazard is None:
            print "downloading ..."
            download_exposure(arguments)

        elif (arguments.hazard is not None) and\
                (arguments.output_file is not None):
            # first do download if necessary
            if arguments.exposure is None and arguments.download is True:
                download_exposure(arguments)

            if arguments.exposure is not None:
                run_impact_function(arguments)
示例#55
0
    def print_impact_table(self, output_path):
        """Pint summary from impact layer to PDF.

        ..note:: The order of the report:
            1. Summary table
            2. Aggregation table
            3. Attribution table

        :param output_path: Output path.
        :type output_path: str

        :return: Path to generated pdf file.
        :rtype: str

        :raises: None
        """
        keywords = self._keyword_io.read_keywords(self.layer)

        if output_path is None:
            output_path = unique_filename(suffix='.pdf', dir=temp_dir())

        summary_table = keywords.get('impact_summary', None)
        full_table = keywords.get('impact_table', None)
        aggregation_table = keywords.get('postprocessing_report', None)
        attribution_table = impact_attribution(keywords)

        # (AG) We will not use impact_table as most of the IF use that as:
        # impact_table = impact_summary + some information intended to be
        # shown on screen (see FloodOsmBuilding)
        # Unless the impact_summary is None, we will use impact_table as the
        # alternative
        html = LOGO_ELEMENT.to_html()
        html += m.Heading(tr('Analysis Results'), **INFO_STYLE).to_html()
        if summary_table is None:
            html += full_table
        else:
            html += summary_table

        if aggregation_table is not None:
            html += aggregation_table

        if attribution_table is not None:
            html += attribution_table.to_html()

        html = html_header() + html + html_footer()

        # Print HTML using composition
        # For QGIS < 2.4 compatibility
        # QgsMapSettings is added in 2.4
        if qgis_version() < 20400:
            map_settings = QgsMapRenderer()
        else:
            map_settings = QgsMapSettings()

        # A4 Portrait
        paper_width = 210
        paper_height = 297

        # noinspection PyCallingNonCallable
        composition = QgsComposition(map_settings)
        # noinspection PyUnresolvedReferences
        composition.setPlotStyle(QgsComposition.Print)
        composition.setPaperSize(paper_width, paper_height)
        composition.setPrintResolution(300)

        # Add HTML Frame
        # noinspection PyCallingNonCallable
        html_item = QgsComposerHtml(composition, False)
        margin_left = 10
        margin_top = 10

        # noinspection PyCallingNonCallable
        html_frame = QgsComposerFrame(
            composition,
            html_item,
            margin_left,
            margin_top,
            paper_width - 2 * margin_left,
            paper_height - 2 * margin_top)
        html_item.addFrame(html_frame)

        # Set HTML
        # From QGIS 2.6, we can set composer HTML with manual HTML
        if qgis_version() < 20600:
            html_path = unique_filename(
                prefix='report', suffix='.html', dir=temp_dir())
            html_to_file(html, file_path=html_path)
            html_url = QUrl.fromLocalFile(html_path)
            html_item.setUrl(html_url)
        else:
            # noinspection PyUnresolvedReferences
            html_item.setContentMode(QgsComposerHtml.ManualHtml)
            # noinspection PyUnresolvedReferences
            html_item.setResizeMode(QgsComposerHtml.RepeatUntilFinished)
            html_item.setHtml(html)
            html_item.loadHtml()

        composition.exportAsPDF(output_path)
        return output_path
示例#56
0
def analysis_summary(aggregate_hazard, analysis):
    """Compute the summary from the aggregate hazard to analysis.

    Source layer :
    | haz_id | haz_class | aggr_id | aggr_name | total_feature |

    Target layer :
    | analysis_name |

    Output layer :
    | analysis_name | count_hazard_class | affected_count | total |

    :param aggregate_hazard: The layer to aggregate vector layer.
    :type aggregate_hazard: QgsVectorLayer

    :param analysis: The target vector layer where to write statistics.
    :type analysis: QgsVectorLayer

    :return: The new target layer with summary.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    source_fields = aggregate_hazard.keywords['inasafe_fields']
    target_fields = analysis.keywords['inasafe_fields']

    target_compulsory_fields = [
        analysis_name_field,
    ]
    check_inputs(target_compulsory_fields, target_fields)

    source_compulsory_fields = [
        aggregation_id_field, aggregation_name_field, hazard_id_field,
        hazard_class_field, total_field
    ]
    check_inputs(source_compulsory_fields, source_fields)

    absolute_values = create_absolute_values_structure(aggregate_hazard,
                                                       ['all'])

    hazard_class = source_fields[hazard_class_field['key']]
    hazard_class_index = aggregate_hazard.fields().lookupField(hazard_class)
    unique_hazard = list(aggregate_hazard.uniqueValues(hazard_class_index))

    hazard_keywords = aggregate_hazard.keywords['hazard_keywords']
    hazard = hazard_keywords['hazard']
    classification = hazard_keywords['classification']

    exposure_keywords = aggregate_hazard.keywords['exposure_keywords']
    exposure = exposure_keywords['exposure']

    total = source_fields[total_field['key']]

    flat_table = FlatTable('hazard_class')

    # First loop over the aggregate_hazard layer
    request = QgsFeatureRequest()
    request.setSubsetOfAttributes([hazard_class, total],
                                  aggregate_hazard.fields())
    request.setFlags(QgsFeatureRequest.NoGeometry)
    for area in aggregate_hazard.getFeatures():
        hazard_value = area[hazard_class_index]
        value = area[total]
        if (value == '' or value is None or isnan(value)
                or (hasattr(value, 'isNull') and value.isNull())):
            # For isnan, see ticket #3812
            value = 0
        if (hazard_value == '' or hazard_value is None or
            (hasattr(hazard_value, 'isNull') and hazard_value.isNull())):
            hazard_value = 'NULL'
        flat_table.add_value(value, hazard_class=hazard_value)

        # We summarize every absolute values.
        for field, field_definition in list(absolute_values.items()):
            value = area[field]
            if (value == '' or value is None
                    or (hasattr(value, 'isNull') and value.isNull())):
                value = 0
            field_definition[0].add_value(value, all='all')

    analysis.startEditing()

    shift = analysis.fields().count()

    counts = [
        total_affected_field, total_not_affected_field, total_exposed_field,
        total_not_exposed_field, total_field
    ]

    dynamic_structure = [
        [hazard_count_field, unique_hazard],
    ]
    add_fields(analysis, absolute_values, counts, dynamic_structure)

    affected_sum = 0
    not_affected_sum = 0
    not_exposed_sum = 0

    # Summarization
    summary_values = {}
    for key, summary_rule in list(summary_rules.items()):
        input_field = summary_rule['input_field']
        case_field = summary_rule['case_field']
        if aggregate_hazard.fields().lookupField(input_field['field_name']) \
                == -1:
            continue
        if aggregate_hazard.fields().lookupField(case_field['field_name']) \
                == -1:
            continue

        summary_value = 0
        for area in aggregate_hazard.getFeatures():
            case_value = area[case_field['field_name']]
            if case_value in summary_rule['case_values']:
                summary_value += area[input_field['field_name']]

        summary_values[key] = summary_value

    for area in analysis.getFeatures(request):
        total = 0
        for i, val in enumerate(unique_hazard):
            if (val == '' or val is None
                    or (hasattr(val, 'isNull') and val.isNull())):
                val = 'NULL'
            sum = flat_table.get_value(hazard_class=val)
            total += sum
            analysis.changeAttributeValue(area.id(), shift + i, sum)

            affected = post_processor_affected_function(
                exposure=exposure,
                hazard=hazard,
                classification=classification,
                hazard_class=val)
            if affected == not_exposed_class['key']:
                not_exposed_sum += sum
            elif affected:
                affected_sum += sum
            else:
                not_affected_sum += sum

        # Total Affected field
        analysis.changeAttributeValue(area.id(), shift + len(unique_hazard),
                                      affected_sum)

        # Total Not affected field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 1,
                                      not_affected_sum)

        # Total Exposed field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 2,
                                      total - not_exposed_sum)

        # Total Not exposed field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 3,
                                      not_exposed_sum)

        # Total field
        analysis.changeAttributeValue(area.id(),
                                      shift + len(unique_hazard) + 4, total)

        # Any absolute postprocessors
        for i, field in enumerate(absolute_values.values()):
            value = field[0].get_value(all='all')
            analysis.changeAttributeValue(area.id(),
                                          shift + len(unique_hazard) + 5 + i,
                                          value)

        # Summarizer of custom attributes
        for key, summary_value in list(summary_values.items()):
            summary_field = summary_rules[key]['summary_field']
            field = create_field_from_definition(summary_field)
            analysis.addAttribute(field)
            field_index = analysis.fields().lookupField(field.name())
            # noinspection PyTypeChecker
            analysis.keywords['inasafe_fields'][summary_field['key']] = (
                summary_field['field_name'])

            analysis.changeAttributeValue(area.id(), field_index,
                                          summary_value)

    # Sanity check ± 1 to the result. Disabled for now as it seems ± 1 is not
    # enough. ET 13/02/17
    # total_computed = (
    #     affected_sum + not_affected_sum + not_exposed_sum)
    # if not -1 < (total_computed - total) < 1:
    #     raise ComputationError

    analysis.commitChanges()

    analysis.keywords['title'] = layer_purpose_analysis_impacted['name']
    if qgis_version() >= 21600:
        analysis.setName(analysis.keywords['title'])
    else:
        analysis.setLayerName(analysis.keywords['title'])
    analysis.keywords['layer_purpose'] = layer_purpose_analysis_impacted['key']

    check_layer(analysis)
    return analysis
示例#57
0
 def test_get_qgis_version(self):
     """Test we can get the version of QGIS"""
     version = qgis_version()
     message = 'Got version %s of QGIS, but at least 107000 is needed'
     assert version > 10700, message
示例#58
0
    def download(self):
        """Downloading the file.

        :returns: True if success, otherwise returns a tuple with format like
            this (QNetworkReply.NetworkError, error_message)

        :raises: IOError - when cannot create output_path
        """
        # Prepare output path
        self.output_file = QFile(self.output_path)
        if not self.output_file.open(QFile.WriteOnly):
            raise IOError(self.output_file.errorString())

        # Prepare downloaded buffer
        self.downloaded_file_buffer = QByteArray()

        # Request the url
        request = QNetworkRequest(self.url)
        self.reply = self.manager.get(request)
        self.reply.readyRead.connect(self.get_buffer)
        self.reply.finished.connect(self.write_data)
        self.manager.requestTimedOut.connect(self.request_timeout)

        if self.progress_dialog:
            # progress bar
            def progress_event(received, total):
                """Update progress.

                :param received: Data received so far.
                :type received: int

                :param total: Total expected data.
                :type total: int
                """
                # noinspection PyArgumentList
                QCoreApplication.processEvents()

                self.progress_dialog.adjustSize()

                human_received = humanize_file_size(received)
                human_total = humanize_file_size(total)

                label_text = tr(
                    "%s : %s of %s" %
                    (self.prefix_text, human_received, human_total))

                self.progress_dialog.setLabelText(label_text)
                self.progress_dialog.setMaximum(total)
                self.progress_dialog.setValue(received)

            # cancel
            def cancel_action():
                """Cancel download."""
                self.manager.deleteReply(self.reply)

            self.reply.downloadProgress.connect(progress_event)
            self.progress_dialog.canceled.connect(cancel_action)

        # Wait until finished
        # On Windows 32bit AND QGIS 2.2, self.reply.isFinished() always
        # returns False even after finished slot is called. So, that's why we
        # are adding self.finished_flag (see #864)
        while not self.reply.isFinished() and not self.finished_flag:
            # noinspection PyArgumentList
            QCoreApplication.processEvents()

        result = self.reply.error()

        if qgis_version() >= 21100:
            self.manager.deleteReply(self.reply)

        if result == QNetworkReply.NoError:
            return True, None

        elif result == QNetworkReply.UnknownNetworkError:
            return False, tr(
                'The network is unreachable. Please check your internet '
                'connection.')

        elif result == QNetworkReply.ProtocolUnknownError or \
                result == QNetworkReply.HostNotFoundError:
            LOGGER.exception('Host not found : %s' % self.url.encodedHost())
            return False, tr(
                'Sorry, the server is unreachable. Please try again later.')

        elif result == QNetworkReply.ContentNotFoundError:
            LOGGER.exception('Path not found : %s' % self.url.path())
            return False, tr('Sorry, the layer was not found on the server.')

        else:
            return result, self.reply.errorString()