Esplodere sovrapposti a nuovi poligoni non sovrapposti?


10

Dati più poligoni che si sovrappongono in più modi, vorrei esportare da queste funzionalità tutti i poligoni che non si sovrappongono con altri, in modo iterativo.

Il prodotto sarebbe una serie di funzioni senza sovrapposizioni che, una volta sommate, compongono l'originale.

I prodotti potrebbero quindi essere utilizzati come input per le statistiche zonali e ciò sarebbe molto più rapido rispetto all'iterazione delle statistiche zonali su ciascun poligono.

Ho cercato di codificare questo in ArcPy senza successo.

Il codice per farlo esiste già?


Vuoi dire che vuoi "appiattire" i dati in un set topologicamente corretto?
nagytech,

@Geoist ZonalStats richiede poligoni che non si sovrappongono. Quando hai una raccolta sovrapposta, la soluzione ovvia ma inefficiente è quella di passare in rassegna i polis e calcolare le statistiche zonali una per una. Sarebbe più efficiente selezionare un sottoinsieme di polys non sovrapposti, applicare loro zonalstats e iterare. La domanda chiede come effettuare tali selezioni in modo efficiente.
whuber

whuber - Penso che @Geoist stia suggerendo di creare un insieme di poligoni non sovrapposti dalle intersezioni dei poligoni di input. Guarda questa immagine - (non puoi pubblicare immagini nei commenti?). L'ingresso è a sinistra. L'intera regione è coperta da tre poligoni, ognuno dei quali interseca entrambi gli altri. Gli unici sottoinsiemi non sovrapposti sono i singoli e questi non soddisfano il requisito di Gotanuki che l'unione riempia lo spazio. Penso che Geoist stia suggerendo di creare l'insieme di regioni non intersecanti sulla destra che è valido per gli zonalstats
Llaves,

Penso che ci sia un po 'di confusione su quale dovrebbe essere il prodotto finale. Potresti fornire un esempio? La mia interpretazione è che vorresti che l'output fosse una selezione di poligoni che non si sovrappongono, mentre scarti o dissolvi i poligoni rimanenti. Stai lavorando con una o più classi di funzioni?
Aaron

1
Mi sembra che @gotanuki voglia creare il numero minimo di classi di caratteristiche che contengono solo poligoni non sovrapposti da una classe di caratteristiche poligonali con poligoni sovrapposti
PolyGeo

Risposte:


14

Questo è un problema di colorazione del grafico .

Ricorda che una colorazione del grafico è un'assegnazione di un colore ai vertici di un grafico in modo tale che nessun vertice che condivida un bordo abbia lo stesso colore. In particolare, i vertici (astratti) del grafico sono i poligoni. Due vertici sono collegati con un bordo (non orientato) ogni volta che si intersecano (come poligoni). Se prendiamo una soluzione al problema - che è una sequenza di (diciamo k ) raccolte disgiunte dei poligoni - e assegniamo un colore unico a ciascuna raccolta nella sequenza, allora avremo ottenuto un k -colore del grafico . È desiderabile trovare un piccolo k .

Questo problema è piuttosto difficile e rimane irrisolto per grafici arbitrari. Prendi in considerazione una soluzione approssimativa semplice da codificare. Un algoritmo sequenziale dovrebbe fare. L'algoritmo Welsh-Powell è una soluzione avida basata su un ordine decrescente dei vertici per grado. Tradotto nella lingua dei poligoni originali, prima ordina i poligoni in ordine decrescente del numero di altri poligoni che si sovrappongono. Lavorando in ordine, dai al primo poligono un colore iniziale. In ogni passaggio successivo, prova a colorare il poligono successivo con un colore esistente: ovvero, scegli un colore che non lo siagià utilizzato da nessuno dei vicini di quel poligono. (Esistono molti modi per scegliere tra i colori disponibili; prova quello che è stato meno utilizzato oppure scegline uno a caso.) Se il prossimo poligono non può essere colorato con un colore esistente, creane uno nuovo e coloralo con quello.

Una volta ottenuta una colorazione con un piccolo numero di colori, esegui zonalstats colore per colore: per costruzione, hai la garanzia che non si sovrappongano due poligoni di un determinato colore.


Ecco il codice di esempio in R. (Il codice Python non sarebbe molto diverso.) Innanzitutto, descriviamo le sovrapposizioni tra i sette poligoni mostrati.

Mappa di sette poligoni

edges <- matrix(c(1,2, 2,3, 3,4, 4,5, 5,1, 2,6, 4,6, 4,7, 5,7, 1,7), ncol=2, byrow=TRUE)

Cioè, i poligoni 1 e 2 si sovrappongono, e così fanno i poligoni 2 e 3, 3 e 4, ..., 1 e 7.

Ordina i vertici in ordine decrescente:

vertices <- unique(as.vector(edges))
neighbors <- function(i) union(edges[edges[, 1]==i,2], edges[edges[, 2]==i,1])
nbrhoods <- sapply(vertices, neighbors)
degrees <- sapply(nbrhoods, length)
v <- vertices[rev(order(degrees))]

Un algoritmo di colorazione sequenziale (grezzo) utilizza il primo colore disponibile non ancora utilizzato da nessun poligono sovrapposto:

color <- function(i) {
  n <- neighbors(i)
  candidate <- min(setdiff(1:color.next, colors[n]))
  if (candidate==color.next) color.next <<- color.next+1
  colors[i] <<- candidate
}

Inizializza le strutture dati ( colorse color.next) e applica l'algoritmo:

colors <- rep(0, length(vertices))
color.next <- 1
temp <- sapply(v, color)

Dividi i poligoni in gruppi in base al colore:

split(vertices, colors)

L'output in questo esempio utilizza quattro colori:

$`1`
[1] 2 4

$`2`
[1] 3 6 7

$`3`
[1] 5

$`4`
[1] 1

Quadricromia dei poligoni

Ha suddiviso i poligoni in quattro gruppi non sovrapposti. In questo caso la soluzione non è ottimale ({{3,6,5}, {2,4}, {1,7}} è un tricolore per questo grafico). In generale, tuttavia, la soluzione che ottiene non dovrebbe essere troppo male.


Non sono sicuro che questo risponda alla domanda o quale sia la domanda, ma è comunque una buona risposta.
nagytech,

@Geoist C'è un modo per chiarire l'illustrazione o spiegare meglio il problema?
whuber

6

La metodologia raccomandata da #whuber mi ha ispirato a prendere una nuova direzione, ed ecco la mia soluzione arcpy, in due funzioni. Il primo, chiamato countOverlaps, crea due campi, "overlaps" e "ovlpCount" per registrare per ogni poli quali polys si sovrappongono con esso e quante sovrapposizioni si sono verificate. La seconda funzione, explodeOverlaps, crea un terzo campo, "expl", che fornisce un numero intero univoco a ciascun gruppo di polys non sovrapposti. L'utente può quindi esportare nuovi fc in base a questo campo. Il processo è suddiviso in due funzioni perché penso che lo strumento countOverlaps possa rivelarsi utile da solo. Si prega di scusare la sciattezza del codice (e la convenzione di denominazione negligente), poiché è piuttosto preliminare, ma funziona. Assicurati anche che "idName" field rappresenta un campo con ID univoci (testato solo con ID interi). Grazie whuber per avermi fornito il framework necessario per affrontare questo problema!

def countOverlaps(fc,idName):
    intersect = arcpy.Intersect_analysis(fc,'intersect')
    findID = arcpy.FindIdentical_management(intersect,"explFindID","Shape")
    arcpy.MakeFeatureLayer_management(intersect,"intlyr")
    arcpy.AddJoin_management("intlyr",arcpy.Describe("intlyr").OIDfieldName,findID,"IN_FID","KEEP_ALL")
    segIDs = {}
    featseqName = "explFindID.FEAT_SEQ"
    idNewName = "intersect."+idName

    for row in arcpy.SearchCursor("intlyr"):
        idVal = row.getValue(idNewName)
        featseqVal = row.getValue(featseqName)
        segIDs[featseqVal] = []
    for row in arcpy.SearchCursor("intlyr"):
        idVal = row.getValue(idNewName)
        featseqVal = row.getValue(featseqName)
        segIDs[featseqVal].append(idVal)

    segIDs2 = {}
    for row in arcpy.SearchCursor("intlyr"):
        idVal = row.getValue(idNewName)
        segIDs2[idVal] = []

    for x,y in segIDs.iteritems():
        for segID in y:
            segIDs2[segID].extend([k for k in y if k != segID])

    for x,y in segIDs2.iteritems():
        segIDs2[x] = list(set(y))

    arcpy.RemoveJoin_management("intlyr",arcpy.Describe(findID).name)

    if 'overlaps' not in [k.name for k in arcpy.ListFields(fc)]:
        arcpy.AddField_management(fc,'overlaps',"TEXT")
    if 'ovlpCount' not in [k.name for k in arcpy.ListFields(fc)]:
        arcpy.AddField_management(fc,'ovlpCount',"SHORT")

    urows = arcpy.UpdateCursor(fc)
    for urow in urows:
        idVal = urow.getValue(idName)
        if segIDs2.get(idVal):
            urow.overlaps = str(segIDs2[idVal]).strip('[]')
            urow.ovlpCount = len(segIDs2[idVal])
        urows.updateRow(urow)

def explodeOverlaps(fc,idName):

    countOverlaps(fc,idName)

    arcpy.AddField_management(fc,'expl',"SHORT")

    urows = arcpy.UpdateCursor(fc,'"overlaps" IS NULL')
    for urow in urows:
        urow.expl = 1
        urows.updateRow(urow)

    i=1
    lyr = arcpy.MakeFeatureLayer_management(fc)
    while int(arcpy.GetCount_management(arcpy.SelectLayerByAttribute_management(lyr,"NEW_SELECTION",'"expl" IS NULL')).getOutput(0)) > 0:
        ovList=[]
        urows = arcpy.UpdateCursor(fc,'"expl" IS NULL','','','ovlpCount D')
        for urow in urows:
            ovVal = urow.overlaps
            idVal = urow.getValue(idName)
            intList = ovVal.replace(' ','').split(',')
            for x in intList:
                intList[intList.index(x)] = int(x)
            if idVal not in ovList:
                urow.expl = i
            urows.updateRow(urow)
            ovList.extend(intList)
        i+=1

2
Per connetterlo alla mia soluzione: la tua countOverlapscorrisponde alle due righe nbrhoods <- sapply(vertices, neighbors); degrees <- sapply(nbrhoods, length)nel mio codice: degreesè il conteggio delle sovrapposizioni. Ovviamente il tuo codice è più lungo perché riflette la maggior parte dell'analisi GIS che è data per scontata nella mia soluzione: vale a dire che prima identifichi quali poligoni si sovrappongono e che alla fine usi la soluzione per produrre set di dati poligonali. Sarebbe una buona idea incapsulare i calcoli teorico-grafici, quindi se mai trovassi un algoritmo di colorazione migliore, sarebbe facile collegarlo.
whuber

1

È passato un po 'di tempo, ma ho usato questo codice per la mia applicazione e ha funzionato benissimo - grazie. Ho riscritto parte di esso per aggiornarlo, applicarlo alle linee (con una tolleranza) e accelerarlo in modo significativo (di seguito - lo sto eseguendo su 50 milioni di buffer intersecanti e ci vogliono solo un paio d'ore).

def ExplodeOverlappingLines(fc, tolerance, keep=True):
        print('Buffering lines...')
        idName = "ORIG_FID"
        fcbuf = arcpy.Buffer_analysis(fc, fc+'buf', tolerance, line_side='FULL', line_end_type='FLAT')
        print('Intersecting buffers...')
        intersect = arcpy.Intersect_analysis(fcbuf,'intersect')

        print('Creating dictionary of overlaps...')
        #Find identical shapes and put them together in a dictionary, unique shapes will only have one value
        segIDs = defaultdict(list)
        with arcpy.da.SearchCursor(intersect, ['Shape@WKT', idName]) as cursor:
            x=0
            for row in cursor:
                if x%100000 == 0:
                    print('Processed {} records for duplicate shapes...'.format(x))
                segIDs[row[0]].append(row[1])
                x+=1

        #Build dictionary of all buffers overlapping each buffer
        segIDs2 = defaultdict(list)
        for v in segIDs.values():
            for segID in v:
                segIDs2[segID].extend([k for k in v if k != segID and k not in segIDs2[segID]])

        print('Assigning lines to non-overlapping sets...')
        grpdict = {}
        # Mark all non-overlapping one to group 1
        for row in arcpy.da.SearchCursor(fcbuf, [idName]):
            if row[0] in segIDs2:
                grpdict[row[0]] = None
            else:
                grpdict[row[0]] = 1

        segIDs2sort = sorted(segIDs2.items(), key=lambda x: (len(x[1]), x[0])) #Sort dictionary by number of overlapping features then by keys
        i = 2
        while None in grpdict.values(): #As long as there remain features not assigned to a group
            print(i)
            ovset = set()  # list of all features overlapping features within current group
            s_update = ovset.update
            for rec in segIDs2sort:
                if grpdict[rec[0]] is None: #If feature has not been assigned a group
                    if rec[0] not in ovset: #If does not overlap with a feature in that group
                        grpdict[rec[0]] = i  # Assign current group to feature
                        s_update(rec[1])  # Add all overlapping feature to ovList
            i += 1 #Iterate to the next group

        print('Writing out results to "expl" field in...'.format(fc))
        arcpy.AddField_management(fc, 'expl', "SHORT")
        with arcpy.da.UpdateCursor(fc,
                                   [arcpy.Describe(fc).OIDfieldName, 'expl']) as cursor:
            for row in cursor:
                if row[0] in grpdict:
                    row[1] = grpdict[row[0]]
                    cursor.updateRow(row)

        if keep == False:
            print('Deleting intermediate outputs...')
            for fc in ['intersect', "explFindID"]:
                arcpy.Delete_management(fc)

-3

In questi casi generalmente utilizzo il seguente metodo:

  • Passa la classe Feature attraverso un UNION; (Rompe i poligoni in tutte le sue intersezioni)
  • Aggiungi i campi X, Y e Area e calcolali;
  • Sciogli il risultato con i campi X, Y, Area.

Credo che il risultato sarà quello che volevi e puoi anche contare il numero di sovrapposizioni. Non so se in termini di prestazioni sarà meglio per te o no.


2
questo metodo non ti porta al prodotto desiderato, che è una serie minima di selezioni o classi di caratteristiche uniche dell'originale che non si sovrappongono. i prodotti verranno inseriti nelle statistiche zonali e pertanto è fondamentale mantenere la geometria originale di ciascuna caratteristica.
ndimhypervol,

Hai ragione, scusa. Non ho capito bene la domanda. In tal caso, e in base alla dimensione del raster, normalmente convertivo il raster in una classe di caratteristiche del punto temporaneo (ogni cella un punto) ed eseguivo un unione spaziale tra esso e il livello poligonale. Forse è un approccio molto semplicistico e ostile alle prestazioni ma funziona e i poligoni sovrapposti non ti daranno alcun problema.
Alexandre Neto,

Se capisco correttamente cosa intendi con questa unione spaziale, la tua seconda soluzione non funzionerà ancora, Alexandre, perché esiste una relazione molti-a-molti tra i punti e i poligoni. Indipendentemente da ciò, per qualsiasi considerevole raster questo approccio basato sui vettori sarà estremamente inefficiente e per i grandi raster sarà impossibile da attuare.
whuber

@whuber Hai ragione sull'essere un processo molto lento (mi ha portato circa mezz'ora con un raster 4284 x 3009 e 2401 poligoni, in un dualcore 2.8Ghz, 3Gb RAM con vista). Ma funziona, come l'ho già provato. Nell'unione spaziale devi usare una relazione uno a uno e aggregare i valori raster (come media, somma, ecc ...). Il risultato sarà un livello poligonale vettoriale simile all'originale ma con una nuova colonna con i valori raster aggregati che intersecano ciascun poligono. Non essendo una soluzione ottimale questo potrebbe essere utile per qualcuno con meno abilità di programmazione (come me :-)).
Alexandre Neto,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.