def digestNodesFromSHP(sourcePath):
    'Import nodes from a shapefile'
    # Initialize
    shapeData = osgeo.ogr.Open(sourcePath)
    layer = shapeData.GetLayer()
    # Prepare spatial reference
    proj4 = layer.GetSpatialRef().ExportToProj4()
    # Prepare nodePacks
    nodePacks = []
    for featureIndex in xrange(layer.GetFeatureCount()):
        # Get feature
        feature = layer.GetFeature(featureIndex)
        geometry = feature.GetGeometryRef()
        # Build nodePack
        valueByLabel = feature.items()
        nodePack = dict((label.lower(), value)
                        for label, value in valueByLabel.iteritems()
                        if value not in ['', None])
        nodePack['x'] = geometry.GetX()
        nodePack['y'] = geometry.GetY()
        # Append
        nodePacks.append(nodePack)
    # Return
    return proj4, nodePacks
Exemple #2
0
def _determine_valid_viewpoints(dem_path, structures_path):
    """Determine which viewpoints are valid and return them.

    A point is considered valid when it meets all of these conditions:

        1. The point must be within the bounding box of the DEM
        2. The point must not overlap a DEM pixel that is nodata
        3. The point must not have the same coordinates as another point

    All invalid points are skipped, and a logger message is written for the
    feature.

    Args:
        dem_path (str): The path to a GDAL-compatible digital elevation model
            raster on disk. The projection must match the projection of the
            ``structures_path`` vector.
        structures_path (str): The path to a GDAL-compatible vector containing
            point geometries and, optionally, a few fields describing
            parameters to the viewshed:

                * 'RADIUS' or 'RADIUS2': How far out from the viewpoint (in m)
                    the viewshed operation is permitted to extend. Default: no
                    limit.
                * 'HEIGHT': The height of the structure (in m). Default: 0.0
                * 'WEIGHT': The numeric weight that this viewshed should be
                    assigned when calculating visual quality. Default: 1.0

    Returns:
        An unsorted list of the valid viewpoints and their metadata. The
        tuples themselves are in the order::

            (viewpoint, radius, weight, height)

        Where

            * ``viewpoint``: a tuple of
                ``(projected x coord, projected y coord``)
            * ``radius``: the maximum radius of the viewshed
            * ``weight``: the weight of the viewshed (for calculating visual
                quality)
            * ``height``: The height of the structure at this point.
    """
    dem_raster_info = pygeoprocessing.get_raster_info(dem_path)
    dem_nodata = dem_raster_info['nodata'][0]
    dem_gt = dem_raster_info['geotransform']
    bbox_minx, bbox_miny, bbox_maxx, bbox_maxy = (
        dem_raster_info['bounding_box'])

    # Use interleaved coordinates (xmin, ymin, xmax, ymax)
    spatial_index = rtree.index.Index(interleaved=True)

    structures_vector = gdal.OpenEx(structures_path, gdal.OF_VECTOR)
    for structures_layer_index in range(structures_vector.GetLayerCount()):
        structures_layer = structures_vector.GetLayer(structures_layer_index)
        layer_name = structures_layer.GetName()
        LOGGER.info('Layer %s has %s features', layer_name,
                    structures_layer.GetFeatureCount())

        radius_fieldname = None
        fieldnames = set(
            column.GetName() for column in structures_layer.schema)
        possible_radius_fieldnames = set(
            ['RADIUS', 'RADIUS2']).intersection(fieldnames)
        if possible_radius_fieldnames:
            radius_fieldname = possible_radius_fieldnames.pop()

        height_present = False
        height_fieldname = 'HEIGHT'
        if height_fieldname in fieldnames:
            height_present = True

        weight_present = False
        weight_fieldname = 'WEIGHT'
        if weight_fieldname in fieldnames:
            weight_present = True

        last_log_time = time.time()
        n_features_touched = -1
        for point in structures_layer:
            n_features_touched += 1
            if time.time() - last_log_time > 5.0:
                LOGGER.info(
                    ("Checking structures in layer %s, approx. "
                     "%.2f%%complete."), layer_name,
                    100.0 * (n_features_touched /
                             structures_layer.GetFeatureCount()))
                last_log_time = time.time()

            # Coordinates in map units to pass to viewshed algorithm
            geometry = point.GetGeometryRef()
            viewpoint = (geometry.GetX(), geometry.GetY())

            if (not bbox_minx <= viewpoint[0] <= bbox_maxx or
                    not bbox_miny <= viewpoint[1] <= bbox_maxy):
                LOGGER.info(
                    ('Feature %s in layer %s is outside of the DEM bounding '
                     'box. Skipping.'), point.GetFID(), layer_name)
                continue

            max_radius = None
            if radius_fieldname:
                max_radius = math.fabs(point.GetField(radius_fieldname))

            height = 0.0
            if height_present:
                height = math.fabs(point.GetField(height_fieldname))

            weight = 1.0
            if weight_present:
                weight = float(point.GetField(weight_fieldname))

            spatial_index.insert(
                point.GetFID(),
                (viewpoint[0], viewpoint[1], viewpoint[0], viewpoint[1]),
                {'max_radius': max_radius,
                 'weight': weight,
                 'height': height})

    # Now check that the viewpoint isn't over nodata in the DEM.
    valid_structures = {}

    dem_origin_x = dem_gt[0]
    dem_origin_y = dem_gt[3]
    dem_pixelsize_x = dem_raster_info['pixel_size'][0]
    dem_pixelsize_y = dem_raster_info['pixel_size'][1]
    dem_raster = gdal.OpenEx(dem_path, gdal.OF_RASTER)
    dem_band = dem_raster.GetRasterBand(1)
    for block_data in pygeoprocessing.iterblocks((dem_path, 1),
                                                 offset_only=True):
        # Using shapely.geometry.box here so that it'll handle the min/max for
        # us and all we need to define here are the correct coordinates.
        block_geom = shapely.geometry.box(
            dem_origin_x + dem_pixelsize_x * block_data['xoff'],
            dem_origin_y + dem_pixelsize_y * block_data['yoff'],
            dem_origin_x + dem_pixelsize_x * (
                block_data['xoff'] + block_data['win_xsize']),
            dem_origin_y + dem_pixelsize_y * (
                block_data['yoff'] + block_data['win_ysize']))

        intersecting_points = list(spatial_index.intersection(
            block_geom.bounds, objects=True))
        if len(intersecting_points) == 0:
            continue

        dem_block = dem_band.ReadAsArray(**block_data)
        for item in intersecting_points:
            viewpoint = (item.bounds[0], item.bounds[2])
            metadata = item.object
            ix_viewpoint = int(
                (viewpoint[0] - dem_gt[0]) // dem_gt[1]) - block_data['xoff']
            iy_viewpoint = int(
                (viewpoint[1] - dem_gt[3]) // dem_gt[5]) - block_data['yoff']
            if utils.array_equals_nodata(
                    numpy.array(dem_block[iy_viewpoint][ix_viewpoint]),
                    dem_nodata).any():
                LOGGER.info(
                    'Feature %s in layer %s is over nodata; skipping.',
                    point.GetFID(), layer_name)
                continue

            if viewpoint in valid_structures:
                LOGGER.info(
                    ('Feature %s in layer %s is a duplicate viewpoint. '
                     'Skipping.'), point.GetFID(), layer_name)
                continue

            # if we've made it here, the viewpoint is valid.
            valid_structures[viewpoint] = metadata

    # Casting to a list so that taskgraph can pickle the result.
    return list(
        (point, meta['max_radius'], meta['weight'], meta['height'])
        for (point, meta) in valid_structures.items())