def content(): """Helper method that returns just the content. This method was added so that the text could be reused in the dock_help module. .. versionadded:: 3.2.3 :returns: A message object without brand element. :rtype: safe.messaging.message.Message """ message = m.Message() paragraph = m.Paragraph(m.Image('file:///%s/img/screenshots/' 'petabencana-screenshot.png' % resources_path()), style_class='text-center') message.add(paragraph) link = m.Link('https://petabencana.id', 'PetaBencana.id') body = m.Paragraph( tr('This tool will fetch current flood data for Jakarta from '), link) tips = m.BulletedList() tips.add( tr('Check the output directory is correct. Note that the saved ' 'dataset will be called jakarta_flood.shp (and associated files).')) tips.add( tr('If you wish you can specify a prefix to ' 'add in front of this default name. For example using a prefix ' 'of \'foo-\' will cause the downloaded files to be saved as e.g. ' '\'foo-rw-jakarta-flood.shp\'. Note that the only allowed prefix ' 'characters are A-Z, a-z, 0-9 and the characters \'-\' and \'_\'. ' 'You can leave this blank if you prefer.')) tips.add( tr('If a dataset already exists in the output directory it will be ' 'overwritten if the "overwrite existing files" checkbox is ticked.') ) tips.add( tr('If the "include date/time in output filename" option is ticked, ' 'the filename will be prefixed with a time stamp e.g. ' '\'foo-22-Mar-2015-08-01-2015-rw-jakarta-flood.shp\' where the date ' 'timestamp is in the form DD-MMM-YYYY.')) tips.add( tr('This tool requires a working internet connection and fetching ' 'data will consume your bandwidth.')) tips.add( m.Link(production_api['help_url'], text=tr( 'Downloaded data is copyright the PetaBencana contributors' ' (click for more info).'))) message.add(body) message.add(tips) return message
def _create_section_header(message, table_of_contents, element_id, text, heading_level=1): # Warning a side effect here is that the SECTION_STYLE is updated # when setting style as we modify the id so we have to make a deep copy style = copy.deepcopy(HEADING_LOOKUPS[heading_level]) style['element_id'] = element_id HEADING_COUNTS[heading_level] += 1 # Reset the heading counts for headings below this level # Also calculate the index of the TOC entry index_number = '' for key in HEADING_COUNTS.keys(): if key > heading_level: HEADING_COUNTS[key] = 0 else: index_number += str(HEADING_COUNTS[key]) + '.' header = m.Heading(text, **style) link = m.Link('#%s' % element_id, index_number + ' ' + text) # See bootstrap docs for ml-1 explanation # https://v4-alpha.getbootstrap.com/utilities/spacing/#examples paragraph = m.Paragraph(link, style_class='ml-%i' % heading_level) table_of_contents.add(paragraph) message.add(header)
def show_info(self): """Show usage info to the user.""" # Read the header and footer html snippets header = html_header() footer = html_footer() string = header heading = m.Heading(self.tr('OSM Downloader'), **INFO_STYLE) body = self.tr( 'This tool will fetch building (\'structure\') or road (' '\'highway\') data from the OpenStreetMap project for you. ' 'The downloaded data will have InaSAFE keywords defined and a ' 'default QGIS style applied. To use this tool effectively:' ) tips = m.BulletedList() tips.add(self.tr( 'Your current extent will be used to determine the area for which ' 'you want data to be retrieved. You can adjust it manually using ' 'the bounding box options below.')) tips.add(self.tr( 'Check the output directory is correct. Note that the saved ' 'dataset will be called either roads.shp or buildings.shp (and ' 'associated files).' )) tips.add(self.tr( 'By default simple file names will be used (e.g. roads.shp, ' 'buildings.shp). If you wish you can specify a prefix to ' 'add in front of this default name. For example using a prefix ' 'of \'padang-\' will cause the downloaded files to be saved as ' '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that ' 'the only allowed prefix characters are A-Z, a-z, 0-9 and the ' 'characters \'-\' and \'_\'. You can leave this blank if you ' 'prefer.' )) tips.add(self.tr( 'If a dataset already exists in the output directory it will be ' 'overwritten.' )) tips.add(self.tr( 'This tool requires a working internet connection and fetching ' 'buildings or roads will consume your bandwidth.')) tips.add(m.Link( 'http://www.openstreetmap.org/copyright', text=self.tr( 'Downloaded data is copyright OpenStreetMap contributors' ' (click for more info).') )) message = m.Message() message.add(heading) message.add(body) message.add(tips) string += message.to_html() string += footer self.web_view.setHtml(string)
def _create_section_header(message, table_of_contents, id, text): # Warning a side effect here is that the SECTION_STYLE is updated # when setting style as we don't have a deep copy style = SECTION_STYLE style['element_id'] = id header = m.Heading(text, **style) link = m.Link('#%s' % id, text) paragraph = m.Paragraph(link) table_of_contents.add(paragraph) message.add(header)
def content(): """Helper method that returns just the content. This method was added so that the text could be reused in the dock_help module. .. versionadded:: 3.2.2 :returns: A message object without brand element. :rtype: safe.messaging.message.Message """ message = m.Message() body = tr('This tool will fetch building (\'structure\') or road (' '\'highway\') data from the OpenStreetMap project for you. ' 'The downloaded data will have InaSAFE keywords defined and a ' 'default QGIS style applied. To use this tool effectively:') tips = m.BulletedList() tips.add( tr('Your current extent, when opening this window, will be used to ' 'determine the area for which you want data to be retrieved.' 'You can interactively select the area by using the ' '\'select on map\' button - which will temporarily hide this ' 'window and allow you to drag a rectangle on the map. After you ' 'have finished dragging the rectangle, this window will ' 'reappear.')) tips.add( tr('Check the output directory is correct. Note that the saved ' 'dataset will be called either roads.shp or buildings.shp (and ' 'associated files).')) tips.add( tr('By default simple file names will be used (e.g. roads.shp, ' 'buildings.shp). If you wish you can specify a prefix to ' 'add in front of this default name. For example using a prefix ' 'of \'padang-\' will cause the downloaded files to be saved as ' '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that ' 'the only allowed prefix characters are A-Z, a-z, 0-9 and the ' 'characters \'-\' and \'_\'. You can leave this blank if you ' 'prefer.')) tips.add( tr('If a dataset already exists in the output directory it will be ' 'overwritten.')) tips.add( tr('This tool requires a working internet connection and fetching ' 'buildings or roads will consume your bandwidth.')) tips.add( m.Link('http://www.openstreetmap.org/copyright', text=tr( 'Downloaded data is copyright OpenStreetMap contributors' ' (click for more info).'))) message.add(body) message.add(tips) return message
def _citations_to_message(message, definition): citations = m.Message() bullets = m.BulletedList() for citation in definition['citations']: if citation['text'] in [None, '']: continue if citation['link'] in [None, '']: bullets.add(m.Paragraph(citation['text'])) else: bullets.add(m.Paragraph(m.Link(citation['link'], citation['text']))) if not bullets.is_empty(): citations.add(m.Paragraph(m.ImportantText(tr('Citations:')))) citations.add(bullets) message.add(citations)
def content(): """Helper method that returns just the content. This method was added so that the text could be reused in the dock_help module. .. versionadded:: 4.1.0 :returns: A message object without brand element. :rtype: safe.messaging.message.Message """ message = m.Message() message.add( m.Paragraph( tr('This section of the help documentation is intended for advanced ' 'users who want to modify the internals of InaSAFE. It assumes that ' 'you have basic coding skills. All examples are in python unless ' 'otherwise stated.'))) message.add(m.Heading(tr('Defining a new hazard type'), **SUBSECTION_STYLE)) message.add(m.Heading(tr('Background'), **BLUE_CHAPTER_STYLE)) paragraph = m.Paragraph( tr('In the previous versions of InaSAFE, we spent a lot of effort ' 'building one impact function per hazard/exposure combination (and ' 'sometimes multiple impact functions per combination). In our new ' 'architecture, we try to deal with everything in the same way - by ' 'following a standardized process of converting the hazard dataset ' 'into a classified polygon layer and then calculating the impacted ' 'and affected areas using a standard work-flow. A simplified version ' 'of this work-flow is described in illustration 1.')) message.add(paragraph) paragraph = m.Paragraph( tr('Because of this change, you will no longer see an impact function ' 'selector in the dock widget and there are no longer any \'impact ' 'function options\' as we had in previous versions of InaSAFE. In ' 'the new system, almost all configuration is managed through ' 'metadata (created using the keywords wizard).')) message.add(paragraph) paragraph = m.Paragraph( tr('Also, in all versions prior to Version 4.0, we made heavy use of ' 'interpolation in order to determine whether buildings or other ' 'exposure layers are impacted. While this is a commonly used ' 'technique in GIS, it often leads to non - intuitive looking ' 'reports. Under our new architecture, we always use geometric ' 'overlay operations to make a determination whether an exposure ' 'feature is affected or not. The implication of this is that we ' 'produce intuitive and easily verifiable impact layers. You can ' 'see an example in Illustration 2.')) message.add(paragraph) paragraph = m.Paragraph( tr('Stepping away from the two previously mentioned paradigms allows ' 'us to simply add new hazard types to the metadata driven impact ' 'function by adding new metadata types to the InaSAFE sources. ' 'In the next chapter we show you how this was achieved and how ' 'it can be repeated for further hazards using the example of ' 'tropical cyclones.')) message.add(paragraph) message.add(m.Heading(tr('Adding a new hazard'), **BLUE_CHAPTER_STYLE)) link = m.Link('https://github.com/inasafe/inasafe/pull/3539/files', tr('Pull Request #3539')) paragraph = m.Paragraph( tr('The whole work needed can be looked at in '), link, tr('. Please bear in mind that the paths of the files are now ' 'safe/definitions/xxx.py and not safe/definitionsv4/xxx.py since ' 'v4 is the default codebase. In the next sections we will show ' 'each file that needs to be extended in order to add a new hazard ' 'type.')) message.add(paragraph) # Setting up units message.add( m.Heading(tr('safe/definitions/units.py'), **BLUE_CHAPTER_STYLE)) paragraph = m.Paragraph( tr('If you are adding an hazard that uses units that are not yet known ' 'to InaSAFE, you need to define them in units.py')) message.add(paragraph) paragraph = m.PreformattedText( _get_definition_from_module(units, 'unit_kilometres_per_hour')) message.add(paragraph) # Setting up style message.add(m.Heading('safe/definitions/colors.py', **BLUE_CHAPTER_STYLE)) paragraph = m.Paragraph( 'If you are adding an hazard that has more classes than any other ' 'hazards you’ll need to add additional colors for the additional ' 'classes in colors.py. You might also define other colors if you ' 'don\'t want to use the standard colors. For the sake of homogeneous ' 'map reports, this addition should not be taken lightly.') message.add(paragraph) # Don't translate this paragraph = m.PreformattedText('very_dark_red = Qcolor(\'#710017\')') message.add(paragraph) # Setting up hazard classification message.add( m.Heading('safe/definitions/hazard_classifications.py', **BLUE_CHAPTER_STYLE)) paragraph = m.Paragraph( tr('Add the classifications you want to make available for your new ' 'hazard type. You can add as many classes as you want in the ' 'classes list.')) message.add(paragraph) paragraph = m.Paragraph( tr('Also, a classification can support multiple units so you don\'t ' 'have to define different classifications just to have the same ' 'classification in two or more different units. These are defined ' 'in the multiple_units attribute of the classification.')) message.add(paragraph) paragraph = m.PreformattedText( _get_definition_from_module(hazard_classifications, 'cyclone_au_bom_hazard_classes')) message.add(paragraph) # Setting up wizard questions message.add( m.Heading('safe/gui/tools/wizard/wizard_strings.py', **BLUE_CHAPTER_STYLE)) paragraph = m.Paragraph(tr('Define the questions for the wizard:')) message.add(paragraph) # don not translate message.add( m.PreformattedText( 'cyclone_kilometres_per_hour_question = tr(\'wind speed in km/h\')' )) message.add( m.PreformattedText( 'cyclone_miles_per_hour_question = tr(\'wind speed in mph\')')) message.add( m.PreformattedText( 'cyclone_knots_question = tr(\'wind speed in kn\')')) # Setting up message.add(m.Heading('safe/definitions/hazard.py', **BLUE_CHAPTER_STYLE)) paragraph = m.Paragraph( tr('Finally define new hazard and add it to the hazard_all list:')) message.add(paragraph) paragraph = m.PreformattedText( _get_definition_from_module(hazard, 'hazard_cyclone')) message.add(paragraph) paragraph = m.Paragraph( tr('Finally define new hazard and add it to the hazard_all list:')) message.add(paragraph) paragraph = m.PreformattedText( _get_definition_from_module(hazard, 'hazard_all')) message.add(paragraph) return message
def definition_to_message(definition, message=None, table_of_contents=None, heading_level=None): """Helper function to render a definition to a message. :param definition: A definition dictionary (see definitions package). :type definition: dict :param message: The message that the definition should be appended to. :type message: safe_extras.parameters.message.Message :param table_of_contents: Table of contents that the headings should be included in. :type message: safe_extras.parameters.message.Message :param heading_level: Optional style to apply to the definition heading. See HEADING_LOOKUPS :type heading_level: int :returns: Message :rtype: str """ if message is None: message = m.Message() if table_of_contents is None: table_of_contents = m.Message() if heading_level: _create_section_header(message, table_of_contents, definition['name'].replace(' ', '-'), definition['name'], heading_level=heading_level) else: header = m.Paragraph(m.ImportantText(definition['name'])) message.add(header) # If the definition has an icon, we put the icon and description side by # side in a table otherwise just show the description as a paragraph url = _definition_icon_url(definition) if url is None: message.add(m.Paragraph(definition['description'])) if 'citations' in definition: _citations_to_message(message, definition) else: LOGGER.info('Creating mini table for definition description: ' + url) table = m.Table(style_class='table table-condensed') row = m.Row() row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE))) row.add(m.Cell(definition['description'])) table.add(row) for citation in definition['citations']: if citation['text'] in [None, '']: continue row = m.Row() row.add(m.Cell('')) if citation['link'] in [None, '']: row.add(m.Cell(citation['text'])) else: row.add(m.Cell(m.Link(citation['link'], citation['text']))) table.add(row) message.add(table) url = _definition_screenshot_url(definition) if url: message.add(m.Paragraph(m.Image(url), style_class='text-center')) # types contains e.g. hazard_all if 'types' in definition: for sub_definition in definition['types']: definition_to_message(sub_definition, message, table_of_contents, heading_level=3) # # Notes section if available # if 'notes' in definition: # Start a notes details group too since we have an exposure message.add(m.Heading(tr('Notes:'), **DETAILS_STYLE)) message.add(m.Heading(tr('General notes:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['notes']: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'citations' in definition: _citations_to_message(message, definition) # This only for EQ if 'earthquake_fatality_models' in definition: current_function = current_earthquake_model_name() paragraph = m.Paragraph( 'The following earthquake fatality models are available in ' 'InaSAFE. Note that you need to set one of these as the ' 'active model in InaSAFE Options. The currently active model is: ', m.ImportantText(current_function)) message.add(paragraph) models_definition = definition['earthquake_fatality_models'] for model in models_definition: message.add(m.Heading(model['name'], **DETAILS_SUBGROUP_STYLE)) if 'description' in model: paragraph = m.Paragraph(model['description']) message.add(paragraph) for note in model['notes']: paragraph = m.Paragraph(note) message.add(paragraph) _citations_to_message(message, model) for exposure in exposure_all: extra_exposure_notes = specific_notes(definition, exposure) if extra_exposure_notes: title = tr(u'Notes for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_notes: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_notes' in definition: message.add( m.Heading(tr('Notes for continuous datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['continuous_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'classified_notes' in definition: message.add( m.Heading(tr('Notes for classified datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['classified_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'single_event_notes' in definition: message.add(m.Heading(tr('Notes for single events'), **DETAILS_STYLE)) if len(definition['single_event_notes']) < 1: message.add(m.Paragraph(tr('No single event notes defined.'))) else: bullets = m.BulletedList() for note in definition['single_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'multi_event_notes' in definition: message.add( m.Heading(tr('Notes for multi events / scenarios:'), **DETAILS_STYLE)) if len(definition['multi_event_notes']) < 1: message.add(m.Paragraph(tr('No multi-event notes defined.'))) else: bullets = m.BulletedList() for note in definition['multi_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'actions' in definition: message.add(m.Paragraph(m.ImportantText(tr('Actions:')))) bullets = m.BulletedList() for note in definition['actions']: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) for exposure in exposure_all: extra_exposure_actions = specific_actions(definition, exposure) if extra_exposure_actions: title = tr(u'Actions for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_actions: if type(note) is dict: bullets = _add_dict_to_bullets(bullets, note) elif note: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_hazard_units' in definition: message.add(m.Paragraph(m.ImportantText(tr('Units:')))) table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Name'), header=True)) row.add(m.Cell(tr('Plural'), header=True)) row.add(m.Cell(tr('Abbreviation'), header=True)) row.add(m.Cell(tr('Details'), header=True)) table.add(row) for unit in definition['continuous_hazard_units']: row = m.Row() row.add(m.Cell(unit['name'])) row.add(m.Cell(unit['plural_name'])) row.add(m.Cell(unit['abbreviation'])) row.add(m.Cell(unit['description'])) table.add(row) message.add(table) if 'fields' in definition: message.add(m.Paragraph(m.ImportantText(tr('Fields:')))) table = _create_fields_table() if 'extra_fields' in definition: all_fields = definition['fields'] + definition['extra_fields'] else: all_fields = definition['fields'] for field in all_fields: _add_field_to_table(field, table) message.add(table) if 'classifications' in definition: message.add(m.Heading(tr('Hazard classifications'), **DETAILS_STYLE)) message.add( m.Paragraph(definitions.hazard_classification['description'])) for inasafe_class in definition['classifications']: definition_to_message(inasafe_class, message, table_of_contents, heading_level=3) if 'classes' in definition: message.add(m.Paragraph(m.ImportantText(tr('Classes:')))) is_hazard = definition['type'] == hazard_classification_type if is_hazard: table = _make_defaults_hazard_table() else: table = _make_defaults_exposure_table() for inasafe_class in definition['classes']: row = m.Row() if is_hazard: # name() on QColor returns its hex code if 'color' in inasafe_class: colour = inasafe_class['color'].name() row.add( m.Cell(u'', attributes='style="background: %s;"' % colour)) else: row.add(m.Cell(u' ')) row.add(m.Cell(inasafe_class['name'])) if is_hazard: if 'affected' in inasafe_class: row.add(m.Cell(tr(inasafe_class['affected']))) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: if inasafe_class.get('fatality_rate') > 0: # we want to show the rate as a scientific notation rate = html_scientific_notation_rate( inasafe_class['fatality_rate']) rate = u'%s%%' % rate row.add(m.Cell(rate)) elif inasafe_class.get('fatality_rate') == 0: row.add(m.Cell('0%')) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: if 'displacement_rate' in inasafe_class: rate = inasafe_class['displacement_rate'] * 100 rate = u'%.0f%%' % rate row.add(m.Cell(rate)) else: row.add(m.Cell(tr('unspecified'))) if 'string_defaults' in inasafe_class: defaults = None for default in inasafe_class['string_defaults']: if defaults: defaults += ',%s' % default else: defaults = default row.add(m.Cell(defaults)) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: # Min may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_min' in inasafe_class: if isinstance(inasafe_class['numeric_default_min'], dict): bullets = m.BulletedList() minima = inasafe_class['numeric_default_min'] for key, value in minima.iteritems(): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_min'])) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: # Max may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_max' in inasafe_class: if isinstance(inasafe_class['numeric_default_max'], dict): bullets = m.BulletedList() maxima = inasafe_class['numeric_default_max'] for key, value in maxima.iteritems(): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_max'])) else: row.add(m.Cell(tr('unspecified'))) table.add(row) # Description goes in its own row with spanning row = m.Row() row.add(m.Cell('')) row.add(m.Cell(inasafe_class['description'], span=7)) table.add(row) # For hazard classes we also add the 'not affected' class manually: if definition['type'] == definitions.hazard_classification_type: row = m.Row() colour = definitions.not_exposed_class['color'].name() row.add(m.Cell(u'', attributes='style="background: %s;"' % colour)) description = definitions.not_exposed_class['description'] row.add(m.Cell(description, span=7)) table.add(row) message.add(table) if 'affected' in definition: if definition['affected']: message.add( m.Paragraph( tr('Exposure entities in this class ARE considered affected' ))) else: message.add( m.Paragraph( tr('Exposure entities in this class are NOT considered ' 'affected'))) if 'optional' in definition: if definition['optional']: message.add( m.Paragraph( tr('This class is NOT required in the hazard keywords.'))) else: message.add( m.Paragraph( tr('This class IS required in the hazard keywords.'))) return message
def content(): """Helper method that returns just the content. This method was added so that the text could be reused in the dock_help module. .. versionadded:: 4.0.0 :returns: A message object without brand element. :rtype: safe.messaging.message.Message """ # We will store a contents section at the top for easy navigation table_of_contents = m.Message() # and this will be the main message that we create message = m.Message() _create_section_header(message, table_of_contents, 'overview', tr('Overview'), heading_level=1) ## # Credits and disclaimers ... ## _create_section_header(message, table_of_contents, 'disclaimer', tr('Disclaimer'), heading_level=2) message.add(m.Paragraph(definitions.messages.disclaimer())) _create_section_header(message, table_of_contents, 'limitations', tr('Limitations and License'), heading_level=2) bullets = m.BulletedList() for item in definitions.limitations(): bullets.add(item) message.add(bullets) ## # Basic concepts ... ## ## # Help dialog contents ... ## _create_section_header(message, table_of_contents, 'glossary', tr('Glossary of terms'), heading_level=1) last_group = None table = None for key, value in definitions.concepts.iteritems(): current_group = value['group'] if current_group != last_group: if last_group is not None: message.add(table) _create_section_header(message, table_of_contents, current_group.replace(' ', '-'), current_group, heading_level=2) table = _start_glossary_table(current_group) last_group = current_group row = m.Row() term = value['key'].replace('_', ' ').title() description = m.Message(value['description']) for citation in value['citations']: if citation['text'] in [None, '']: continue if citation['link'] in [None, '']: description.add(m.Paragraph(citation['text'])) else: description.add( m.Paragraph(m.Link(citation['link'], citation['text']))) row.add(m.Cell(term)) row.add(m.Cell(description)) url = _definition_icon_url(value) if url: row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE))) else: row.add(m.Cell('')) table.add(row) # ensure the last group's table is added message.add(table) ## # Help dialog contents ... ## _create_section_header(message, table_of_contents, 'core-functionality', tr('Core functionality and tools'), heading_level=1) _create_section_header(message, table_of_contents, 'dock', tr('The InaSAFE Dock'), heading_level=2) message.add(dock_help()) _create_section_header(message, table_of_contents, 'reports', tr('InaSAFE Reports'), heading_level=2) message.add(report_help()) _create_section_header( message, table_of_contents, 'extents', tr('Managing analysis extents with the extents selector'), heading_level=2) message.add(extent_help()) _create_section_header(message, table_of_contents, 'options', tr('InaSAFE Options'), heading_level=2) message.add(options_help()) _create_section_header(message, table_of_contents, 'batch-runner', tr('The Batch Runner'), heading_level=2) message.add(batch_help()) _create_section_header(message, table_of_contents, 'osm-downloader', tr('The OpenStreetmap Downloader'), heading_level=2) message.add(osm_help()) _create_section_header(message, table_of_contents, 'petabencana-downloader', tr('The PetaBencana Downloader'), heading_level=2) message.add(petabencana_help()) _create_section_header(message, table_of_contents, 'shakemap-converter', tr('The Shakemap Converter'), heading_level=2) message.add(shakemap_help()) _create_section_header(message, table_of_contents, 'multi-buffer-tool', tr('The Multi Buffer Tool'), heading_level=2) message.add(multi_buffer_help()) # Field mapping tool has a few added bits to enumerate the groups _create_section_header(message, table_of_contents, 'field-mapping-tool', tr('The Field Mapping Tool'), heading_level=2) message.add(field_mapping_tool_help()) _create_section_header(message, table_of_contents, 'exposure-groups', tr('Exposure Groups'), heading_level=3) message.add( m.Paragraph( 'The following demographic groups apply only to vector population ' 'exposure layers:')) for group in population_field_groups: definition_to_message(group, message, table_of_contents, heading_level=4) _create_section_header(message, table_of_contents, 'aggregation-groups', tr('Aggregation Groups'), heading_level=3) message.add( m.Paragraph( 'The following demographic groups apply only to aggregation layers:' )) for group in aggregation_field_groups: definition_to_message(group, message, table_of_contents, heading_level=4) # End of field mapping tool help # Keep this last in the tool section please as it has subsections # and so uses the top level section style _create_section_header(message, table_of_contents, 'minimum-needs', tr('Minimum Needs'), heading_level=2) _create_section_header(message, table_of_contents, 'minimum-needs-tool', tr('The minimum needs tool'), heading_level=3) message.add(needs_help()) _create_section_header(message, table_of_contents, 'minimum-manager', tr('The minimum needs manager'), heading_level=3) message.add(needs_manager_help()) ## # Analysis workflow ## _create_section_header(message, table_of_contents, 'analysis-steps', tr('Analysis steps'), heading_level=1) _create_section_header(message, table_of_contents, 'analysis-internal-process', tr('Analysis internal process'), heading_level=2) analysis = definitions.concepts['analysis'] message.add(analysis['description']) url = _definition_screenshot_url(analysis) if url: message.add(m.Paragraph(m.Image(url), style_class='text-center')) _create_section_header(message, table_of_contents, 'analysis-progress-reporting', tr('Progress reporting steps'), heading_level=2) steps = definitions.analysis_steps.values() for step in steps: definition_to_message(step, message, table_of_contents, heading_level=3) ## # Hazard definitions ## _create_section_header(message, table_of_contents, 'hazards', tr('Hazard Concepts'), heading_level=1) hazard_category = definitions.hazard_category definition_to_message(hazard_category, message, table_of_contents, heading_level=2) hazards = definitions.hazards definition_to_message(hazards, message, table_of_contents, heading_level=2) ## # Exposure definitions ## _create_section_header(message, table_of_contents, 'exposures', tr('Exposure Concepts'), heading_level=1) exposures = definitions.exposures definition_to_message(exposures, message, table_of_contents, heading_level=2) ## # Defaults ## _create_section_header(message, table_of_contents, 'defaults', tr('InaSAFE Defaults'), heading_level=1) table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Name'), header=True)) row.add(m.Cell(tr('Default value'), header=True)) row.add(m.Cell(tr('Default min'), header=True)) row.add(m.Cell(tr('Default max'), header=True)) row.add(m.Cell(tr('Description'), header=True)) table.add(row) defaults = [ definitions.youth_ratio_default_value, definitions.adult_ratio_default_value, definitions.elderly_ratio_default_value, definitions.female_ratio_default_value, definitions.feature_rate_default_value ] for default in defaults: row = m.Row() row.add(m.Cell(default['name'])) row.add(m.Cell(default['default_value'])) row.add(m.Cell(default['min_value'])) row.add(m.Cell(default['max_value'])) row.add(m.Cell(default['description'])) table.add(row) message.add(table) ## # All Fields ## _create_section_header(message, table_of_contents, 'all-fields', tr('Fields'), heading_level=1) _create_section_header(message, table_of_contents, 'input-fields', tr('Input dataset fields'), heading_level=2) _create_fields_section(message, table_of_contents, tr('Exposure fields'), definitions.exposure_fields) _create_fields_section(message, table_of_contents, tr('Hazard fields'), definitions.hazard_fields) _create_fields_section(message, table_of_contents, tr('Aggregation fields'), definitions.aggregation_fields) _create_section_header(message, table_of_contents, 'output-fields', tr('Output dataset fields'), heading_level=2) _create_fields_section(message, table_of_contents, tr('Impact fields'), definitions.impact_fields) _create_fields_section(message, table_of_contents, tr('Aggregate hazard fields'), definitions.aggregate_hazard_fields) _create_fields_section(message, table_of_contents, tr('Aggregation summary fields'), definitions.aggregation_summary_fields) _create_fields_section(message, table_of_contents, tr('Exposure summary table fields'), definitions.exposure_summary_table_fields) _create_fields_section(message, table_of_contents, tr('Analysis fields'), definitions.analysis_fields) ## # Geometries ## _create_section_header(message, table_of_contents, 'geometries', tr('Layer Geometry Types'), heading_level=1) _create_section_header(message, table_of_contents, 'vector-geometries', tr('Vector'), heading_level=2) definition_to_message(definitions.layer_geometry_point, message, table_of_contents, heading_level=3) definition_to_message(definitions.layer_geometry_line, message, table_of_contents, heading_level=3) definition_to_message(definitions.layer_geometry_polygon, message, table_of_contents, heading_level=3) _create_section_header(message, table_of_contents, 'raster-geometries', tr('Raster'), heading_level=2) definition_to_message(definitions.layer_geometry_raster, message, table_of_contents, heading_level=3) ## # Layer Modes ## _create_section_header(message, table_of_contents, 'layer-modes', tr('Layer Modes'), heading_level=1) definition_to_message(definitions.layer_mode, message, table_of_contents, heading_level=2) ## # Layer Purposes ## _create_section_header(message, table_of_contents, 'layer-purposes', tr('Layer Purposes'), heading_level=1) definition_to_message(definitions.layer_purpose_hazard, message, table_of_contents, heading_level=2) definition_to_message(definitions.layer_purpose_exposure, message, table_of_contents, heading_level=2) definition_to_message(definitions.layer_purpose_aggregation, message, table_of_contents, heading_level=2) definition_to_message(definitions.layer_purpose_exposure_summary, message, table_of_contents, heading_level=2) definition_to_message(definitions.layer_purpose_aggregate_hazard_impacted, message, table_of_contents, heading_level=2) definition_to_message(definitions.layer_purpose_aggregation_summary, message, table_of_contents, heading_level=2) definition_to_message(definitions.layer_purpose_exposure_summary_table, message, table_of_contents, heading_level=2) definition_to_message(definitions.layer_purpose_profiling, message, table_of_contents, heading_level=2) ## # All units ## _create_section_header(message, table_of_contents, 'all-units', tr('All Units'), heading_level=1) table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Name'), header=True)) row.add(m.Cell(tr('Plural'), header=True)) row.add(m.Cell(tr('Abbreviation'), header=True)) row.add(m.Cell(tr('Details'), header=True)) table.add(row) for unit in definitions.units_all: row = m.Row() row.add(m.Cell(unit['name'])) row.add(m.Cell(unit['plural_name'])) row.add(m.Cell(unit['abbreviation'])) row.add(m.Cell(unit['description'])) table.add(row) message.add(table) ## # Post processors ## _create_section_header(message, table_of_contents, 'post-processors', tr('Post Processors'), heading_level=1) _create_section_header(message, table_of_contents, 'post-processor-input-types', tr('Post Processor Input Types'), heading_level=2) table = _create_post_processor_subtable(post_processor_input_types) message.add(table) _create_section_header(message, table_of_contents, 'post-processor-input-values', tr('Post Processor Input Values'), heading_level=2) table = _create_post_processor_subtable(post_processor_input_values) message.add(table) _create_section_header(message, table_of_contents, 'post-processor-process-values', tr('Post Processor Process Types'), heading_level=2) table = _create_post_processor_subtable( definitions.post_processor_process_types) message.add(table) _create_section_header(message, table_of_contents, 'post-processors', tr('Post Processors'), heading_level=2) post_processors = safe.definitions.post_processors table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Name'), header=True)) row.add(m.Cell(tr('Input Fields'), header=True)) row.add(m.Cell(tr('Output Fields'), header=True)) table.add(row) for post_processor in post_processors: row = m.Row() row.add(m.Cell(post_processor['name'])) # Input fields bullets = m.BulletedList() for key, value in post_processor['input'].iteritems(): bullets.add(key) row.add(m.Cell(bullets)) # Output fields bullets = m.BulletedList() for key, value in post_processor['output'].iteritems(): name = value['value']['name'] formula_type = value['type']['key'] if formula_type == 'formula': formula = value['formula'] else: # We use python introspection because the processor # uses a python function for calculations formula = value['function'].__name__ formula += ' (' formula += value['function'].__doc__ formula += ')' bullets.add('%s %s. : %s' % (name, formula_type, formula)) row.add(m.Cell(bullets)) table.add(row) # Add the descriptions row = m.Row() row.add(m.Cell('')) row.add(m.Cell(post_processor['description'], span=2)) table.add(row) message.add(table) ## # Developer documentation ## _create_section_header(message, table_of_contents, 'developer-guide', tr('Developer Guide'), heading_level=1) message.add(developer_help()) # Finally we add the table of contents at the top full_message = m.Message() # Contents is not a link so reset style style = SECTION_STYLE style['element_id'] = '' header = m.Heading(tr('Contents'), **style) full_message.add(header) full_message.add(table_of_contents) full_message.add(message) return full_message
def definition_to_message(definition, heading_style=None): """Helper function to render a definition to a message. :param definition: A definition dictionary (see definitions package). :type definition: dict :param heading_style: Optional style to apply to the definition heading. See safe.messaging.styles :type heading_style: dict :returns: Message :rtype: str """ if heading_style: header = m.Heading(definition['name'], **heading_style) else: header = m.Paragraph(m.ImportantText(definition['name'])) message = m.Message() message.add(header) # If the definition has an icon, we put the icon and description side by # side in a table otherwise just show the description as a paragraph url = _definition_icon_url(definition) if url is None: LOGGER.info('No URL for definition icon') message.add(m.Paragraph(definition['description'])) for citation in definition['citations']: if citation['text'] in [None, '']: continue if citation['link'] in [None, '']: message.add(m.Paragraph(citation['text'])) else: message.add( m.Paragraph(m.Link(citation['link'], citation['text']))) else: LOGGER.info('Creating mini table for definition description: ' + url) table = m.Table(style_class='table table-condensed') row = m.Row() row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE))) row.add(m.Cell(definition['description'])) table.add(row) for citation in definition['citations']: if citation['text'] in [None, '']: continue row = m.Row() row.add(m.Cell('')) if citation['link'] in [None, '']: row.add(m.Cell(citation['text'])) else: row.add(m.Cell(m.Link(citation['link'], citation['text']))) table.add(row) message.add(table) url = _definition_screenshot_url(definition) if url: message.add(m.Image(url)) # types contains e.g. hazard_all if 'types' in definition: for sub_definition in definition['types']: message.add( definition_to_message(sub_definition, RED_CHAPTER_STYLE)) # # Notes section if available # if 'notes' in definition: # Start a notes details group too since we have an exposure message.add(m.Heading(tr('Notes:'), **DETAILS_STYLE)) message.add(m.Heading(tr('General notes:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['notes']: bullets.add(m.Text(note)) message.add(bullets) # This only for EQ if 'earthquake_fatality_models' in definition: for model in definition['earthquake_fatality_models']: message.add(m.Heading(model['name'], **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in model['notes']: bullets.add(m.Text(note)) message.add(bullets) for exposure in exposure_all: extra_exposure_notes = specific_notes(definition, exposure) if extra_exposure_notes: title = tr(u'Notes for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_notes: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_notes' in definition: message.add( m.Heading(tr('Notes for continuous datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['continuous_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'classified_notes' in definition: message.add( m.Heading(tr('Notes for classified datasets:'), **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in definition['classified_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'single_event_notes' in definition: message.add(m.Heading(tr('Notes for single events'), **DETAILS_STYLE)) if len(definition['single_event_notes']) < 1: message.add(m.Paragraph(tr('No single event notes defined.'))) else: bullets = m.BulletedList() for note in definition['single_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'multi_event_notes' in definition: message.add( m.Heading(tr('Notes for multi events / scenarios:'), **DETAILS_STYLE)) if len(definition['multi_event_notes']) < 1: message.add(m.Paragraph(tr('No multi-event notes defined.'))) else: bullets = m.BulletedList() for note in definition['multi_event_notes']: bullets.add(m.Text(note)) message.add(bullets) if 'actions' in definition: message.add(m.Paragraph(m.ImportantText(tr('Actions:')))) bullets = m.BulletedList() for note in definition['actions']: bullets.add(m.Text(note)) message.add(bullets) for exposure in exposure_all: extra_exposure_actions = specific_actions(definition, exposure) if extra_exposure_actions: title = tr(u'Actions for exposure : {exposure_name}').format( exposure_name=exposure['name']) message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE)) bullets = m.BulletedList() for note in extra_exposure_actions: bullets.add(m.Text(note)) message.add(bullets) if 'continuous_hazard_units' in definition: message.add(m.Paragraph(m.ImportantText(tr('Units:')))) table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Name')), header_flag=True) row.add(m.Cell(tr('Plural')), header_flag=True) row.add(m.Cell(tr('Abbreviation')), header_flag=True) row.add(m.Cell(tr('Details')), header_flag=True) table.add(row) for unit in definition['continuous_hazard_units']: row = m.Row() row.add(m.Cell(unit['name'])) row.add(m.Cell(unit['plural_name'])) row.add(m.Cell(unit['abbreviation'])) row.add(m.Cell(unit['description'])) table.add(row) message.add(table) if 'extra_fields' and 'fields' in definition: message.add(m.Paragraph(m.ImportantText(tr('Fields:')))) table = _create_fields_table() all_fields = definition['fields'] + definition['extra_fields'] for field in all_fields: _add_field_to_table(field, table) message.add(table) if 'classifications' in definition: message.add(m.Heading(tr('Hazard classifications'), **DETAILS_STYLE)) message.add( m.Paragraph(definitions.hazard_classification['description'])) for inasafe_class in definition['classifications']: message.add( definition_to_message(inasafe_class, DETAILS_SUBGROUP_STYLE)) if 'classes' in definition: message.add(m.Paragraph(m.ImportantText(tr('Classes:')))) table = _make_defaults_table() for inasafe_class in definition['classes']: row = m.Row() # name() on QColor returns its hex code if 'color' in inasafe_class: colour = inasafe_class['color'].name() row.add( m.Cell(u'', attributes='style="background: %s;"' % colour)) else: row.add(m.Cell(u' ')) row.add(m.Cell(inasafe_class['name'])) if 'affected' in inasafe_class: row.add(m.Cell(tr(inasafe_class['affected']))) else: row.add(m.Cell(tr('unspecified'))) if 'displacement_rate' in inasafe_class: rate = inasafe_class['displacement_rate'] * 100 rate = u'%s%%' % rate row.add(m.Cell(rate)) else: row.add(m.Cell(tr('unspecified'))) if 'string_defaults' in inasafe_class: defaults = None for default in inasafe_class['string_defaults']: if defaults: defaults += ',%s' % default else: defaults = default row.add(m.Cell(defaults)) else: row.add(m.Cell(tr('unspecified'))) # Min may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_min' in inasafe_class: if isinstance(inasafe_class['numeric_default_min'], dict): bullets = m.BulletedList() minima = inasafe_class['numeric_default_min'] for key, value in minima.iteritems(): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_min'])) else: row.add(m.Cell(tr('unspecified'))) # Max may be a single value or a dict of values so we need # to check type and deal with it accordingly if 'numeric_default_max' in inasafe_class: if isinstance(inasafe_class['numeric_default_max'], dict): bullets = m.BulletedList() maxima = inasafe_class['numeric_default_max'] for key, value in maxima.iteritems(): bullets.add(u'%s : %s' % (key, value)) row.add(m.Cell(bullets)) else: row.add(m.Cell(inasafe_class['numeric_default_max'])) else: row.add(m.Cell(tr('unspecified'))) table.add(row) # Description goes in its own row with spanning row = m.Row() row.add(m.Cell('')) row.add(m.Cell(inasafe_class['description'], span=6)) table.add(row) # For hazard classes we also add the 'not affected' class manually: if definition['type'] == definitions.hazard_classification_type: row = m.Row() colour = definitions.not_exposed_class['color'].name() row.add(m.Cell(u'', attributes='style="background: %s;"' % colour)) description = definitions.not_exposed_class['description'] row.add(m.Cell(description, span=5)) table.add(row) message.add(table) if 'affected' in definition: if definition['affected']: message.add( m.Paragraph( tr('Exposure entities in this class ARE considered affected' ))) else: message.add( m.Paragraph( tr('Exposure entities in this class are NOT considered ' 'affected'))) if 'optional' in definition: if definition['optional']: message.add( m.Paragraph( tr('This class is NOT required in the hazard keywords.'))) else: message.add( m.Paragraph( tr('This class IS required in the hazard keywords.'))) return message
def content(): """Helper method that returns just the content. This method was added so that the text could be reused in the dock_help module. .. versionadded:: 3.2.2 :returns: A message object without brand element. :rtype: safe.messaging.message.Message """ message = m.Message() body = tr('This tool will fetch building (\'structure\') or road (' '\'highway\') data from the OpenStreetMap project for you. ' 'The downloaded data will have InaSAFE keywords defined and a ' 'default QGIS style applied. To use this tool effectively:') tips = m.BulletedList() tips.add( tr('Your current extent, when opening this window, will be used to ' 'determine the area for which you want data to be retrieved. ' 'You can interactively select the area by using the ' '\'select on map\' button - which will temporarily hide this ' 'window and allow you to drag a rectangle on the map. After you ' 'have finished dragging the rectangle, this window will ' 'reappear.')) tips.add( tr('Check the output directory is correct. Note that the saved ' 'dataset will be named after the type of data being downloaded ' 'e.g. roads.shp or buildings.shp (and associated files).')) tips.add( tr('By default simple file names will be used (e.g. roads.shp, ' 'buildings.shp). If you wish you can specify a prefix to ' 'add in front of this default name. For example using a prefix ' 'of \'padang-\' will cause the downloaded files to be saved as ' '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that ' 'the only allowed prefix characters are A-Z, a-z, 0-9 and the ' 'characters \'-\' and \'_\'. You can leave this blank if you ' 'prefer.')) tips.add( tr('If a dataset already exists in the output directory it will be ' 'overwritten.')) tips.add( tr('This tool requires a working internet connection and fetching ' 'buildings or roads will consume your bandwidth.')) tips.add( m.Link('http://www.openstreetmap.org/copyright', text=tr( 'Downloaded data is copyright OpenStreetMap contributors ' '(click for more info).'))) message.add(m.Paragraph(body)) message.add(tips) message.add( m.Paragraph( # format 'When the __Political boundaries__' for proper i18n tr('When the %s ' 'box in the Feature types menu is ticked, the Political boundary ' 'options panel will be enabled. The panel lets you select which ' 'admin level you wish to download. The admin levels are country ' 'specific. When you select an admin level, the local name for ' 'that admin level will be shown. You can change which country ' 'is used for the admin level description using the country drop ' 'down menu. The country will be automatically set to coincide ' 'with the view extent if a matching country can be found.') % (m.ImportantText(tr('Political boundaries')).to_html(), ))) message.add( m.Paragraph( m.ImportantText(tr('Note: ')), tr('We have only provide presets for a subset of the available ' 'countries. If you want to know what the levels are for your ' 'country, please check on the following web page: '), m.Link( 'http://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative', text=tr('List of OSM Admin Boundary definitions ')))) return message