def save_hazard_layer(self, hazard_path): # download or copy hazard path/url # It is a single tif file if not hazard_path and not os.path.exists(self.hazard_path): raise IOError("Hazard file not specified") if hazard_path: temp_hazard = download_file(hazard_path) shutil.copy(temp_hazard, self.hazard_path) # copy qml and metadata shutil.copy(self.ash_fixtures_dir("hazard.qml"), self.working_dir_path("hazard.qml")) keyword_io = KeywordIO() keywords = { "hazard_category": u"single_event", "keyword_version": u"3.5", "title": u"Ash Fall", "hazard": u"volcanic_ash", "continuous_hazard_unit": u"centimetres", "layer_geometry": u"raster", "layer_purpose": u"hazard", "layer_mode": u"continuous", } hazard_layer = read_qgis_layer(self.hazard_path, "Ash Fall") keyword_io.write_keywords(hazard_layer, keywords)
def generate_population_aggregation(self): # duplicate exposure data QgsVectorFileWriter.writeAsVectorFormat( self.exposure_layer.as_qgis_native(), self.population_aggregate_path, 'CP1250', None, 'ESRI Shapefile') population_aggregate = read_qgis_layer(self.population_aggregate_path, 'Impacted Population') shutil.copy(self.population_path.replace('.shp', '.xml'), self.population_aggregate_path.replace('.shp', '.xml')) # add affected population field population_aggregate.startEditing() field = QgsField(self.affect_field, QVariant.Int) field2 = QgsField(self.target_field, QVariant.Int) population_aggregate.dataProvider().addAttributes([field, field2]) population_aggregate.commitChanges() idx = population_aggregate.fieldNameIndex(self.affect_field) idx2 = population_aggregate.fieldNameIndex(self.target_field) impact_layer = self.impact_layer.as_qgis_native() keyword_io = KeywordIO() name_field = keyword_io.read_keywords(population_aggregate, 'area_name_field') attribute_field = keyword_io.read_keywords(population_aggregate, 'field') # calculate affected data district_dict = {} for f in impact_layer.getFeatures(): if f[self.target_field] >= 1: if f[name_field] in district_dict: district_dict[f[name_field]] += f[attribute_field] else: district_dict[f[name_field]] = f[attribute_field] population_aggregate.startEditing() for f in population_aggregate.getFeatures(): f[idx] = district_dict.get(f[name_field], 0) if f[idx] == 0: # mark as unaffected f[idx2] = 0 else: # mark as affected f[idx2] = 1 population_aggregate.updateFeature(f) population_aggregate.commitChanges() self.affected_aggregate = district_dict # calculate total affected people total_affected = 0 for k, v in district_dict.iteritems(): total_affected += v # calculate new minimum needs min_needs = self.impact_data.minimum_needs for k, v in min_needs.iteritems(): for need in v: need['amount'] = need['value'] * total_affected
def save_hazard_layer(self, hazard_path): # download or copy hazard path/url # It is a single tif file if not hazard_path and not os.path.exists(self.hazard_path): raise IOError('Hazard file not specified') if hazard_path: temp_hazard = download_file(hazard_path) shutil.copy(temp_hazard, self.hazard_path) # copy qml and metadata shutil.copy(self.ash_fixtures_dir('hazard.qml'), self.working_dir_path('hazard.qml')) keyword_io = KeywordIO() keywords = { 'hazard_category': u'single_event', 'keyword_version': u'3.5', 'title': u'Ash Fall', 'hazard': u'volcanic_ash', 'continuous_hazard_unit': u'centimetres', 'layer_geometry': u'raster', 'layer_purpose': u'hazard', 'layer_mode': u'continuous' } hazard_layer = read_qgis_layer(self.hazard_path, 'Ash Fall') keyword_io.write_keywords(hazard_layer, keywords)
def generate_report(self): # Generate pdf report from impact if not self.impact_exists: # Cannot generate report when no impact layer present return layer_registry = QgsMapLayerRegistry.instance() layer_registry.removeAllMapLayers() impact_qgis_layer = read_qgis_layer(self.impact_layer.filename) layer_registry.addMapLayer(impact_qgis_layer) CANVAS.setExtent(impact_qgis_layer.extent()) CANVAS.refresh() report = ImpactReport(IFACE, template=None, layer=impact_qgis_layer) report.print_map_to_pdf(self.map_report_path) report.print_impact_table(self.table_report_path) layer_registry.removeAllMapLayers()
def generate_report(self): # Generate pdf report from impact/hazard LOGGER.info("Generating report") if not self.impact_exists: # Cannot generate report when no impact layer present LOGGER.info("Cannot Generate report when no impact present.") return project_instance = QgsProject.instance() project_instance.setFileName(self.project_path) project_instance.read() # get layer registry layer_registry = QgsMapLayerRegistry.instance() layer_registry.removeAllMapLayers() # Set up the map renderer that will be assigned to the composition map_renderer = CANVAS.mapRenderer() # Enable on the fly CRS transformations map_renderer.setProjectionsEnabled(True) default_crs = map_renderer.destinationCrs() crs = QgsCoordinateReferenceSystem("EPSG:4326") map_renderer.setDestinationCrs(crs) # add place name layer layer_registry.addMapLayer(self.cities_layer, False) # add airport layer layer_registry.addMapLayer(self.airport_layer, False) # add volcano layer layer_registry.addMapLayer(self.volcano_layer, False) # add impact layer hazard_layer = read_qgis_layer(self.hazard_path, self.tr("People Affected")) layer_registry.addMapLayer(hazard_layer, False) # add basemap layer layer_registry.addMapLayer(self.highlight_base_layer, False) # add basemap layer layer_registry.addMapLayer(self.overview_layer, False) CANVAS.setExtent(hazard_layer.extent()) CANVAS.refresh() template_path = self.ash_fixtures_dir("realtime-ash.qpt") with open(template_path) as f: template_content = f.read() document = QDomDocument() document.setContent(template_content) # Now set up the composition # map_settings = QgsMapSettings() # composition = QgsComposition(map_settings) composition = QgsComposition(map_renderer) subtitution_map = self.event_dict() LOGGER.debug(subtitution_map) # load composition object from template result = composition.loadFromTemplate(document, subtitution_map) if not result: LOGGER.exception("Error loading template %s with keywords\n %s", template_path, subtitution_map) raise MapComposerError # get main map canvas on the composition and set extent map_impact = composition.getComposerItemById("map-impact") if map_impact: map_impact.zoomToExtent(hazard_layer.extent()) map_impact.renderModeUpdateCachedImage() else: LOGGER.exception("Map canvas could not be found in template %s", template_path) raise MapComposerError # get overview map canvas on the composition and set extent map_overall = composition.getComposerItemById("map-overall") if map_overall: map_overall.setLayerSet([self.overview_layer.id()]) # this is indonesia extent indonesia_extent = QgsRectangle( 94.0927980005593554, -15.6629591962689343, 142.0261493318861312, 10.7379406374101816 ) map_overall.zoomToExtent(indonesia_extent) map_overall.renderModeUpdateCachedImage() else: LOGGER.exception("Map canvas could not be found in template %s", template_path) raise MapComposerError # setup impact table self.render_population_table() self.render_nearby_table() self.render_landcover_table() impact_table = composition.getComposerItemById("table-impact") if impact_table is None: message = "table-impact composer item could not be found" LOGGER.exception(message) raise MapComposerError(message) impacts_html = composition.getComposerHtmlByItem(impact_table) if impacts_html is None: message = "Impacts QgsComposerHtml could not be found" LOGGER.exception(message) raise MapComposerError(message) impacts_html.setUrl(QUrl(self.population_html_path)) # setup nearby table nearby_table = composition.getComposerItemById("table-nearby") if nearby_table is None: message = "table-nearby composer item could not be found" LOGGER.exception(message) raise MapComposerError(message) nearby_html = composition.getComposerHtmlByItem(nearby_table) if nearby_html is None: message = "Nearby QgsComposerHtml could not be found" LOGGER.exception(message) raise MapComposerError(message) nearby_html.setUrl(QUrl(self.nearby_html_path)) # setup landcover table landcover_table = composition.getComposerItemById("table-landcover") if landcover_table is None: message = "table-landcover composer item could not be found" LOGGER.exception(message) raise MapComposerError(message) landcover_html = composition.getComposerHtmlByItem(landcover_table) if landcover_html is None: message = "Landcover QgsComposerHtml could not be found" LOGGER.exception(message) raise MapComposerError(message) landcover_html.setUrl(QUrl(self.landcover_html_path)) # setup logos logos_id = ["logo-bnpb", "logo-geologi"] for logo_id in logos_id: logo_picture = composition.getComposerItemById(logo_id) if logo_picture is None: message = "%s composer item could not be found" % logo_id LOGGER.exception(message) raise MapComposerError(message) pic_path = os.path.basename(logo_picture.picturePath()) pic_path = os.path.join("logo", pic_path) logo_picture.setPicturePath(self.ash_fixtures_dir(pic_path)) # save a pdf composition.exportAsPDF(self.map_report_path) project_instance.write(QFileInfo(self.project_path)) layer_registry.removeAllMapLayers() map_renderer.setDestinationCrs(default_crs) map_renderer.setProjectionsEnabled(False) LOGGER.info("Report generation completed.")
def __init__( self, event_time=None, volcano_name=None, volcano_location=None, eruption_height=None, region=None, alert_level=None, locale=None, working_dir=None, hazard_path=None, overview_path=None, highlight_base_path=None, population_path=None, volcano_path=None, landcover_path=None, cities_path=None, airport_path=None, ): """ :param event_time: :param volcano_name: :param volcano_location: :param eruption_height: :param region: :param alert_level: :param locale: :param working_dir: :param hazard_path: It can be a url or local file path :param population_path: :param landcover_path: :param cities_path: :param airport_path: """ QObject.__init__(self) if event_time: self.time = event_time else: self.time = datetime.datetime.now().replace(tzinfo=pytz.timezone("Asia/Jakarta")) # Check timezone awareness if not self.time.tzinfo: raise Exception("Need timezone aware object for event time") self.volcano_name = volcano_name self.volcano_location = volcano_location if self.volcano_location: self.longitude = self.volcano_location[0] self.latitude = self.volcano_location[1] else: self.longitude = None self.latitude = None self.erupction_height = eruption_height self.region = region self.alert_level = alert_level self.locale = locale if not self.locale: self.locale = "en" if not working_dir: raise Exception("Working directory can't be empty") self.working_dir = working_dir if not os.path.exists(self.working_dir_path()): os.makedirs(self.working_dir_path()) # save hazard layer self.hazard_path = self.working_dir_path("hazard.tif") self.save_hazard_layer(hazard_path) if not os.path.exists(self.hazard_path): IOError("Hazard path doesn't exists") self.population_html_path = self.working_dir_path("population-table.html") self.nearby_html_path = self.working_dir_path("nearby-table.html") self.landcover_html_path = self.working_dir_path("landcover-table.html") self.map_report_path = self.working_dir_path("report.pdf") self.project_path = self.working_dir_path("project.qgs") self.impact_exists = None self.locale = "en" self.population_path = population_path self.cities_path = cities_path self.airport_path = airport_path self.landcover_path = landcover_path self.volcano_path = volcano_path self.highlight_base_path = highlight_base_path self.overview_path = overview_path # load layers self.hazard_layer = read_qgis_layer(self.hazard_path, "Ash Fall") self.population_layer = read_qgis_layer(self.population_path, "Population") self.landcover_layer = read_qgis_layer(self.landcover_path, "Landcover") self.cities_layer = read_qgis_layer(self.cities_path, "Cities") self.airport_layer = read_qgis_layer(self.airport_path, "Airport") self.volcano_layer = read_qgis_layer(self.volcano_path, "Volcano") self.highlight_base_layer = read_qgis_layer(self.highlight_base_path, "Base Map") self.overview_layer = read_qgis_layer(self.overview_path, "Overview") # Write metadata for self reference self.write_metadata()
def render_nearby_table(self): hazard_mapping = {0: "Very Low", 1: "Low", 2: "Moderate", 3: "High", 4: "Very High"} # load PLACES keyword_io = KeywordIO() try: cities_impact = read_qgis_layer(self.working_dir_path("cities_impact.shp"), "Cities") hazard = keyword_io.read_keywords(cities_impact, "target_field") hazard_field_index = cities_impact.fieldNameIndex(hazard) name_field = keyword_io.read_keywords(self.cities_layer, "name_field") name_field_index = cities_impact.fieldNameIndex(name_field) try: population_field = keyword_io.read_keywords(self.cities_layer, "population_field") population_field_index = cities_impact.fieldNameIndex(population_field) except KeywordNotFoundError: population_field = None population_field_index = None table_places = [] for f in cities_impact.getFeatures(): haz_class = f.attributes()[hazard_field_index] city_name = f.attributes()[name_field_index] if population_field_index >= 0: city_pop = f.attributes()[population_field_index] else: city_pop = 1 # format: # [ # 'hazard class', # 'city's population', # 'city's name', # 'the type' # ] haz = hazard_mapping[haz_class] item = { "class": haz_class, "hazard": haz, "css": haz.lower().replace(" ", "-"), "population": format_int(population_rounding(city_pop / 1000)), "name": city_name.title(), "type": "places", } table_places.append(item) # sort table by hazard zone, then population table_places = sorted(table_places, key=lambda x: (-x["class"], -x["population"])) except Exception as e: LOGGER.exception(e) table_places = [] # load AIRPORTS try: airport_impact = read_qgis_layer(self.working_dir_path("airport_impact.shp"), "Airport") hazard = keyword_io.read_keywords(airport_impact, "target_field") hazard_field_index = airport_impact.fieldNameIndex(hazard) name_field = keyword_io.read_keywords(self.airport_layer, "name_field") name_field_index = airport_impact.fieldNameIndex(name_field) # airport doesnt have population, so enter 0 for population table_airports = [] for f in airport_impact.getFeatures(): haz_class = f.attributes()[hazard_field_index] airport_name = f.attributes()[name_field_index] haz = hazard_mapping[haz_class] item = { "class": haz_class, "hazard": haz, "css": haz.lower().replace(" ", "-"), "population": 0, "name": airport_name.title(), "type": "airport", } table_airports.append(item) # Sort by hazard class table_airports = sorted(table_airports, key=lambda x: -x["class"]) except Exception as e: LOGGER.exception(e) table_airports = [] # decide which to show # maximum 2 airport max_airports = 2 airport_count = min(max_airports, len(table_airports)) # maximum total 7 entries to show max_rows = 6 places_count = min(len(table_places), max_rows - airport_count) # get top airport table_airports = table_airports[:airport_count] # get top places table_places = table_places[:places_count] item_list = table_places + table_airports # sort entry by hazard level item_list = sorted(item_list, key=lambda x: (-x["class"], -x["population"])) nearby_template = self.ash_fixtures_dir("nearby-table.template.html") with open(nearby_template) as f: template = Template(f.read()) # generate table here html_string = template.render(item_list=item_list) with open(self.nearby_html_path, "w") as f: f.write(html_string) # copy airport logo shutil.copy(self.ash_fixtures_dir("logo/airport.jpg"), self.working_dir_path("airport.jpg"))
def generate_report(self): # Generate pdf report from impact/hazard if not self.impact_exists: # Cannot generate report when no impact layer present return project_path = os.path.join(self.report_path, 'project-%s.qgs' % self.locale) project_instance = QgsProject.instance() project_instance.setFileName(project_path) project_instance.read() # Set up the map renderer that will be assigned to the composition map_renderer = CANVAS.mapRenderer() # Set the labelling engine for the canvas labelling_engine = QgsPalLabeling() map_renderer.setLabelingEngine(labelling_engine) # Enable on the fly CRS transformations map_renderer.setProjectionsEnabled(True) default_crs = map_renderer.destinationCrs() crs = QgsCoordinateReferenceSystem('EPSG:4326') map_renderer.setDestinationCrs(crs) # get layer registry layer_registry = QgsMapLayerRegistry.instance() layer_registry.removeAllMapLayers() # add impact layer population_affected_layer = read_qgis_layer( self.population_aggregate_path, self.tr('People Affected')) layer_registry.addMapLayer(population_affected_layer, True) # add boundary mask boundary_mask = read_qgis_layer( self.flood_fixtures_dir('boundary-mask.shp')) layer_registry.addMapLayer(boundary_mask, False) # add hazard layer hazard_layer = read_qgis_layer(self.hazard_path, self.tr('Flood Depth (cm)')) layer_registry.addMapLayer(hazard_layer, True) # add boundary layer boundary_layer = read_qgis_layer( self.flood_fixtures_dir('boundary-5.shp')) layer_registry.addMapLayer(boundary_layer, False) CANVAS.setExtent(boundary_layer.extent()) CANVAS.refresh() # add basemap layer # this code uses OpenlayersPlugin base_map = QgsRasterLayer(self.flood_fixtures_dir('jakarta.jpg')) layer_registry.addMapLayer(base_map, False) CANVAS.refresh() template_path = self.flood_fixtures_dir('realtime-flood.qpt') with open(template_path) as f: template_content = f.read() document = QDomDocument() document.setContent(template_content) # set destination CRS to Jakarta CRS # EPSG:32748 # This allows us to use the scalebar in meter unit scale crs = QgsCoordinateReferenceSystem('EPSG:32748') map_renderer.setDestinationCrs(crs) # Now set up the composition composition = QgsComposition(map_renderer) subtitution_map = self.event_dict() LOGGER.debug(subtitution_map) # load composition object from template result = composition.loadFromTemplate(document, subtitution_map) if not result: LOGGER.exception('Error loading template %s with keywords\n %s', template_path, subtitution_map) raise MapComposerError # get main map canvas on the composition and set extent map_canvas = composition.getComposerItemById('map-canvas') if map_canvas: map_canvas.setNewExtent(map_canvas.currentMapExtent()) map_canvas.renderModeUpdateCachedImage() else: LOGGER.exception('Map canvas could not be found in template %s', template_path) raise MapComposerError # get map legend on the composition map_legend = composition.getComposerItemById('map-legend') if map_legend: # show only legend for Flood Depth # ''.star # showed_legend = [layer_id for layer_id in map_renderer.layerSet() # if layer_id.startswith('Flood_Depth')] # LOGGER.info(showed_legend) # LOGGER.info(map_renderer.layerSet()) # LOGGER.info(hazard_layer.id()) # map_legend.model().setLayerSet(showed_legend) # map_legend.modelV2().clear() # print dir(map_legend.modelV2()) # map_legend.setLegendFilterByMapEnabled(True) map_legend.model().setLayerSet( [hazard_layer.id(), population_affected_layer.id()]) else: LOGGER.exception('Map legend could not be found in template %s', template_path) raise MapComposerError content_analysis = composition.getComposerItemById( 'content-analysis-result') if not content_analysis: message = 'Content analysis composer item could not be found' LOGGER.exception(message) raise MapComposerError(message) content_analysis_html = content_analysis.multiFrame() if content_analysis_html: # set url to generated html analysis_html_path = self.generate_analysis_result_html() # We're using manual HTML to avoid memory leak and segfault # happened when using Url Mode content_analysis_html.setContentMode(QgsComposerHtml.ManualHtml) with open(analysis_html_path) as f: content_analysis_html.setHtml(f.read()) content_analysis_html.loadHtml() else: message = 'Content analysis HTML not found in template' LOGGER.exception(message) raise MapComposerError(message) # save a pdf composition.exportAsPDF(self.map_report_path) project_instance.write(QFileInfo(project_path)) layer_registry.removeAllMapLayers() map_renderer.setDestinationCrs(default_crs) map_renderer.setProjectionsEnabled(False)
def generate_report(self): # Generate pdf report from impact/hazard if not self.impact_exists: # Cannot generate report when no impact layer present return project_path = os.path.join( self.report_path, 'project-%s.qgs' % self.locale) project_instance = QgsProject.instance() project_instance.setFileName(project_path) project_instance.read() # Set up the map renderer that will be assigned to the composition map_renderer = CANVAS.mapRenderer() # Set the labelling engine for the canvas labelling_engine = QgsPalLabeling() map_renderer.setLabelingEngine(labelling_engine) # Enable on the fly CRS transformations map_renderer.setProjectionsEnabled(True) default_crs = map_renderer.destinationCrs() crs = QgsCoordinateReferenceSystem('EPSG:4326') map_renderer.setDestinationCrs(crs) # get layer registry layer_registry = QgsMapLayerRegistry.instance() layer_registry.removeAllMapLayers() # add impact layer population_affected_layer = read_qgis_layer( self.population_aggregate_path, self.tr('People Affected')) layer_registry.addMapLayer(population_affected_layer, True) # add boundary mask boundary_mask = read_qgis_layer( self.flood_fixtures_dir('boundary-mask.shp')) layer_registry.addMapLayer(boundary_mask, False) # add hazard layer hazard_layer = read_qgis_layer( self.hazard_path, self.tr('Flood Depth (cm)')) layer_registry.addMapLayer(hazard_layer, True) # add boundary layer boundary_layer = read_qgis_layer( self.flood_fixtures_dir('boundary-5.shp')) layer_registry.addMapLayer(boundary_layer, False) CANVAS.setExtent(boundary_layer.extent()) CANVAS.refresh() # add basemap layer # this code uses OpenlayersPlugin base_map = QgsRasterLayer( self.flood_fixtures_dir('jakarta.jpg')) layer_registry.addMapLayer(base_map, False) CANVAS.refresh() template_path = self.flood_fixtures_dir('realtime-flood.qpt') with open(template_path) as f: template_content = f.read() document = QDomDocument() document.setContent(template_content) # set destination CRS to Jakarta CRS # EPSG:32748 # This allows us to use the scalebar in meter unit scale crs = QgsCoordinateReferenceSystem('EPSG:32748') map_renderer.setDestinationCrs(crs) # Now set up the composition composition = QgsComposition(map_renderer) subtitution_map = self.event_dict() LOGGER.debug(subtitution_map) # load composition object from template result = composition.loadFromTemplate(document, subtitution_map) if not result: LOGGER.exception( 'Error loading template %s with keywords\n %s', template_path, subtitution_map) raise MapComposerError # get main map canvas on the composition and set extent map_canvas = composition.getComposerItemById('map-canvas') if map_canvas: map_canvas.setNewExtent(map_canvas.currentMapExtent()) map_canvas.renderModeUpdateCachedImage() else: LOGGER.exception('Map canvas could not be found in template %s', template_path) raise MapComposerError # get map legend on the composition map_legend = composition.getComposerItemById('map-legend') if map_legend: # show only legend for Flood Depth # ''.star # showed_legend = [layer_id for layer_id in map_renderer.layerSet() # if layer_id.startswith('Flood_Depth')] # LOGGER.info(showed_legend) # LOGGER.info(map_renderer.layerSet()) # LOGGER.info(hazard_layer.id()) # map_legend.model().setLayerSet(showed_legend) # map_legend.modelV2().clear() # print dir(map_legend.modelV2()) # map_legend.setLegendFilterByMapEnabled(True) map_legend.model().setLayerSet( [hazard_layer.id(), population_affected_layer.id()]) else: LOGGER.exception('Map legend could not be found in template %s', template_path) raise MapComposerError content_analysis = composition.getComposerItemById( 'content-analysis-result') if not content_analysis: message = 'Content analysis composer item could not be found' LOGGER.exception(message) raise MapComposerError(message) content_analysis_html = content_analysis.multiFrame() if content_analysis_html: # set url to generated html analysis_html_path = self.generate_analysis_result_html() # We're using manual HTML to avoid memory leak and segfault # happened when using Url Mode content_analysis_html.setContentMode(QgsComposerHtml.ManualHtml) with open(analysis_html_path) as f: content_analysis_html.setHtml(f.read()) content_analysis_html.loadHtml() else: message = 'Content analysis HTML not found in template' LOGGER.exception(message) raise MapComposerError(message) # save a pdf composition.exportAsPDF(self.map_report_path) project_instance.write(QFileInfo(project_path)) layer_registry.removeAllMapLayers() map_renderer.setDestinationCrs(default_crs) map_renderer.setProjectionsEnabled(False)
def generate_population_aggregation(self): # duplicate exposure data QgsVectorFileWriter.writeAsVectorFormat( self.exposure_layer.as_qgis_native(), self.population_aggregate_path, 'CP1250', None, 'ESRI Shapefile') population_aggregate = read_qgis_layer( self.population_aggregate_path, 'Impacted Population') shutil.copy( self.population_path.replace('.shp', '.xml'), self.population_aggregate_path.replace('.shp', '.xml')) # add affected population field population_aggregate.startEditing() field = QgsField(self.affect_field, QVariant.Int) field2 = QgsField(self.target_field, QVariant.Int) population_aggregate.dataProvider().addAttributes([field, field2]) population_aggregate.commitChanges() idx = population_aggregate.fieldNameIndex(self.affect_field) idx2 = population_aggregate.fieldNameIndex(self.target_field) impact_layer = self.impact_layer.as_qgis_native() keyword_io = KeywordIO() name_field = keyword_io.read_keywords( population_aggregate, 'area_name_field') attribute_field = keyword_io.read_keywords( population_aggregate, 'field') # calculate affected data district_dict = {} for f in impact_layer.getFeatures(): if f[self.target_field] >= 1: if f[name_field] in district_dict: district_dict[f[name_field]] += f[attribute_field] else: district_dict[f[name_field]] = f[attribute_field] population_aggregate.startEditing() for f in population_aggregate.getFeatures(): f[idx] = district_dict.get(f[name_field], 0) if f[idx] == 0: # mark as unaffected f[idx2] = 0 else: # mark as affected f[idx2] = 1 population_aggregate.updateFeature(f) population_aggregate.commitChanges() self.affected_aggregate = district_dict # calculate total affected people total_affected = 0 for k, v in district_dict.iteritems(): total_affected += v # calculate new minimum needs min_needs = self.impact_data.minimum_needs for k, v in min_needs.iteritems(): for need in v: need['amount'] = need['value'] * total_affected
def generate_report(self): # Generate pdf report from impact/hazard LOGGER.info('Generating report') if not self.impact_exists: # Cannot generate report when no impact layer present LOGGER.info('Cannot Generate report when no impact present.') return project_instance = QgsProject.instance() project_instance.setFileName(self.project_path) project_instance.read() # get layer registry layer_registry = QgsMapLayerRegistry.instance() layer_registry.removeAllMapLayers() # Set up the map renderer that will be assigned to the composition map_renderer = CANVAS.mapRenderer() # Enable on the fly CRS transformations map_renderer.setProjectionsEnabled(True) default_crs = map_renderer.destinationCrs() crs = QgsCoordinateReferenceSystem('EPSG:4326') map_renderer.setDestinationCrs(crs) # add place name layer layer_registry.addMapLayer(self.cities_layer, False) # add airport layer layer_registry.addMapLayer(self.airport_layer, False) # add volcano layer layer_registry.addMapLayer(self.volcano_layer, False) # add impact layer hazard_layer = read_qgis_layer(self.hazard_path, self.tr('People Affected')) layer_registry.addMapLayer(hazard_layer, False) # add basemap layer layer_registry.addMapLayer(self.highlight_base_layer, False) # add basemap layer layer_registry.addMapLayer(self.overview_layer, False) CANVAS.setExtent(hazard_layer.extent()) CANVAS.refresh() template_path = self.ash_fixtures_dir('realtime-ash.qpt') with open(template_path) as f: template_content = f.read() document = QDomDocument() document.setContent(template_content) # Now set up the composition # map_settings = QgsMapSettings() # composition = QgsComposition(map_settings) composition = QgsComposition(map_renderer) subtitution_map = self.event_dict() LOGGER.debug(subtitution_map) # load composition object from template result = composition.loadFromTemplate(document, subtitution_map) if not result: LOGGER.exception('Error loading template %s with keywords\n %s', template_path, subtitution_map) raise MapComposerError # get main map canvas on the composition and set extent map_impact = composition.getComposerItemById('map-impact') if map_impact: map_impact.zoomToExtent(hazard_layer.extent()) map_impact.renderModeUpdateCachedImage() else: LOGGER.exception('Map canvas could not be found in template %s', template_path) raise MapComposerError # get overview map canvas on the composition and set extent map_overall = composition.getComposerItemById('map-overall') if map_overall: map_overall.setLayerSet([self.overview_layer.id()]) # this is indonesia extent indonesia_extent = QgsRectangle(94.0927980005593554, -15.6629591962689343, 142.0261493318861312, 10.7379406374101816) map_overall.zoomToExtent(indonesia_extent) map_overall.renderModeUpdateCachedImage() else: LOGGER.exception('Map canvas could not be found in template %s', template_path) raise MapComposerError # setup impact table self.render_population_table() self.render_nearby_table() self.render_landcover_table() impact_table = composition.getComposerItemById('table-impact') if impact_table is None: message = 'table-impact composer item could not be found' LOGGER.exception(message) raise MapComposerError(message) impacts_html = composition.getComposerHtmlByItem(impact_table) if impacts_html is None: message = 'Impacts QgsComposerHtml could not be found' LOGGER.exception(message) raise MapComposerError(message) impacts_html.setUrl(QUrl(self.population_html_path)) # setup nearby table nearby_table = composition.getComposerItemById('table-nearby') if nearby_table is None: message = 'table-nearby composer item could not be found' LOGGER.exception(message) raise MapComposerError(message) nearby_html = composition.getComposerHtmlByItem(nearby_table) if nearby_html is None: message = 'Nearby QgsComposerHtml could not be found' LOGGER.exception(message) raise MapComposerError(message) nearby_html.setUrl(QUrl(self.nearby_html_path)) # setup landcover table landcover_table = composition.getComposerItemById('table-landcover') if landcover_table is None: message = 'table-landcover composer item could not be found' LOGGER.exception(message) raise MapComposerError(message) landcover_html = composition.getComposerHtmlByItem(landcover_table) if landcover_html is None: message = 'Landcover QgsComposerHtml could not be found' LOGGER.exception(message) raise MapComposerError(message) landcover_html.setUrl(QUrl(self.landcover_html_path)) # setup logos logos_id = ['logo-bnpb', 'logo-geologi'] for logo_id in logos_id: logo_picture = composition.getComposerItemById(logo_id) if logo_picture is None: message = '%s composer item could not be found' % logo_id LOGGER.exception(message) raise MapComposerError(message) pic_path = os.path.basename(logo_picture.picturePath()) pic_path = os.path.join('logo', pic_path) logo_picture.setPicturePath(self.ash_fixtures_dir(pic_path)) # save a pdf composition.exportAsPDF(self.map_report_path) project_instance.write(QFileInfo(self.project_path)) layer_registry.removeAllMapLayers() map_renderer.setDestinationCrs(default_crs) map_renderer.setProjectionsEnabled(False) LOGGER.info('Report generation completed.')
def __init__(self, event_time=None, volcano_name=None, volcano_location=None, eruption_height=None, region=None, alert_level=None, locale=None, working_dir=None, hazard_path=None, overview_path=None, highlight_base_path=None, population_path=None, volcano_path=None, landcover_path=None, cities_path=None, airport_path=None): """ :param event_time: :param volcano_name: :param volcano_location: :param eruption_height: :param region: :param alert_level: :param locale: :param working_dir: :param hazard_path: It can be a url or local file path :param population_path: :param landcover_path: :param cities_path: :param airport_path: """ QObject.__init__(self) if event_time: self.time = event_time else: self.time = datetime.datetime.now().replace( tzinfo=pytz.timezone('Asia/Jakarta')) # Check timezone awareness if not self.time.tzinfo: raise Exception('Need timezone aware object for event time') self.volcano_name = volcano_name self.volcano_location = volcano_location if self.volcano_location: self.longitude = self.volcano_location[0] self.latitude = self.volcano_location[1] else: self.longitude = None self.latitude = None self.erupction_height = eruption_height self.region = region self.alert_level = alert_level self.locale = locale if not self.locale: self.locale = 'en' if not working_dir: raise Exception("Working directory can't be empty") self.working_dir = working_dir if not os.path.exists(self.working_dir_path()): os.makedirs(self.working_dir_path()) # save hazard layer self.hazard_path = self.working_dir_path('hazard.tif') self.save_hazard_layer(hazard_path) if not os.path.exists(self.hazard_path): IOError("Hazard path doesn't exists") self.population_html_path = self.working_dir_path( 'population-table.html') self.nearby_html_path = self.working_dir_path('nearby-table.html') self.landcover_html_path = self.working_dir_path( 'landcover-table.html') self.map_report_path = self.working_dir_path('report.pdf') self.project_path = self.working_dir_path('project.qgs') self.impact_exists = None self.locale = 'en' self.population_path = population_path self.cities_path = cities_path self.airport_path = airport_path self.landcover_path = landcover_path self.volcano_path = volcano_path self.highlight_base_path = highlight_base_path self.overview_path = overview_path # load layers self.hazard_layer = read_qgis_layer(self.hazard_path, 'Ash Fall') self.population_layer = read_qgis_layer(self.population_path, 'Population') self.landcover_layer = read_qgis_layer(self.landcover_path, 'Landcover') self.cities_layer = read_qgis_layer(self.cities_path, 'Cities') self.airport_layer = read_qgis_layer(self.airport_path, 'Airport') self.volcano_layer = read_qgis_layer(self.volcano_path, 'Volcano') self.highlight_base_layer = read_qgis_layer(self.highlight_base_path, 'Base Map') self.overview_layer = read_qgis_layer(self.overview_path, 'Overview') # Write metadata for self reference self.write_metadata()
def render_nearby_table(self): hazard_mapping = { 0: 'Very Low', 1: 'Low', 2: 'Moderate', 3: 'High', 4: 'Very High' } # load PLACES keyword_io = KeywordIO() try: cities_impact = read_qgis_layer( self.working_dir_path('cities_impact.shp'), 'Cities') hazard = keyword_io.read_keywords(cities_impact, 'target_field') hazard_field_index = cities_impact.fieldNameIndex(hazard) name_field = keyword_io.read_keywords(self.cities_layer, 'name_field') name_field_index = cities_impact.fieldNameIndex(name_field) try: population_field = keyword_io.read_keywords( self.cities_layer, 'population_field') population_field_index = cities_impact.fieldNameIndex( population_field) except KeywordNotFoundError: population_field = None population_field_index = None table_places = [] for f in cities_impact.getFeatures(): haz_class = f.attributes()[hazard_field_index] city_name = f.attributes()[name_field_index] if population_field_index >= 0: city_pop = f.attributes()[population_field_index] else: city_pop = 1 # format: # [ # 'hazard class', # 'city's population', # 'city's name', # 'the type' # ] haz = hazard_mapping[haz_class] item = { 'class': haz_class, 'hazard': haz, 'css': haz.lower().replace(' ', '-'), 'population': format_int(population_rounding(city_pop / 1000)), 'name': city_name.title(), 'type': 'places' } table_places.append(item) # sort table by hazard zone, then population table_places = sorted(table_places, key=lambda x: (-x['class'], -x['population'])) except Exception as e: LOGGER.exception(e) table_places = [] # load AIRPORTS try: airport_impact = read_qgis_layer( self.working_dir_path('airport_impact.shp'), 'Airport') hazard = keyword_io.read_keywords(airport_impact, 'target_field') hazard_field_index = airport_impact.fieldNameIndex(hazard) name_field = keyword_io.read_keywords(self.airport_layer, 'name_field') name_field_index = airport_impact.fieldNameIndex(name_field) # airport doesnt have population, so enter 0 for population table_airports = [] for f in airport_impact.getFeatures(): haz_class = f.attributes()[hazard_field_index] airport_name = f.attributes()[name_field_index] haz = hazard_mapping[haz_class] item = { 'class': haz_class, 'hazard': haz, 'css': haz.lower().replace(' ', '-'), 'population': 0, 'name': airport_name.title(), 'type': 'airport' } table_airports.append(item) # Sort by hazard class table_airports = sorted(table_airports, key=lambda x: -x['class']) except Exception as e: LOGGER.exception(e) table_airports = [] # decide which to show # maximum 2 airport max_airports = 2 airport_count = min(max_airports, len(table_airports)) # maximum total 7 entries to show max_rows = 6 places_count = min(len(table_places), max_rows - airport_count) # get top airport table_airports = table_airports[:airport_count] # get top places table_places = table_places[:places_count] item_list = table_places + table_airports # sort entry by hazard level item_list = sorted(item_list, key=lambda x: (-x['class'], -x['population'])) nearby_template = self.ash_fixtures_dir('nearby-table.template.html') with open(nearby_template) as f: template = Template(f.read()) # generate table here html_string = template.render(item_list=item_list) with open(self.nearby_html_path, 'w') as f: f.write(html_string) # copy airport logo shutil.copy(self.ash_fixtures_dir('logo/airport.jpg'), self.working_dir_path('airport.jpg'))