Пример #1
0
def calculate_travel_time(inH, traversal_time, destinations, out_raster=''):
    ''' Calculate travel time raster
    
    INPUTS
        inH [rasterio object] - template raster used to identify locations of destinations
        traversal_time [numpy array] - describies per pixel seconds to cross
        destinations [geopandas df] - destinations for nearest calculations
        
    LINKS
        https://scikit-image.org/docs/0.7.0/api/skimage.graph.mcp.html#skimage.graph.mcp.MCP.find_costs
    '''
    # create skimage graph
    mcp = graph.MCP_Geometric(traversal_time)
    cities = list(set([inH.index(x.x, x.y) for x in destinations['geometry']]))
    cities = [
        x for x in cities if ((x[0] > 0) and (x[1] > 0) and (
            x[0] <= inH.shape[0]) and (x[1] <= inH.shape[1]))
    ]

    costs, traceback = mcp.find_costs(cities)
    if not out_raster == '':
        meta = inH.meta.copy()
        meta.update(dtype=costs.dtype)
        with rasterio.open(out_raster, 'w', **meta) as out:
            out.write_band(1, costs)

    return (costs)
Пример #2
0
    def computeMinPath(self, r, c):
        """
        **Internal function** to compute the minimum path between a specific node and all other ones.

        Args:
            c (int): row indice of the consider point
            r (int): column indice of a consider point

        Returns:
            mincost: minimum-cost path for the specified node.

        Note:
            This function relies on scikit-image_ (image processing in python) and finds
            distance-weighted minimum cost paths through an n-d costs array.

        .. _scikit-image: https://scikit-image.org/docs/dev/api/skimage.graph.html
        """
        # Create the cost surface based on the square of the difference in elevation between the considered
        # node and all the others vertices
        weight = np.square(self.nz - self.nz[r, c])

        # From the weight-surface we create a 'landscape graph' object which can then be
        # analysed using distance-weighted minimum cost path
        cost = graph.MCP_Geometric(weight, fully_connected=self.diagonals)

        # Calculate the least-cost distance from the start cell to all other cells
        return cost.find_costs(starts=[(r, c)])[0]
def open_ds_and_array(CostSurfacefn):
	in_ds = gdal.Open(CostSurfacefn)
	in_band = in_ds.GetRasterBand(1)
	geotransform = in_ds.GetGeoTransform()
	costarray = in_band.ReadAsArray()
	holder_array = np.zeros_like(costarray, dtype=np.int32)
	costDistMCP = graph.MCP_Geometric(costarray, fully_connected=True)
	return in_ds, in_band, geotransform, costarray, holder_array, costDistMCP
Пример #4
0
def generate_market_sheds(inR, inH, out_file='', verbose=True, factor=1000, bandIdx=0):
    ''' identify pixel-level maps of market sheds based on travel time    
    INPUTS
        inR [rasterio] - raster from which to grab index for calculations in MCP
        inH [geopandas data frame] - geopandas data frame of destinations
        factor [int] - value by which to multiply raster 
        
    RETURNS
        [numpy array] - marketsheds by index
        
    NOTES:
        Incredible help from StackOverflow:
        https://stackoverflow.com/questions/62135639/mcp-geometrics-for-calculating-marketsheds
        https://gist.github.com/bpstewar/9c15fc0948e82aa9667f1b04fd2c0295
    '''
    xx = inR.read()[bandIdx,:,:] * factor
    orig_shape = xx.shape
    # In order to calculate the marketsheds, the input array needs to be NxN shape, 
    #   at the end, we will select out the original shape in order to write to file
    max_speed = xx.max()
    if xx.shape[0] < xx.shape[1]:
        extra_size = np.zeros([(xx.shape[1] - xx.shape[0]), xx.shape[1]]) + max_speed
        new_xx = np.vstack([xx, extra_size])
        
    if xx.shape[1] < xx.shape[0]:
        extra_size = np.zeros([(xx.shape[0] - xx.shape[1]), xx.shape[0]]) + max_speed
        new_xx = np.hstack([xx, extra_size])        
    mcp = graph.MCP_Geometric(new_xx)
    
    
    dests = get_mcp_dests(inR, inH)    
    costs, traceback = mcp.find_costs(dests)
    
    offsets = _mcp.make_offsets(2, True)
    offsets.append(np.array([0, 0]))
    offsets_arr = np.array(offsets)
    indices = np.indices(traceback.shape)
    offset_to_neighbor = offsets_arr[traceback]
    neighbor_index = indices - offset_to_neighbor.transpose((2, 0, 1))
    ids = np.arange(traceback.size).reshape(costs.shape)
    neighbor_ids = np.ravel_multi_index(
        tuple(neighbor_index), traceback.shape
    )
    g = sparse.coo_matrix((
        np.ones(traceback.size),
        (ids.flat, neighbor_ids.flat),
    ), shape=[traceback.size, traceback.size]).tocsr()
    n, components = sparse.csgraph.connected_components(g)
    basins = components.reshape(costs.shape)
    out_basins = basins[:orig_shape[0], :orig_shape[1]]
    if out_file != '':
        meta = inR.meta.copy()
        meta.update(dtype=out_basins.dtype)
        with rasterio.open(out_file, 'w', **meta) as out_raster:
            out_raster.write_band(1, out_basins)
    else:
        return(out_basins)
Пример #5
0
def connect_centerline(centerlines, dv_costs):
    """Connect a set of centerline pixel segments into a single spanning tree.

    Parameters:
        centerlines: a mask image of centerline pixels, as returned by find_centerline_pixels()
        dv_costs: a traversal-cost matrix based on the dv coordinages, as returned by get_costs()

    Returns: connected_centerline, is_loop
        connected_centerline: mask image with a single connected component: either
            a large loop (containing over 85% of the centerline pixels), or a
            spanning tree of the non-loop components where all of the endpoints
            are mutually reachable via the centerline pixels, or the lowest-cost
            path between components.
        is_loop: True if a single large loop or lariat (with 0 or 1 endpoints),
            was returned; if False if a spanning tree with two or more endpoints
            was returned.
    """
    # retain only pixels that have at least one nonzero neighbor (i.e. remove stray singletons)
    centerlines = centerlines & ndimage.maximum_filter(
        centerlines, footprint=[[1, 1, 1], [1, 0, 1], [1, 1, 1]])
    costs = numpy.array(dv_costs)
    costs[centerlines] = 0
    endpoints = worm_spline.get_endpoints(centerlines)
    ep_indices = numpy.transpose(endpoints.nonzero())
    # check for any large loops/lariats, and if found, just return that for special handling
    # NB: no handling of trying to connect segments to loops; too complex
    labels, num_segments = ndimage.label(centerlines,
                                         structure=FULLY_CONNECTED)
    centerline_px = centerlines.sum()
    for i in range(1, num_segments + 1):
        segment_mask = labels == i
        segment_endpoints = segment_mask & endpoints
        if segment_endpoints.sum() < 2:
            # the segment contains a loop
            if segment_mask.sum() > 0.85 * centerline_px:
                return segment_mask, True
    if num_segments == 1:
        # it's not a loop/lariat because that would be caught above, and there's no reason
        # to try to connect it because there's just one, so return it now
        return centerlines, False
    # find minimum-cost paths from arbitrary endpoint to all others
    spanning_tree = numpy.zeros_like(centerlines)
    mcp = graph.MCP_Geometric(costs)
    if len(ep_indices) > 1:
        mcp.find_costs(starts=[ep_indices[0]], ends=ep_indices[1:])
        for end in ep_indices[1:]:
            path = mcp.traceback(end)
            spanning_tree[tuple(numpy.transpose(path))] = 1
    # The spanning tree could actually have become a loop or lariat structure
    # so check how many endpoints we have
    endpoints = worm_spline.get_endpoints(spanning_tree)
    return spanning_tree, endpoints.sum() < 2
Пример #6
0
def compute_cost(inputfile, outputfile):
    src1 = rio.open(inputfile)
    arr1 = src1.read()[0]
    lg = graph.MCP_Geometric(arr1)
    lcd = lg.find_costs(starts=[(41, 3)])[0]
    dtype = lcd.dtype
    transform = src1.transform
    crs = src1.crs
    with rasterio.open(outputfile,
                       'w',
                       driver='GTiff',
                       height=src1.shape[0],
                       width=src1.shape[1],
                       count=1,
                       dtype=dtype,
                       crs=crs,
                       transform=transform) as dst:
        dst.write(lcd, 1)
Пример #7
0
def shortest_path_in_score_image(scoreImg, returnPath=False, yLength=1.):
    mcp = skg.MCP_Geometric(scoreImg,sampling=(yLength,1.),\
        offsets=[(1,0),(0,1),(-1,0),(1,1),(-1,1)])
    starts = np.zeros((scoreImg.shape[0], 2))
    starts[:, 0] = np.arange(scoreImg.shape[0])
    ends = np.zeros((scoreImg.shape[0], 2))
    ends.fill(scoreImg.shape[1] - 1)
    ends[:, 0] = np.arange(scoreImg.shape[0])
    cumCosts, trace = mcp.find_costs(starts=starts, ends=ends)

    yMinEnd = np.argmin(cumCosts[:, -1])
    minPath = mcp.traceback([yMinEnd, scoreImg.shape[1] - 1])
    p = np.array(minPath).T
    pathImg = np.zeros(scoreImg.shape, dtype='int')
    pathImg[p[0], p[1]] = 1.
    if (returnPath):
        return pathImg, p
    return pathImg
Пример #8
0
def shortest_path_cluster(array, seed, geometric=True):
    """
    cluster array cells around seeds such that the connecting path has minimum cost
    The cost of a path is the sum of the array cells value on the path 

    *** This function require the skimage (scikits image) module ***

    :Input:
        array: float   array containing the cost of each pixels
        seed:  integer array where 0 are background cells, to cluster around seeds
               and positive value are clustering seed (the value is the label)
        geometric: if True, weight diagonal edges by 1/sqrt(2)
                   - see skimage.graph.MCP and MCP_geometric for details -
                   
    :Output:
        labeled array, i.e. an integer array where each cell has the value
        of the closest seed
    """
    import skimage.graph as graph

    # create graph object
    if geometric: g = graph.MCP_Geometric(array)
    else: g = graph.MCP(array)

    c, t = g.find_costs(zip(*seed.nonzero()))  # compute minimum path

    # convert skimage.graph trace to parenting index-map (with flat indices)
    offsets = _np.concatenate(
        (-g.offsets, [[0] * seed.ndim]))  # add null shift at end of offsets
    p = _np.arange(seed.size) + _ravel(
        offsets[t.ravel()],
        array.shape)  #_np.dot(offsets[t.ravel()],[array.shape[1],1])

    # find tree roots (top ancestor of all array elements)
    # iteratively replace parent indices by grand parent, until there is no change
    gp = p[p]  # grand-parent
    ggp = p[gp]  # grand-grand-parent
    while _np.any(gp != ggp):
        gp = ggp
        ggp = p[gp]

    gp.shape = seed.shape

    return seed.ravel()[gp]
Пример #9
0
def get_feasible_path(stack):
    nt, nx, ny = stack.shape

    mcp_obj = sig.MCP_Geometric(
        stack, [[1, 0, 0], [1, 0, 1], [1, 0, -1], [1, 1, 0], [1, -1, 0],
                [1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, -1]])

    starts = [(0, i, j) for (i, j) in np.ndindex(nx, ny)]
    ends = [(nt - 1, i, j) for (i, j) in np.ndindex(nx, ny)]
    costs, traceback = mcp_obj.find_costs(starts, ends)

    i = costs[-1].argmin()
    ix, iy = np.unravel_index(i, costs[-1].shape)

    print "cost:", costs[-1].min()
    #import matplotlib.pyplot as plt; plt.imshow(stack[0],extent = [yticks[0],yticks[-1],xticks[0],xticks[-1]],origin='lower')

    triples = mcp_obj.traceback((nt - 1, ix, iy))
    return np.array([(ix, iy) for (t, ix, iy) in triples])
Пример #10
0
    def _computeMinPath(self, r, c):
        """
        This function computes the minimum path between of specific nodes and all other nodes
        in the array.

        Parameters
        ----------
        variables: c, r
            Rocw, column indices of the consieder point.
        """
        # Create the cost surface based on the square of the difference in elevation between the considered
        # node and all the others vertices
        weight = np.square(self.data - self.data[r, c])

        # From the weight-surface we create a 'landscape graph' object which can then be
        # analysed using least-cost modelling
        cost = graph.MCP_Geometric(weight, fully_connected=self.connected)

        # Calculate the least-cost distance from the start cell to all other cells
        return cost.find_costs(starts=[(r, c)])[0]
Пример #11
0
def get_weights(flats, aux_topo, presills_pos):
    """
    This function calculate weights in the flats areas by doing a cost-distance analysis.
    It uses presill positions as seed locations, and an auxiliar topography as friction
    surface.

    Parameters:
    -----------
    flats : *numpy.array* [dtype = np.bool]
      Numpy array with the location of the flats surfaces
    aux_topo: *numpy.array* [dtype = np.float32]
      Numpy array with the auxiliar topography
    presill_pos *list*
      List of tuples (row, col) with the location of the presills

    Returns:
    --------
    weigths : *numpy.array*
      Numpy array with the cost of routing throught the flat areas
    
    References:
    -----------
    This algoritm is adapted from the TopoToolbox matlab codes by Wolfgang Schwanghart.
    
    Schwanghart, W., Kuhn, N.J., 2010. TopoToolbox: A set of Matlab functions 
    for topographic analysis. Environ. Model. Softw. 25, 770–781. 
    https://doi.org/10.1016/j.envsoft.2009.12.002
    
    Schwanghart, W., Scherler, D., 2014. Short Communication: TopoToolbox 2 - 
    MATLAB-based software for topographic analysis and modeling in Earth 
    surface sciences. Earth Surf. Dyn. 2, 1–7. https://doi.org/10.5194/esurf-2-1-2014
    """
    flats = np.invert(flats)
    aux_topo[flats] = 99999
    if len(presills_pos) > 0:
        lg = graph.MCP_Geometric(aux_topo)
        aux_topo = lg.find_costs(starts=presills_pos)[0] + 1
    aux_topo[flats] = -99999
    
    return aux_topo
Пример #12
0
    def open_ds_and_array(self):
        """Function to open impedance raster and create arrays of same dimensions
		to hold the output data.

		Arguments:
		None

		Returns:
		impedance raster, band, geotransform, cost_array (Numpy array of impedance raster),
		holder_arr (empty array in which to place output - same dimensions as impedance),
		 graph dataset of impedance raster
		"""
        in_ds = gdal.Open(self.CostSurfacefn)  #Open impedance raster with GDAL
        in_band = in_ds.GetRasterBand(1)  #Access raster band
        geotransform = in_ds.GetGeoTransform(
        )  #Access Geotransform data - Origin, pixel dimensions etc
        costarray = in_band.ReadAsArray(
        )  #Read impedance raster into NumPy array
        holder_array = np.zeros_like(
            costarray, dtype=np.int32)  #Empty copy of impedance raster
        costDistMCP = graph.MCP_Geometric(
            costarray, fully_connected=True
        )  #Create graph dataset of impedance raster using scikit-image
        return in_ds, in_band, geotransform, costarray, holder_array, costDistMCP
Пример #13
0
#Initialize the arrayList and edgeList lists
arrList = []
edgeList = []

#Loop through each patch and compute its cost distance to all other patches
for patchID in patchIDs:
    arcpy.SetProgressorLabel("Patch {} of {}".format(patchID, steps))

    #Reclassify cost in source patch cells to zero
    arrCostMod = arrCost.copy()
    arrCostMod[arrPatch == patchID] = 0
    arrCostMod[arrCostMod == -9999] = 100000

    #Create the MCP object (Geometric accounts for diagonals)
    lg = graph.MCP_Geometric(arrCostMod, sampling=(cellSize, cellSize))

    #Get the index of a cell in the current patch ID
    i, j = np.where(arrPatch == patchID)
    startCells = list(zip(i, j))

    #Compute cost distances away from a source
    lcd = lg.find_costs(starts=startCells)[0]

    #Write the output to the edgelist
    for toID in patchIDs:
        if toID > patchID:
            edgeList.append((patchID, toID, lcd[arrPatch == toID].min()))

    #Add array to arrList
    arrList.append(lcd)
Пример #14
0
    colp = colp[valid_rc]
    # Discard cells (row-col pairs) that do not fullfill both conditions
    cond01 = zvals[row[valid_rc], col[valid_rc]] == zvals[rowp, colp]
    cond02 = flats_arr[rowp, colp]
    valid_pix = np.logical_and(cond01, cond02)
    ps_rows = np.append(ps_rows, rowp[valid_pix])
    ps_cols = np.append(ps_cols, colp[valid_pix])

ps_pos = zip(ps_rows, ps_cols)
print("Presills identified -- {0:.3f} seconds".format(time.time() - time_lap))

time_lap = time.time()
# 05 Calculate weights for the cost-distance analysis
flats_arr = np.invert(flats_arr)
topodiff[flats_arr] = 99999
lg = graph.MCP_Geometric(topodiff)
topodiff = lg.find_costs(starts=ps_pos)[0] + 1
topodiff[flats_arr] = -99999
print("Weights calculated -- {0:.3f} seconds".format(time.time() - time_lap))

time_lap = time.time()
# 06 Sort pixels
# Sort the flat areas
rdem = zvals.ravel()
topodiff = topodiff.ravel()
ix_flats = np.argsort(-topodiff, kind='mergesort')

# Sort the rest of the pixels from the DEM
ndx = np.arange(ncells, dtype=np.int)
ndx = ndx[ix_flats]
ix = ndx[np.argsort(-rdem[ndx], kind='mergesort')]
     str(bext[0]),
     str(bext[2]),
     str(bext[1]),
     str(bext[3]), "-input_file_list", forfileindex, csrast
 ])
 cso = hfu.raster2array(csrast)
 cs = cso[0]
 cs[cs == 255] = 1000  # set no go areas to higher value
 # Give x and y coordinates. Returns row and column indices of coordinates
 locs1 = hfu.coord2pixelOffset(csrast, [cx1], [cy1])
 locs1 = [i[0] for i in locs1]  # drop list level
 locs1 = [(locs1[0], locs1[1])]  # convert to a list of lists...
 # Get distance from point in first layer
 # Create graph object
 go = graph.MCP_Geometric(cs,
                          offsets=None,
                          sampling=(xint, yint),
                          fully_connected=True)
 cc1, tb = go.find_costs(locs1)
 del tb  # delete traceback
 locs2 = hfu.coord2pixelOffset(csrast, [cx2], [cy2])
 locs2 = [i[0] for i in locs2]  # drop list level
 locs2 = [(locs2[0], locs2[1])]  # convert to list of lists...
 # Create graph object
 go = graph.MCP_Geometric(cs,
                          offsets=None,
                          sampling=(xint, yint),
                          fully_connected=True)
 cc2, tb = go.find_costs(locs2)  # this may take x,y order...
 del tb  # delete traceback
 # Add cost distance surfaces together
 cct = cc1 + cc2
Пример #16
0
    def run(self):
        """Run method that performs all the real work"""
        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = Cost_surface_generalizationDialog()
        # On regarde l'état des cases à cocher au lancement de l'extension pour afficher les composants que s'il le faut
        if not self.dlg.checkBox_etendue.isChecked():
            self.dlg.label_output_etendue.hide()
            self.dlg.lineEdit_output_etendue.hide()
            self.dlg.pushButton_output_etendue.hide()

        if not self.dlg.checkBox_moyenne.isChecked():
            self.dlg.label_output_moyenne.hide()
            self.dlg.lineEdit_output_moyenne.hide()
            self.dlg.pushButton_output_moyenne.hide()

        if not self.dlg.checkBox_variance.isChecked():
            self.dlg.label_output_variance.hide()
            self.dlg.lineEdit_output_variance.hide()
            self.dlg.pushButton_output_variance.hide()

        # création de plusieurs listes qui contiendront les couches chargées dans le projet QGIS en fonction de
        # certaines caractéritiques (vecteur, raster, csv [spatiale ou non])
        networks_list = []
        raster_list = []
        points_list = []
        # récupération de toutes les couches du projet courant
        layers = QgsProject.instance().mapLayers().values()
        # Parcours des couches
        for l in layers:
            # si la couche est vectorielle
            if l.type() == QgsMapLayer.VectorLayer:
                # si elle est spatiale
                if l.isSpatial():
                    # si entités ponctuelles (== 0)
                    if l.geometryType() == 0:
                        points_list.append(l.name())
                    # si entités linéaires (== 1)
                    if l.geometryType() == 1:
                        networks_list.append(l.name())
                # si elle n'est pas spatiale (fichier csv non spatialisé)
                else:
                    points_list.append(l.name())
            # si la couche est raster
            if l.type() == QgsMapLayer.RasterLayer:
                raster_list.append(l.name())

        # on commence par supprimer ce que contiennent les listes déroulantes pour ne pas que ça s'ajoute chaque fois
        # à celle existante lorsqu'on appelle l'extension
        self.dlg.comboBox_vector.clear()
        self.dlg.comboBox_vector.addItems(networks_list)

        self.dlg.comboBox_raster.clear()
        self.dlg.comboBox_raster.addItems(raster_list)

        self.dlg.comboBox_csv.clear()
        self.dlg.comboBox_csv.addItems(points_list)

        # affichage de l'interface du plugin
        self.dlg.show()

        # Si aucune couche n'est chargée dans le projet --> erreur
        if len(layers) == 0:
            QMessageBox.critical(self.dlg, "Erreur", "Il n'y a pas de couche.")
            return
        # Si aucune couche RASTER n'est chargée dans le projet --> erreur
        if len(raster_list) == 0:
            QMessageBox.critical(self.dlg, "Erreur", "Il n'y a pas de couche raster.")
            return
        # Si aucune couche VECTORIELLE n'est chargée dans le projet --> erreur
        if len(networks_list) == 0:
            QMessageBox.critical(self.dlg, "Erreur", "Il n'y a pas de couche vectorielle de réseau routier.")
            return
        # Si aucune couche provenant d'un fichier CSV n'est chargée dans le projet --> erreur
        if len(points_list) == 0:
            QMessageBox.critical(self.dlg, "Erreur", "Il n'y a pas de fichier CSV importé.")
            return

        # suppression de ce que contiennent les listes déroulantes (cf. infra)
        self.dlg.comboBox_attribute_ID.clear()
        self.dlg.comboBox_length.clear()
        self.dlg.comboBox_speed.clear()
        # récupération de la couche courante de la liste déroulante de la couche vectorielle du réseau routier
        name_layer = self.dlg.comboBox_vector.currentText()
        # parcours des couches du projet courant
        for l in layers:
            # lorsqu'on trouve la couche dans la liste, on remplit les 3 listes déroulantes des champs d'attribut
            if l.name() == name_layer:
                pr = l.dataProvider()
                fields = pr.fields()
                self.dlg.comboBox_attribute_ID.addItems(field.name() for field in fields)
                self.dlg.comboBox_speed.addItems(field.name() for field in fields)
                self.dlg.comboBox_length.addItems(field.name() for field in fields)
        # lorsque la couche courante de la liste déroulante de la couche vectorielle change, on appelle les méthodes
        # qui permettent la modification des listes déroulantes qui en dépendent
        self.dlg.comboBox_vector.currentIndexChanged.connect(self.layer_changed_ID)
        self.dlg.comboBox_vector.currentIndexChanged.connect(self.layer_changed_speed)
        self.dlg.comboBox_vector.currentIndexChanged.connect(self.layer_changed_length)

        # lorsque l'état des cases à cocher est modifié (via un clic), on appelle les méthodes qui permettent
        # l'affichage ou la suppression des composants d'export de fichier
        self.dlg.checkBox_etendue.stateChanged.connect(self.in_visible_etendue)
        self.dlg.checkBox_moyenne.stateChanged.connect(self.in_visible_moyenne)
        self.dlg.checkBox_variance.stateChanged.connect(self.in_visible_variance)

        # pour éviter que les connections avec le clicked s'ajoutent à chaque lancement de l'extension
        try:
            self.dlg.pushButton_output_etendue.clicked.disconnect()
            self.dlg.pushButton_output_moyenne.clicked.disconnect()
            self.dlg.pushButton_output_variance.clicked.disconnect()
        except:
            pass
        # lorsque le bouton pour la définition des fichiers en output est  déclenché, on appelle les méthodes associées
        self.dlg.pushButton_output_etendue.clicked.connect(self.select_output_file_etendue)
        self.dlg.pushButton_output_moyenne.clicked.connect(self.select_output_file_moyenne)
        self.dlg.pushButton_output_variance.clicked.connect(self.select_output_file_variance)

        # Run the dialog event loop
        result = self.dlg.exec_()
        # Lorsque "OK" est cliqué, on entre ici
        if result:
            # récupération des états des cases à cocher afin de définir les processus à lancer
            ischecked_etendue = self.dlg.checkBox_etendue.isChecked()
            ischecked_moyenne = self.dlg.checkBox_moyenne.isChecked()
            ischecked_variance = self.dlg.checkBox_variance.isChecked()

            # si aucune méthode sélectionnée : erreur
            if not ischecked_etendue and not ischecked_moyenne and not ischecked_variance:
                QMessageBox.critical(self.dlg, "Erreur", "Aucune méthode sélectionnée.")
                return

            # récupération des chemin d'accès complet (i.e. avec nom de fichier) des output si les cases à cocher
            # sont remplies
            if ischecked_etendue:
                output_etendue = self.dlg.lineEdit_output_etendue.text()
            if ischecked_moyenne:
                output_moyenne = self.dlg.lineEdit_output_moyenne.text()
            if ischecked_variance:
                output_variance = self.dlg.lineEdit_output_variance.text()

            # si aucun chemin n'est défini alors que la méthode est choisie : erreur
            if ischecked_etendue and not output_etendue:
                QMessageBox.critical(self.dlg, "Erreur", "Aucun chemin d'accès spécifié pour la première méthode.")
                return

            if ischecked_moyenne and not output_moyenne:
                QMessageBox.critical(self.dlg, "Erreur", "Aucun chemin d'accès spécifié pour la deuxième méthode.")
                return

            if ischecked_variance and not output_variance:
                QMessageBox.critical(self.dlg, "Erreur", "Aucun chemin d'accès spécifié pour la troisième méthode.")
                return

            # récupération des différents éléments courants dans toutes les listes déroulantes
            shp_name = self.dlg.comboBox_vector.currentText()
            attribute_ID = self.dlg.comboBox_attribute_ID.currentText()
            length = self.dlg.comboBox_length.currentText()
            speed = self.dlg.comboBox_speed.currentText()
            raster_name = self.dlg.comboBox_raster.currentText()
            csv_name = self.dlg.comboBox_csv.currentText()

            # on récupère les couches vecteur, raster et CSV
            for l in layers:
                if l.name() == shp_name:
                    vlayer = l
                if l.name() == raster_name:
                    rlayer = l
                if l.name() == csv_name:
                    csvreader = l

            # Création d'une fenêtre de dialogue qui informe que le prcessus est toujours en cours
            dialog = QProgressDialog()
            dialog.setWindowTitle("Progress")
            bar = QProgressBar(dialog)
            bar.setTextVisible(True)
            bar.setRange(0,0)
            dialog.setBar(bar)
            dialog.setMinimumWidth(300)
            dialog.show()

            # création de listes pour la récupération des données spatiales et temporelles
            x_sources = []
            y_sources = []
            event_time = []
            event_time_seconds = []
            features = csvreader.getFeatures()
            for feature in features:
                attrs = feature.attributes()
                x_sources.append(attrs[0])
                y_sources.append(attrs[1])
                # récupération des données temporelles uniquement dans le cas de la méthode de l'étendue, car elle n'est
                # pas utilisée dans les autres méthodes. On essaie de les récupérer ("try") et si on n'y arrive pas,
                # on catch l'exception (cas de données temporelles manquantes)
                if ischecked_etendue:
                    try:
                        event_time.append(attrs[2])
                    except:
                        QMessageBox.critical(self.dlg, "Erreur","Données temporelles manquantes.")
                        return

            if ischecked_etendue:
                # si la première méthode a été sélectionnée, on met l'heure en secondes en séparant les heures des
                # minutes puis en convertissant le tout en secondes
                for d in event_time:
                    # récupération de l'heure,
                    event_time_split = d.split(':')
                    # on remet l'heure du crime en secondes de la journée (1 heure = 3600 secondes, 1 minute = 60
                    # secondes), si on n'y arrive pas c'est que le format est invalide
                    try:
                        event_time_seconds.append(int(event_time_split[0]) * 3600 + int(event_time_split[1]) * 60)
                    except:
                        QMessageBox.critical(self.dlg, "Erreur","Format des données temporelles invalide.")
                        return

            # Déclaration de deux listes qui vont contenir les coordonnées résultantes de la méthode "closest_point"
            y_road = []
            x_road = []
            # Récupération de tous les points du réseau routier correspondant aux faits
            for p in range(0, len(x_sources), 1):
                y, x = self.closest_point(float(x_sources[p]), float(y_sources[p]), vlayer,rlayer)
                y_road.append(y)
                x_road.append(x)

            # Définition d'une liste contenant des tuples "ID tronçon - coût" pour la création de la surface de friction
            # améliorée
            tuple_ID_cost = []
            features = vlayer.getFeatures()
            # parcours des tronçons routier du réseau routier
            for feature in features:
                # ID du tronçon pour le comptage des pixels par tronçon et cost est le coût en SECONDES du passage du
                # tronçon total(combien de secondes pour parcourir tout le tronçon)
                cost = feature[length] / (feature[speed] / 3.6)
                tuple_ID_cost.append([int(feature[attribute_ID]), cost])

            # Récupération des métadonnées du raster
            prov = rlayer.dataProvider()
            rows = rlayer.height()
            cols = rlayer.width()
            extent = prov.extent()
            xmin = extent.xMinimum()
            ymax = extent.yMaximum()
            xsize = rlayer.rasterUnitsPerPixelX()
            ysize = rlayer.rasterUnitsPerPixelY()

            # Récupération du "block", i.e. un objet QGIS contenant les données du raster sous forme d'array
            block = prov.block(1, prov.extent(), cols, rows)
            no_data = block.noDataValue()

            # on affecte le block à une matrice numpy pour effectuer les traitements
            data = np.zeros((rows,cols))
            for i in range(rows):
                for j in range(cols):
                    data[i,j] = block.value(i,j)

            # On récupère chaque identifiant unique de tronçon dans "unique_elements" et le nombre de pixels associé
            # dans "count_elements"
            unique_elements, count_elements = np.unique(data, return_counts=True)

            # Parcours de tous les ID uniques sauf le premier qui est NODATA. Une deuxième boucle va quant à
            # elle parcourir les tuples "ID - cost" de manière à matcher les ID et ainsi obtenir pour un ID donné, le
            # nombre de pixels associé ainsi que le coût total pour parcourir ce tronçon.
            for i in range(1, len(unique_elements), 1):
                for d in tuple_ID_cost:
                    # si les ID matchent
                    if int(unique_elements[i]) == d[0]:
                        # normalisation du coût (d[1]) par pixel
                        cost_by_pixel = d[1] / count_elements[i]
                        # Modification de chaque pixel de data si la valeur est égale à cet ID (d[0])
                        data[data == d[0]] = cost_by_pixel

            if ischecked_etendue:
                """Si la méthode de l'étendue a été choisie, création d'une deep copy d'un array numpy (i.e. une copie
                sans référence à numpy_array de manière à ce que si on modifie la copie, on ne modifie pas l'original).
                On fait ça pour plusieurs array :
                - rster : utilisé dans la fonction "closest_point" pour voir si un pixel donné appartient au réseau 
                routier rasterisé;
                - sooner : contient les temps au plus tôt;
                - later : contient les temps au plus tard;
                - time_difference : contient les différences finales entre temps au plus tard et temps au plus tôt
                Dans sooner, on change tous les pixels du réseau routier à une valeur très grande pour y être inférieur,
                on fait le contraire pour later (temps au plus tard)"""
                sooner = copy.deepcopy(data)
                later = copy.deepcopy(data)
                time_difference = copy.deepcopy(data)
                sooner[sooner != no_data] = 99999
                later[later != no_data] = -1

            if ischecked_moyenne or ischecked_variance:
                # Si au moins l'une des méthodes de la moyenne ou de la variance est choisie, on définit les listes qui
                # serviront à stocker les surfaces de coût, les valeurs des rasters de la méthode de la moyenne et celle
                # de la variance
                cost_surfaceS = []
                moyennes = []
                variances = []

            # Création des surfaces de coût
            for z in range(0, len(x_sources), 1):
                # tranformation de la matrice numpy en un graphe SKIMAGE en passant en argument la surface de friction
                # améliorée
                mcp = graph.MCP_Geometric(data)
                # On applique la fonction de surface de coût partant depuis le pixel du réseau routier obtenu grâce à
                # la fonction "on the road".
                cost_surface, traceback = mcp.find_costs([[y_road[z], x_road[z]]], ends=None, find_all_ends=True,
                                                         max_coverage=1.0, max_cumulative_cost=None, max_cost=None)

                # Si au moins une des deux méthodes de moyenne / variance est sélectionnée, on a juste à stocker toutes
                # les surfaces de coût après leur création
                if ischecked_moyenne or ischecked_variance:
                    cost_surfaceS.append(cost_surface)

                if ischecked_etendue:
                    """Si la méthode de l'étendue est choisie, les traitements doivent en revanche être appliqués à chaque 
                    itération de la boucle. On commence par soustraire les valeurs de la surface de coût au temps de 
                    l'évènement enregistré afin d'avoir un temps régressif. Ensuite, on s'assure de garder toutes les
                    valeurs de la surface de coût dans un intervalle entre minuit (0 seconde) et 86399 (23:59:59). Par
                    après, les comparaisons entre la surface de coût et les rasters des temps au plus tôt et au plus
                    tard sont réalisées et si une valeur respectivement plus tôt et plus tardive est trouvée on 
                    l'affecte"""
                    # Comme on doit avoir un temps depuis l'heure du fait, on soustrait la surface de coût à celle-ci
                    cost_surface = event_time_seconds[z] - cost_surface
                    cost_surface[cost_surface < 0] += 86400

                    sooner_comparison = np.greater(sooner, cost_surface)
                    index_row = 0
                    for d in sooner_comparison:
                        index_col = 0
                        for p in d:
                            if p:  # (if p == True)
                                sooner[index_row, index_col] = cost_surface[index_row, index_col]
                            index_col += 1
                        index_row += 1

                    later_comparison = np.less(later, cost_surface)
                    index_row = 0
                    for d in later_comparison:
                        index_col = 0
                        for p in d:
                            if p:
                                later[index_row, index_col] = cost_surface[index_row, index_col]
                            index_col += 1
                        index_row += 1

            # une fois que les rasters au plus tôt et au plus tard ont été comparés à toutes les surfaces de coût,
            # on créé le raster finale des différences "max - min"
            if ischecked_etendue:
                for index_row in range(0, rows, 1):
                    for index_col in range(0, cols, 1):
                        if time_difference[index_row, index_col] != -99:
                            time_difference[index_row, index_col] = later[index_row, index_col] \
                                                                    - sooner[index_row, index_col]

            if ischecked_moyenne or ischecked_variance:
                """Concernant les méthodes moyenne / variance, on parcourt tous les rasters de la liste et on calcule
                pour chaque pixel homologue du réseau routier (ici, la liste element contient toutes les valeurs des 
                pixels homologues du réseau routier, la moyenne ou la variance. Pour les pixels hors réseau routier,
                la valeur NoData est affectée."""
                for arr in zip(*cost_surfaceS):
                    for element in zip(*arr):
                        if float("-inf") not in element and float("inf") not in element:
                            if ischecked_moyenne:
                                mean = np.mean(element, dtype=np.float32)
                                moyennes.append(float(mean))
                            if ischecked_variance:
                                var = np.var(element, dtype=np.float32)
                                variances.append(float(var))
                        else:
                            if ischecked_moyenne:
                                moyennes.append(no_data)
                            if ischecked_variance:
                                variances.append(no_data)
                # les array 1D dont transformés en arrays 2D avec la fonction reshape de Numpy
                if ischecked_moyenne:
                    moyennes = np.array(moyennes)
                    moyennes = np.reshape(moyennes, (rows,cols))

                if ischecked_variance:
                    variances = np.array(variances)
                    variances = np.reshape(variances, (rows, cols))

            geotransform = [xmin, xsize, 0, ymax, 0, -ysize]
            driver = gdal.GetDriverByName("GTiff")
            # récupération du SRS de la couche vectorielle du réseau routier
            srs = vlayer.sourceCrs()

            """Pour chaque choix de méthode, on procède à l'export de fichiers si elles sont choisies. On créé d'abord
             un fichier vide qu'on remplit avec les arrays 2D créés auparavant. On définit la valeur NoData, l'extension
             , la résolution et la projection."""
            if ischecked_etendue:
                ds_etendue = driver.Create(output_etendue, cols, rows, 1, gdal.GDT_Float32)

                band_etendue = ds_etendue.GetRasterBand(1)
                band_etendue.WriteArray(time_difference)
                band_etendue.SetNoDataValue(no_data)

                ds_etendue.SetGeoTransform(geotransform)
                ds_etendue.SetProjection(srs.toWkt())
                ds_etendue = None

                lyr_etendue = QgsRasterLayer(output_etendue, "Etendue")
                if not lyr_etendue.isValid():
                    QgsMessageLog.logMessage("Layer failed to load!")
                    return
                QgsProject.instance().addMapLayer(lyr_etendue)

            if ischecked_moyenne:
                ds_moyenne = driver.Create(output_moyenne, cols, rows, 1, gdal.GDT_Float32)

                band_moyenne = ds_moyenne.GetRasterBand(1)
                band_moyenne.WriteArray(moyennes)
                band_moyenne.SetNoDataValue(no_data)

                ds_moyenne.SetGeoTransform(geotransform)
                ds_moyenne.SetProjection(srs.toWkt())
                ds_moyenne = None

                lyr_mean = QgsRasterLayer(output_moyenne, "Moyenne")
                if not lyr_mean.isValid():
                    QgsMessageLog.logMessage("Layer failed to load!")
                    return
                QgsProject.instance().addMapLayer(lyr_mean)

            if ischecked_variance:
                ds_variance = driver.Create(output_variance, cols, rows, 1, gdal.GDT_Float32)

                band_variance = ds_variance.GetRasterBand(1)
                band_variance.WriteArray(variances)
                band_variance.SetNoDataValue(no_data)

                ds_variance.SetGeoTransform(geotransform)
                ds_variance.SetProjection(srs.toWkt())
                ds_variance = None

                lyr_var = QgsRasterLayer(output_variance, "Variance")
                if not lyr_var.isValid():
                    QgsMessageLog.logMessage("Layer failed to load!")
                    return
                QgsProject.instance().addMapLayer(lyr_var)

            """Enfin, on affiche à l'utilisateur que le(s) fichier(s) ont bien été créés et le chemin d'accès vers ceux-
            ci"""
            if ischecked_etendue:
                self.iface.messageBar().pushMessage("Success", "File created in %s" % output_etendue,level=Qgis.Success, duration=3)

            if ischecked_moyenne:
                self.iface.messageBar().pushMessage("Success", "File created in %s" % output_moyenne,level=Qgis.Success, duration=3)

            if ischecked_variance:
                self.iface.messageBar().pushMessage("Success", "File created in %s" % output_variance,level=Qgis.Success, duration=3)
            return
Пример #17
0
def _get_aux_topography(dem):
    """
    This function calculate the auxiliary topography by using the topography in 
    depressions to derive the most realistic flow paths. It uses as weight sweights the 
    differences between the filled and the raw DEM.
    
    References:
    -----------
    This algoritm is adapted to Python from TopoToolbox matlab codes by Wolfgang Schwanghart 
    (version 2017-09-02). It is equivalent to use the preprocessing option
    'carve' in that code (default preprocessing option).
    
    Schwanghart, W., Kuhn, N.J., 2010. TopoToolbox: A set of Matlab functions 
    for topographic analysis. Environ. Model. Softw. 25, 770–781. 
    https://doi.org/10.1016/j.envsoft.2009.12.002
    
    Schwanghart, W., Scherler, D., 2014. Short Communication: TopoToolbox 2 - 
    MATLAB-based software for topographic analysis and modeling in Earth 
    surface sciences. Earth Surf. Dyn. 2, 1–7. https://doi.org/10.5194/esurf-2-1-2014
    """

    # Fill the DEM *
    fill = dem.fill_sinks()
    diff = fill.read_array() - dem.read_array()
    dem = fill

    # Identify flats and sills *
    flats, sills = dem.identify_flats(nodata=False)

    # Derive the cost of routing through sills *
    carvemin = 0.1
    struct = np.ones((3, 3), dtype="int8")

    lbl_arr, nlbl = ndimage.label(flats.read_array(), structure=struct)
    lbls = np.arange(1, nlbl + 1)

    tweight = 2

    for lbl in lbls:
        diff[lbl_arr == lbl] = (diff[lbl_arr == lbl].max() -
                                diff[lbl_arr == lbl])**tweight + carvemin

    del lbl_arr

    # Get presill pixels i.e. pixels immediately upstream to sill pixels *
    zvals = dem.read_array()
    flats_arr = flats.read_array().astype("bool")
    dims = zvals.shape
    row, col = sills.find()

    rowadd = np.array([-1, -1, 0, 1, 1, 1, 0, -1])
    coladd = np.array([0, 1, 1, 1, 0, -1, -1, -1])
    presill_rows = np.array([], dtype="int")
    presill_cols = np.array([], dtype="int")

    for n in range(8):
        rowp = row + rowadd[n]
        colp = col + coladd[n]
        # Avoid neighbors outside array (remove cells and their neighbors)
        valid_rc = (rowp >= 0) & (colp >= 0) & (rowp < dims[0]) & (colp <
                                                                   dims[1])
        rowp = rowp[valid_rc]
        colp = colp[valid_rc]
        # Discard cells (row-col pairs) that do not fullfill both conditions
        cond01 = zvals[row[valid_rc], col[valid_rc]] == zvals[rowp, colp]
        cond02 = flats_arr[rowp, colp]
        valid_pix = np.logical_and(cond01, cond02)
        presill_rows = np.append(presill_rows, rowp[valid_pix])
        presill_cols = np.append(presill_cols, colp[valid_pix])

    starts = [xx for xx in zip(presill_rows, presill_cols)]

    # Calulate auxiliary topography, ie. the cost surface seeded at presill pixels *
    flats_arr = np.invert(flats_arr)
    diff[flats_arr] = 99999
    lg = graph.MCP_Geometric(diff)
    diff = lg.find_costs(starts=starts)[0] + 1
    diff[flats_arr] = -99999

    return diff
Пример #18
0
                                     'skyview': True
                                 })

#%%
O2 = neilpy.openness(Z, cellsize, lookup_pixels=20)

#%%

import numpy as np
from skimage import graph
import matplotlib.pyplot as plt

#%%
cs = Z
cellSize = Zt[0]
lg = graph.MCP_Geometric(cs, sampling=(cellSize, cellSize))
startCell = (5, 5)
lcd = lg.find_costs(starts=[startCell])[0]

#%%

I = np.array([[1, 1, 1], [1, 100, 1], [1, 1, 1]])
lg = graph.MCP_Geometric(I)
startCell = (0, 0)
cumCost, tb = lg.find_costs(starts=[startCell])
print(cumCost)

#%%

import numpy as np
import skimage.graph.mcp as mcp