Ejemplo n.º 1
0
    def generate_height_tiles():
        """
        This is in a subroutine because it
        must be possible to not use it.
        """
        logger.info('Generating height and depth tiles...')
        logger.debug(
            'height min max=%f, %f, depth min max=%f, %f' %
            (min_height, max_height, min_depth, max_depth),
        )
        for ahn_index in ahn_indices:
            ahn_name = ahn_index.bladnr
            height_slug = slug_for_height(ahn_name, min_height, max_height)
            height_slugs.append(height_slug)  # part of result
            geo_image_depth_count = -1
            try:
                depth_slug = slug_for_depth(ahn_name, min_depth, max_depth)
                depth_slugs.append(depth_slug)  # part of result
                geo_image_depth_count = models.GeoImage.objects.filter(
                    slug=depth_slug,
                ).count()
            except:
                logger.warning('GeoImage for depth failed because of fully masked')

            geo_image_height_count = models.GeoImage.objects.filter(
                slug=height_slug,
            ).count()
            if (geo_image_height_count == 0 or geo_image_depth_count == 0):
                # Copied from above
                try:
                    alldata = raster.get_calc_data(
                        waterlevel_datasets=waterlevel_datasets,
                        method=settings.RASTER_SOURCE,
                        floodtime=floodtime,
                        ahn_name=ahn_name,
                        logger=logger,
                    )
                    if alldata is None:
                        logger.warning(
                            'Skipping height tiles generation for {}'
                            .format(ahn_name),
                        )
                        continue

                    landuse, depth, geo, floodtime_px, ds_height, height = alldata
                except:
                    # Log this error and all previous normal logs,
                    # instead of hard crashing
                    logger.error('Exception')
                    for exception_line in traceback.format_exc().split('\n'):
                        logger.error(exception_line)
                    return

            if geo_image_height_count == 0:
                # 1000x1250 meters = 2000x2500 pixels
                extent = ahn_index.the_geom.extent
                # Actually create tile
                logger.info("Generating height GeoImage: %s" % height_slug)
                models.GeoImage.from_data_with_min_max(
                    height_slug, height, extent, min_height, max_height)
            if geo_image_depth_count == 0:
                # 1000x1250 meters = 2000x2500 pixels
                extent = ahn_index.the_geom.extent
                # Actually create tile
                logger.info("Generating depth GeoImage: %s" % depth_slug)
                try: #if isinstance(min_depth, float) and isinstance(max_depth, float):
                    models.GeoImage.from_data_with_min_max(
                        depth_slug, depth, extent, min_depth, max_depth,
                        cdict=cdict_water_depth)
                    depth_slugs.append(depth_slug)  # part of result
                except:
                    logger.info("Skipped depth GeoImage because of masked only or unknown error")
Ejemplo n.º 2
0
def calc_damage_for_waterlevel(
    repetition_time,
    ds_wl_filenames,
    dt_path=None,
    month=9, floodtime=20*3600,
    repairtime_roads=None, repairtime_buildings=None,
    calc_type=CALC_TYPE_MAX,
    logger=logger):
    """
    Calculate damage for provided waterlevel file.

    in:

    - waterlevel file (provided by user)

    - damage table (optionally provided by user, else default)

    - AHN: models.AhnIndex refer to ahn tiles available on
      <settings.DATA_ROOT>/...

    - month, floodtime (s), repairtime_roads/buildings (s): provided
      by user, used by calc.calculate

    out:

    - per ahn tile an .asc and .csv (see write_result and write_table)

    - schade_totaal.csv (see write_table)

    """
    cdict_water_depth = {
        'red': ((0.0, 170./256, 170./256),
                (0.5, 65./256, 65./256),
                (1.0, 4./256, 4./256)),
        'green': ((0.0, 200./256, 200./256),
                  (0.5, 120./256, 120./256),
                  (1.0, 65./256, 65./256)),
        'blue': ((0.0, 255./256, 255./256),
                 (0.5, 221./256, 221./256),
                 (1.0, 176./256, 176./256)),
        }

    zip_result = []  # store all the file references for zipping. {'filename': .., 'arcname': ...}
    img_result = []
    landuse_slugs = []  # slugs for landuse geo images
    height_slugs = []  # slugs for height geo images
    depth_slugs = []  # slugs for depth geo images

    logger.info('water level: %s' % ds_wl_filenames)
    logger.info('damage table: %s' % dt_path)
    output_zipfile = mkstemp_and_close()
    waterlevel_ascfiles = ds_wl_filenames
    correct_ascfiles(waterlevel_ascfiles)  # TODO: do it elsewhere
    waterlevel_datasets = [raster.import_dataset(waterlevel_ascfile, 'AAIGrid')
                           for waterlevel_ascfile in waterlevel_ascfiles]
    logger.info('waterlevel_ascfiles: %r' % waterlevel_ascfiles)
    logger.info('waterlevel_datasets: %r' % waterlevel_datasets)
    for fn, ds in zip(waterlevel_ascfiles, waterlevel_datasets):
        if ds is None:
            logger.error('data source is not available,'
                         ' please check folder %s' % fn)
            return

    if dt_path is None:
        damage_table_path = 'data/damagetable/dt.cfg'
        dt_path = os.path.join(settings.BUILDOUT_DIR, damage_table_path)
    with open(dt_path) as cfg:
        dt = table.DamageTable.read_cfg(cfg)
    zip_result.append({'filename': dt_path, 'arcname': 'dt.cfg'})

    overall_area = collections.defaultdict(float)
    overall_damage = collections.defaultdict(float)
    roads_flooded_global = {i: {} for i in ROAD_GRIDCODE}
    result_images = []  # Images to be modified for indirect road damage.

    min_height = None
    max_height = None
    min_depth = 0.0  # Set defaults for testing... depth is always >= 0
    max_depth = 0.1

    ahn_indices = raster.get_ahn_indices(waterlevel_datasets[0])
    for ahn_index in ahn_indices:
        ahn_name = ahn_index.bladnr
        logger.info("Preparing calculation for tile %s..." % ahn_name)

        # Prepare data for calculation
        try:
            alldata = raster.get_calc_data(
                waterlevel_datasets=waterlevel_datasets,
                method=settings.RASTER_SOURCE,
                floodtime=floodtime,
                ahn_name=ahn_name,
                logger=logger,
            )
            if alldata is None:
                logger.warning(
                    'Skipping damage calculation for {}'.format(ahn_name),
                )
                continue

            landuse, depth, geo, floodtime_px, ds_height, height = alldata
        except:
            # Log this error and all previous normal logs, instead of hard crashing
            logger.error('Exception')
            for exception_line in traceback.format_exc().split('\n'):
                logger.error(exception_line)
            return

        extent = ahn_index.the_geom.extent  # 1000x1250 meters = 2000x2500 pixels

        # For height map
        new_min_height = np.amin(height)
        if min_height is None or new_min_height < min_height:
            min_height = new_min_height
        new_max_height = np.amax(height)
        if max_height is None or new_max_height < max_height:
            max_height = new_max_height

        # For depth map
        new_min_depth = np.amin(depth)
        if min_depth is None or new_min_depth < min_depth:
            min_depth = new_min_depth
        new_max_depth = np.amax(depth)
        if max_depth is None or new_max_depth < max_depth:
            max_depth = new_max_depth

        # For landuse map
        landuse_slug = slug_for_landuse(ahn_name)
        landuse_slugs.append(landuse_slug)  # part of result
        # note: multiple objects with the same slug can exist if they
        # enter this function at the same time
        if models.GeoImage.objects.filter(slug=landuse_slug).count() == 0:
            logger.info("Generating landuse GeoImage: %s" % landuse_slug)
            models.GeoImage.from_data_with_legend(landuse_slug, landuse.data, extent, landuse_legend())

        # Result is a np array
        damage, count, area, result, roads_flooded_for_tile = calculate(
            use=landuse, depth=depth, geo=geo, calc_type=calc_type,
            table=dt, month=month, floodtime=floodtime_px,
            repairtime_roads=repairtime_roads,
            repairtime_buildings=repairtime_buildings,
            logger=logger,
        )
        for code, roads_flooded in roads_flooded_for_tile.iteritems():
            for road, flooded_m2 in roads_flooded.iteritems():
                if road in roads_flooded_global[code]:
                    roads_flooded_global[code][road]['area'] += flooded_m2
                else:
                    roads_flooded_global[code][road] = dict(
                        shape=depth.shape,
                        area=flooded_m2,
                        geo=geo,
                    )

        logger.debug("result sum: %f" % result.sum())
        arcname = 'schade_{}'.format(ahn_name)
        if repetition_time:
            arcname += '_T%.1f' % repetition_time
        asc_result = {'filename': mkstemp_and_close(), 'arcname': arcname + '.asc',
            'delete_after': True}
        write_result(
            name=asc_result['filename'],
            ma_result=result,
            ds_template=ds_height,
        )
        zip_result.append(asc_result)

        # Generate image in .png
        # Subdivide tiles
        x_tiles = 1
        y_tiles = 1
        tile_x_size = (extent[2] - extent[0]) / x_tiles
        tile_y_size = (extent[3] - extent[1]) / y_tiles
        result_tile_size_x = result.shape[1] / x_tiles
        result_tile_size_y = result.shape[0] / y_tiles
        #print ('result tile size: %r %r' % (result_tile_size_x, result_tile_size_y))
        for tile_x in range(x_tiles):
            for tile_y in range(y_tiles):
                e = (extent[0] + tile_x * tile_x_size, extent[1] + tile_y * tile_y_size,
                    extent[0] + (tile_x + 1) * tile_x_size, extent[1] + (tile_y + 1) * tile_y_size)
                # We are writing a png + pgw now, but in the task a tiff will be created and uploaded
                base_filename = mkstemp_and_close()
                image_result = {
                    'filename_tif': base_filename + '.tif',
                    'filename_png': base_filename + '.png',
                    'filename_pgw': base_filename + '.pgw',
                    'dstname': 'schade_%s_' + ahn_name + '.png',
                    'extent': ahn_index.extent_wgs84(e=e)}  # %s is for the damage_event.slug
                write_image(
                    name=image_result['filename_png'],
                    values=result[(y_tiles-tile_y-1)*result_tile_size_y:(y_tiles-tile_y)*result_tile_size_y,
                                (tile_x)*result_tile_size_x:(tile_x+1)*result_tile_size_x])
                result_images.append({
                    'extent': e,
                    'path': image_result['filename_png'],
                })
                models.write_pgw(
                    name=image_result['filename_pgw'],
                    extent=e)
                img_result.append(image_result)

        csv_result = {'filename': mkstemp_and_close(), 'arcname': arcname + '.csv',
            'delete_after': True}
        meta = [
            ['schade module versie', tools.version()],
            ['waterlevel', waterlevel_ascfiles[0]],
            ['damage table', dt_path],
            ['maand', str(month)],
            ['duur overstroming (s)', str(floodtime)],
            ['hersteltijd wegen (s)', str(repairtime_roads)],
            ['hersteltijd bebouwing (s)', str(repairtime_buildings)],
            ['berekening', {1: 'Minimum', 2: 'Maximum', 3: 'Gemiddelde'}[calc_type]],
            ['ahn_name', ahn_name],
            ]
        write_table(
            name=csv_result['filename'],
            damage=damage,
            area=area,
            dt=dt,
            meta=meta,
        )
        zip_result.append(csv_result)

        for k in damage.keys():
            if k in overall_damage:
                overall_damage[k] += damage[k]
            else:
                overall_damage[k] = damage[k]

        for k in area.keys():
            if k in overall_area:
                overall_area[k] += area[k]
            else:
                overall_area[k] = area[k]

        add_to_zip(output_zipfile, zip_result, logger)
        zip_result = []

    def generate_height_tiles():
        """
        This is in a subroutine because it
        must be possible to not use it.
        """
        logger.info('Generating height and depth tiles...')
        logger.debug(
            'height min max=%f, %f, depth min max=%f, %f' %
            (min_height, max_height, min_depth, max_depth),
        )
        for ahn_index in ahn_indices:
            ahn_name = ahn_index.bladnr
            height_slug = slug_for_height(ahn_name, min_height, max_height)
            height_slugs.append(height_slug)  # part of result
            geo_image_depth_count = -1
            try:
                depth_slug = slug_for_depth(ahn_name, min_depth, max_depth)
                depth_slugs.append(depth_slug)  # part of result
                geo_image_depth_count = models.GeoImage.objects.filter(
                    slug=depth_slug,
                ).count()
            except:
                logger.warning('GeoImage for depth failed because of fully masked')

            geo_image_height_count = models.GeoImage.objects.filter(
                slug=height_slug,
            ).count()
            if (geo_image_height_count == 0 or geo_image_depth_count == 0):
                # Copied from above
                try:
                    alldata = raster.get_calc_data(
                        waterlevel_datasets=waterlevel_datasets,
                        method=settings.RASTER_SOURCE,
                        floodtime=floodtime,
                        ahn_name=ahn_name,
                        logger=logger,
                    )
                    if alldata is None:
                        logger.warning(
                            'Skipping height tiles generation for {}'
                            .format(ahn_name),
                        )
                        continue

                    landuse, depth, geo, floodtime_px, ds_height, height = alldata
                except:
                    # Log this error and all previous normal logs,
                    # instead of hard crashing
                    logger.error('Exception')
                    for exception_line in traceback.format_exc().split('\n'):
                        logger.error(exception_line)
                    return

            if geo_image_height_count == 0:
                # 1000x1250 meters = 2000x2500 pixels
                extent = ahn_index.the_geom.extent
                # Actually create tile
                logger.info("Generating height GeoImage: %s" % height_slug)
                models.GeoImage.from_data_with_min_max(
                    height_slug, height, extent, min_height, max_height)
            if geo_image_depth_count == 0:
                # 1000x1250 meters = 2000x2500 pixels
                extent = ahn_index.the_geom.extent
                # Actually create tile
                logger.info("Generating depth GeoImage: %s" % depth_slug)
                try: #if isinstance(min_depth, float) and isinstance(max_depth, float):
                    models.GeoImage.from_data_with_min_max(
                        depth_slug, depth, extent, min_depth, max_depth,
                        cdict=cdict_water_depth)
                    depth_slugs.append(depth_slug)  # part of result
                except:
                    logger.info("Skipped depth GeoImage because of masked only or unknown error")

    if ((min_height is not None) and
        (max_height is not None) and
        (min_depth is not None) and
        (max_depth is not None)):
        generate_height_tiles()

    # Only after all tiles have been processed, calculate overall indirect
    # Road damage. This is not visible in the per-tile-damagetable.
    roads_flooded_over_threshold = []
    for code, roads_flooded in roads_flooded_global.iteritems():
        for road, info in roads_flooded.iteritems():
            if info['area'] >= 100:
                roads_flooded_over_threshold.append(road)
                overall_damage[code] += (
                    dt.data[code].to_indirect_damage(CALC_TYPES[calc_type]) *
                    dt.data[code].to_gamma_repairtime(repairtime_roads)
                )

    def add_roads_to_image(roads, image_path, extent):
        """ This function could be moved to top level. """

        # Get old image that needs indirect road damage visualized
        image = Image.open(result_image['path'])

        # Rasterize all roads that have indirect damage
        size = image.size
        geotransform = [
            extent[0],
            (extent[2] - extent[0]) / size[0],
            0,
            extent[3],
            0,
            (extent[1] - extent[3]) / size[1],
        ]
        roadgrid = raster.get_mask(
            roads=road_objects,
            shape=image.size[::-1],
            geo=(b'', geotransform),
        )

        # Paste it into the old image and overwrite the image file
        rgba = np.uint8([[[0, 0, 0, 153]]]) * roadgrid.reshape(
            roadgrid.shape[0], roadgrid.shape[1], 1
        )

        image_roads_rgb = Image.fromarray(rgba[:, :, 0:3])
        image_roads_mask = Image.fromarray(rgba[:, :, 3])
        image.paste(image_roads_rgb, None, image_roads_mask)
        image.save(result_image['path'])

    road_objects = models.Roads.objects.filter(
        pk__in=roads_flooded_over_threshold,
    )
    for result_image in result_images:
        add_roads_to_image(
            roads=road_objects,
            image_path=result_image['path'],
            extent=result_image['extent'],
        )

    csv_result = {'filename': mkstemp_and_close(), 'arcname': 'schade_totaal.csv',
        'delete_after': True}
    meta = [
        ['schade module versie', tools.version()],
        ['waterlevel', waterlevel_ascfiles[0]],
        ['damage table', dt_path],
        ['maand', str(month)],
        ['duur overstroming (s)', str(floodtime)],
        ['hersteltijd wegen (s)', str(repairtime_roads)],
        ['hersteltijd bebouwing (s)', str(repairtime_buildings)],
        ['berekening', {1: 'Minimum', 2: 'Maximum', 3: 'Gemiddelde'}[calc_type]],
        ]
    write_table(
        name=csv_result['filename'],
        damage=overall_damage,
        area=overall_area,
        dt=dt,
        meta=meta,
        include_total=True,
        )
    result_table = result_as_dict(
        name=csv_result['filename'],
        damage=overall_damage,
        area=overall_area,
        damage_table=dt
        )
    zip_result.append(csv_result)

    add_to_zip(output_zipfile, zip_result, logger)
    zip_result = []

    logger.info('zipfile: %s' % output_zipfile)

    return output_zipfile, img_result, result_table, landuse_slugs, height_slugs, depth_slugs