Trovare un angolo tra le feature che si intersecano in due classi di feature usando ArcGIS Desktop e Python? [chiuso]


19

Ho due featureclasses di linea intersecanti. Voglio trovare l'angolo in ogni punto di intersezione usando ArcGIS 10 e Python.

Qualcuno può aiutare?


Ho replicato il metodo di Whuber (grazie) in uno script Python usando Arcpy ma sto riscontrando problemi con il calcolo dell'angolo. Una volta completato in Esri ArcMap (calcolatrice sul campo), viene calcolato correttamente. Se calcolato all'interno di uno script Python (utilizzando nuovamente la calcolatrice di campo), viene calcolato in modo errato (come un decimale). Non è semplicemente una conversione da radianti a gradi problema. La funzione arcpica per calcolare il campo sotto forma di angolo è inferiore. Vengono proiettate le classi di funzioni (British National Grid). C'è un ulteriore passo che devo fare per calcolare gli angoli in pitone da un documento della mappa
Andy,

Risposte:


13

C'è un flusso di lavoro relativamente semplice. Supera i potenziali problemi che due caratteristiche possono intersecare in più di un punto. Non richiede script (ma può essere prontamente trasformato in uno script). Può essere fatto principalmente dal menu ArcGIS.

L'idea è di sfruttare uno strato di punti di intersezione, un punto per ogni coppia distinta di polilinee intersecanti. È necessario ottenere un piccolo pezzo di ciascuna polilinea che si interseca in questi punti di intersezione. Usa gli orientamenti di questi pezzi per calcolare i loro angoli di intersezione.

Ecco i passaggi:

  1. Assicurati che ciascuna delle caratteristiche della polilinea abbia un identificatore univoco nella sua tabella degli attributi. Questo verrà utilizzato in seguito per unire alcuni attributi geometrici delle polilinee alla tabella dei punti di intersezione.

  2. Geoprocessing | Intersect ottiene i punti (assicurarsi di specificare i punti desiderati per l'output).

  3. Geoprocessing | Buffer consente di bufferizzare i punti di una piccola quantità. Renderlo davvero minuscolo in modo che la parte di ogni linea all'interno di un buffer non si pieghi.

  4. Geoprocessing | Clip (applicato due volte) limita i livelli di polilinea originali solo ai buffer. Poiché questo produce nuovi set di dati per il suo output, le operazioni successive non modificheranno i dati originali (il che è positivo).

    figura

    Ecco uno schema di ciò che accade: due strati di polilinea, mostrati in azzurro e rosso chiaro, hanno prodotto punti di intersezione scuri. Attorno a questi punti, i piccoli buffer sono mostrati in giallo. I segmenti blu e rosso più scuri mostrano i risultati del clipping delle caratteristiche originali su questi buffer. Il resto dell'algoritmo funziona con i segmenti scuri. (Non puoi vederlo qui, ma una minuscola polilinea rossa interseca due delle linee blu in un punto comune, producendo quello che sembra essere un buffer attorno a due polilinee blu. Sono in realtà due buffer attorno a due punti sovrapposti di intersezione rosso-blu. , questo diagramma mostra cinque buffer in tutto.)

  5. Utilizzare lo strumento AddField per creare quattro nuovi campi in ciascuno di questi livelli ritagliati: [X0], [Y0], [X1] e [Y1]. Conterranno le coordinate dei punti, quindi falli raddoppiare e dai loro molta precisione.

  6. Calcola geometria (invocata facendo clic con il pulsante destro del mouse su ciascuna nuova intestazione del campo) consente di calcolare le coordinate x e y dei punti iniziale e finale di ciascuna polilinea ritagliata: inserirle in [X0], [Y0], [X1] e [Y1], rispettivamente. Questo viene fatto per ogni livello ritagliato, quindi sono necessari 8 calcoli.

  7. Utilizzare lo strumento AddField per creare un nuovo campo [Angolo] nel livello del punto di intersezione.

  8. Unire le tabelle ritagliate alla tabella dei punti di intersezione in base agli identificatori di oggetti comuni. (I join vengono eseguiti facendo clic con il pulsante destro del mouse sul nome del layer e selezionando "Join e relativi".)

    A questo punto la tabella di intersezione dei punti ha 9 nuovi campi: due sono chiamati [X0], ecc. E uno è chiamato [Angolo]. Alias i campi [X0], [Y0], [X1] e [Y1] che appartengono a una delle tabelle unite. Chiamiamo questi (diciamo) "X0a", "Y0a", "X1a" e "Y1a".

  9. Utilizzare il calcolatore di campo per calcolare l'angolo nella tabella delle intersezioni. Ecco un blocco di codice Python per il calcolo:

    dx = !x1!-!x0!
    dy = !y1!-!y0!
    dxa = !x1a!-!x0a!
    dya = !y1a!-!y0a!
    r = math.sqrt(math.pow(dx,2) + math.pow(dy,2))
    ra = math.sqrt(math.pow(dxa,2) + math.pow(dya,2))
    c = math.asin(abs((dx*dya - dy*dxa))/(r*ra)) / math.pi * 180

    L'espressione di calcolo del campo è, ovviamente, semplicemente

    c

Nonostante la lunghezza di questo blocco di codice, la matematica è semplice: (dx, dy) è un vettore di direzione per la prima polilinea e (dxa, dya) è un vettore di direzione per la seconda. Le loro lunghezze, r e ra (calcolate tramite il Teorema di Pitagora), vengono utilizzate per normalizzarle in vettori di unità. (Non dovrebbero esserci problemi con lunghezze pari a zero, perché il clipping dovrebbe produrre caratteristiche di lunghezza positiva.) Le dimensioni del loro prodotto a cuneo dx dya - dydxa (dopo la divisione per r e ra) è il seno dell'angolo. (L'uso del prodotto a cuneo piuttosto che del normale prodotto interno dovrebbe fornire una migliore precisione numerica per angoli vicini allo zero.) Infine, l'angolo viene convertito da radianti a gradi. Il risultato sarà compreso tra 0 e 90. Nota l'evitamento della trigonometria fino alla fine: questo approccio tende a produrre risultati affidabili e facilmente calcolabili.

Alcuni punti possono apparire più volte nel livello di intersezione. In tal caso, otterranno più angoli associati a loro.

Il buffering e il clipping in questa soluzione sono relativamente costosi (passaggi 3 e 4): non si desidera farlo in questo modo quando sono coinvolti milioni di punti di intersezione. L'ho raccomandato perché (a) semplifica il processo di ricerca di due punti successivi lungo ciascuna polilinea all'interno del suo punto di intersezione e (b) il buffering è così semplice da eseguire in qualsiasi GIS - non sono necessarie licenze aggiuntive sopra il livello base di ArcMap - e di solito produce risultati corretti. (Altre operazioni di "geoprocessing" potrebbero non essere così affidabili.)


1
Questo potrebbe funzionare, ma non puoi fare riferimento ai nomi dei campi nel blocco del codice, quindi dovresti avvolgere il codice in una funzione e chiamarlo usando i nomi dei campi come argomenti.
mvexel,

@mv Grazie per quell'osservazione. Si potrebbe anche usare VBS invece di Python - VBS analizzerà i nomi dei campi nel blocco di codice.
whuber

1
In realtà ha funzionato come un incantesimo quando si utilizza un wrapper di funzioni. Ho scoperto che in ArcGIS 10 e quando si utilizza Python, non è necessario aliasare le variabili, è possibile anteporre il nome della tabella di join nel riferimento del campo, ad esempio !table1.x0!.
mvexel,

6

Credo che tu debba creare uno script Python.

Puoi farlo usando strumenti di geoprocessing e arcpy.

Ecco i principali strumenti e idee che possono esserti utili:

  1. Crea intersezioni tra le tue due polilinee (chiamiamole PLINE_FC1, PLINE_FC2) featureclass (di conseguenza hai bisogno di funzioni puntiforme - POINT_FC) usando lo strumento Intersect . Avrai ID da PLINE_FC1, PLINE_FC2 nei punti POINT_FC.
  2. Dividi PLINE_FC1 per POINT_FC usando lo strumento Dividi linea nel punto. Di conseguenza avrai polilinee divise - il principale vantaggio di esso è che puoi semplicemente prendere il primo / ultimo vertice di tale linea confrontandolo con il vertice successivo / precedente (differenza di coordinate) e calcolare l'angolo. Quindi, avrai un angolo della linea nel punto di intersezione. C'è un problema qui: devi eseguire questo strumento manualmente più volte per capire come viene scritto l'output. Voglio dire se prende la polilinea, la divide, scrive due polilinee di risultato sull'output e quindi procede alla polilinea successiva e ripete. O potrebbe essere questa parte (risultato della divisione) che viene scritta in diverse classi di funzioni di memoria, quindi aggiunta all'output. Questo è il problema principale: capire come viene scritto l'output per poter filtrare solo la prima parte di ciascuna polilinea dopo la divisione. Un'altra possibile soluzione è quella di passare in rassegna tutte le polilinee divise con i risultatiCerca il cursore e accetta solo i primi rilevati (per ID delle polilinee di origine PLINE_FC1).
  3. Per ottenere l'angolo dovrai accedere ai vertici della polilinea dei risultati usando arcpy . Scrivi gli angoli risultanti sui punti (POINT_FC).
  4. Ripeti i passaggi 2-3 per PLINE_FC2.
  5. Sottrai gli attributi dell'angolo (in POINT_FC) e ottieni il risultato.

Può essere molto difficile codificare il passaggio 2 (anche alcuni strumenti richiedono la licenza ArcInfo). Quindi puoi anche provare ad analizzare i vertici di ogni polilinea (raggruppandoli per ID dopo l'intersezione).

Ecco il modo di farlo:

  1. Prendi il primo punto di intersezione POINT_FC. Ottieni le sue coordinate ( point_x, point_y)
  2. Con il suo ID prendere la rispettiva polilinea sorgente da PLINE_FC1.
  3. Prendi i primi ( vert0_x, vert0_y) e secondi ( vert1_x, vert1_y) vertici di esso.
  4. Per il primo vertice calcolare la tangente della linea tra questo vertice e il punto di intersezione: tan0 = (point_y - vert0_y) / (point_x - vert0_x)
  5. Calcola la stessa cosa per il secondo vertice: tan1 = (vert1_y - point_y) / (vert1_x - point_x)
  6. Se tan1è uguale a tan2, hai trovato due vertici della tua linea che hanno un punto di intersezione tra loro e puoi calcolare l'angolo di intersezione per questa linea. Altrimenti devi passare alla prossima coppia di vertici (seconda, terza) e così via.
  7. Ripetere i passaggi 1-6 per ogni punto di intersezione.
  8. Ripetere i passaggi 1-7 per la seconda polilinea featureclass PLINE_FC2.
  9. Sottrai gli attributi dell'angolo da PLINE_FC1 e PLINE_FC2 e ottieni il risultato.

1

Di recente stavo cercando di farlo da solo.

La mia funzione di indizio si basa su punti circolari attorno alle intersezioni di linee e su punti situati ad una distanza di un metro dalle intersezioni. L'output è una classe caratteristica polilinea che ha gli attributi del numero di angoli su intersezioni e angolo.

Si noti che le linee devono essere planarizzate al fine di trovare intersezioni e il riferimento spaziale deve essere impostato con la visualizzazione della lunghezza della linea corretta (la mia è WGS_1984_Web_Mercator_Auxitime_Sphere).

In esecuzione nella console ArcMap ma può essere facilmente convertito in uno script nella casella degli strumenti. Questo script utilizza solo il livello di linea in TOC, niente di più.

import arcpy
import time

mxd = arcpy.mapping.MapDocument("CURRENT")
df = mxd.activeDataFrame


line = ' * YOUR POLYLINE FEATURE LAYER * ' # paste the name of line layer here    

def crossing_cors(line_layer):
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    arcpy.env.overwriteOutput = True
    sr = arcpy.Describe(line_layer).spatialReference

    dict_cors = {}
    dang_list = []

    with arcpy.da.UpdateCursor(line_layer, ['SHAPE@', 'OID@']) as uc:
        for row in uc:
            if row[0] is None:
                uc.deleteRow()

    with arcpy.da.UpdateCursor(line_layer, 'SHAPE@', spatial_reference = sr) as uc:
        for row in uc:
            line = row[0].getPart(0)
            for cor in line:
                coord = (cor.X, cor.Y)
                try:
                    dict_cors[coord] += 1
                except:
                    dict_cors[coord] = 1
    cors_only = [f for f in dict_cors if dict_cors[f]!=1]
    cors_layer = arcpy.CreateFeatureclass_management('in_memory', 'cross_pnt', "POINT", spatial_reference = sr)
    arcpy.AddField_management(cors_layer[0], 'ANGLE_NUM', 'LONG')
    with arcpy.da.InsertCursor(cors_layer[0], ['SHAPE@', 'ANGLE_NUM']) as ic:
        for x in cors_only:
            pnt_geom = arcpy.PointGeometry(arcpy.Point(x[0], x[1]), sr)
            ic.insertRow([pnt_geom, dict_cors[x]])
    return cors_layer

def one_meter_dist(line_layer):
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    arcpy.env.overwriteOutput = True
    sr = arcpy.Describe(line_layer).spatialReference

    dict_cors = {}
    dang_list = []
    cors_list = []
    with arcpy.da.UpdateCursor(line_layer, 'SHAPE@', spatial_reference = sr) as uc:
        for row in uc:
            line = row[0]
            length_line = line.length 
            if length_line > 2.0:
                pnt1 = line.positionAlongLine(1.0)
                pnt2 = line.positionAlongLine(length_line - 1.0)
                cors_list.append(pnt1)
                cors_list.append(pnt2)
            else:
                pnt = line.positionAlongLine(0.5, True)
    cors_layer = arcpy.CreateFeatureclass_management('in_memory', 'cross_one_meter', "POINT", spatial_reference = sr)
    ic = arcpy.da.InsertCursor(cors_layer[0], 'SHAPE@')
    for x in cors_list:
        ic.insertRow([x])
    return cors_layer

def circles(pnts):

    import math
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    arcpy.env.overwriteOutput = True
    sr = df.spatialReference

    circle_layer = arcpy.CreateFeatureclass_management('in_memory', 'circles', "POINT", spatial_reference = sr)


    ic = arcpy.da.InsertCursor(circle_layer[0], 'SHAPE@')
    with arcpy.da.SearchCursor(pnts, 'SHAPE@', spatial_reference = sr) as sc:
        for row in sc:
            fp = row[0].centroid
            list_circle =[]
            for i in xrange(0,36):
                an = math.radians(i * 10)
                np_x = fp.X + (1* math.sin(an))
                np_y = fp.Y + (1* math.cos(an))
                pnt_new = arcpy.PointGeometry(arcpy.Point(np_x,np_y), sr)

                ic.insertRow([pnt_new])
    del ic 
    return circle_layer

def angles(centers, pnts, rnd):
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = mxd.activeDataFrame
    sr = df.spatialReference

    line_lyr = arcpy.CreateFeatureclass_management('in_memory', 'line_angles', "POLYLINE", spatial_reference = sr)
    arcpy.AddField_management(line_lyr[0], 'ANGLE', "DOUBLE")
    arcpy.AddField_management(line_lyr[0], 'ANGLE_COUNT', "LONG")

    ic = arcpy.da.InsertCursor(line_lyr[0], ['SHAPE@', 'ANGLE', 'ANGLE_COUNT'])

    arcpy.AddField_management(pnts, 'ID_CENT', "LONG")
    arcpy.AddField_management(pnts, 'CENT_X', "DOUBLE")
    arcpy.AddField_management(pnts, 'CENT_Y', "DOUBLE")
    arcpy.Near_analysis(pnts, centers,'',"LOCATION") 

    with arcpy.da.UpdateCursor(line, ['SHAPE@', 'OID@']) as uc:
        for row in uc:
            if row[0] is None:
                uc.deleteRow()

    with arcpy.da.UpdateCursor(pnts, [u'ID_CENT', u'CENT_X', u'CENT_Y', u'NEAR_FID', u'NEAR_DIST', u'NEAR_X', u'NEAR_Y'], spatial_reference = sr) as uc:
        for row in uc:
            row[0] = row[3]
            row[1] = row[5]
            row[2] = row[6]
            uc.updateRow(row)
            if row[4] > 1.1:
                uc.deleteRow()


    arcpy.Near_analysis(pnts, rnd,'',"LOCATION")     

    list_id_cent = []
    with arcpy.da.UpdateCursor(pnts, [u'ID_CENT', u'CENT_X', u'CENT_Y', u'NEAR_FID', u'NEAR_DIST', u'NEAR_X', u'NEAR_Y', 'SHAPE@'], spatial_reference = sr) as uc:
        for row in uc:
            pnt_init = (row[-1].centroid.X, row[-1].centroid.Y)
            list_id_cent.append([(row[1], row[2]), row[3], pnt_init])

    list_id_cent.sort()
    values = set(map(lambda x:x[0], list_id_cent))
    newlist = [[y for y in list_id_cent if y[0]==x] for x in values]

    dict_cent_angle = {}

    for comp in newlist:
        dict_ang = {}
        for i, val in enumerate(comp):

            curr_pnt = comp[i][2]
            prev_p = comp[i-1][2]
            init_p = comp[i][0]


            angle_prev = math.degrees(math.atan2(prev_p[1]-init_p[1], prev_p[0]-init_p[0]))
            angle_next = math.degrees(math.atan2(curr_pnt[1]-init_p[1], curr_pnt[0]-init_p[0]))

            diff = abs(angle_next-angle_prev)%180


            vec1 = [(curr_pnt[0] - init_p[0]), (curr_pnt[1] - init_p[1])]
            vec2 = [(prev_p[0] - init_p[0]), (prev_p[1] - init_p[1])]

            ab = (vec1[0] * vec2[0]) + (vec1[1] * vec2[1]) 
            mod_ab = math.sqrt(math.pow(vec1[0], 2) + math.pow(vec1[1], 2)) * math.sqrt(math.pow(vec2[0], 2) + math.pow(vec2[1], 2))
            cos_a = round(ab/mod_ab, 2)

            diff = math.degrees(math.acos(cos_a))

            pnt1 = arcpy.Point(prev_p[0], prev_p[1])
            pnt2 = arcpy.Point(init_p[0], init_p[1])
            pnt3 = arcpy.Point(curr_pnt[0], curr_pnt[1])


            line_ar = arcpy.Array([pnt1, pnt2, pnt3])
            line_geom = arcpy.Polyline(line_ar, sr)

            ic.insertRow([line_geom , diff, len(comp)])
    del ic

    lyr_lst = [f.name for f in arcpy.mapping.ListLayers(mxd)]
    if 'line_angles' not in lyr_lst:
        arcpy.mapping.AddLayer(df, arcpy.mapping.Layer(line_lyr[0]))


centers = crossing_cors(line)

pnts = one_meter_dist(line)

rnd = circles(centers)

angle_dict = angles(centers, pnts, rnd)
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.