Вибух перекриття нових полігонів, що не перетинаються?


10

З огляду на декілька багатокутників, які перетинаються декількома способами, я хотів би експортувати з цих функцій всі багатокутники, які не перетинаються з іншими, ітеративно.

У виробі буде ряд особливостей, що не перекриваються, які, коли їх підсумовувати, складають оригінал.

Потім продукти можуть використовуватися як вхідні дані до зональної статистики, і це буде набагато швидше, ніж повторення зональної статистики для кожного полігону.

Я намагався кодувати це в ArcPy без успіху.

Чи існує код для цього вже?


Ви маєте на увазі, що хочете "згладити" дані в топологічно правильний набір?
nagytech

@Geoist ZonalStats вимагає багатокутників, які не перетинаються. Коли у вас є збірка, що перекривається, очевидним, але неефективним рішенням є перекидання полісів та обчислення зональної статистики по черзі. Було б ефективніше вибрати підмножину полісів, що не перекриваються, застосувати до них зональні статистики та повторити ітерацію. Питання задає питання, як зробити такі виділення ефективно.
whuber

whuber - Я думаю, що @Geoist пропонує створити набір полігонів, що не перекриваються, з перетинів вхідних полігонів. Подивіться на це зображення - (не можете розміщувати зображення в коментарях?). Вхід знаходиться зліва. Весь регіон охоплений трьома багатокутниками, кожен з яких перетинає обидва інші. Єдиними підмножинами, що не перекриваються, є одинаки, і вони не задовольняють вимоги Готанукі про те, що об'єднання заповнює простір. Я думаю, що Geoist пропонує створити набір непересічних регіонів праворуч, що дійсно для zonalstats
Llaves

Я думаю, що існує деяка плутанина щодо того, яким повинен бути кінцевий продукт. Чи можете ви навести приклад? Моя інтерпретація полягає в тому, що ви хотіли б, щоб результатом було виділення багатокутників, які не перетинаються - відкидаючи або розчиняючи решта багатокутників. Ви працюєте з одним чи багатьма класами функцій?
Аарон

1
Мені здається, що @gotanuki хоче створити мінімальну кількість класів функцій, які містять лише полігони, що не перекриваються, з класу характеристик багатокутника з полігонами, що перекриваються
PolyGeo

Відповіді:


14

Це проблема фарбування графіків .

Нагадаємо, що забарвлення графа - це призначення кольору вершинам графа таким чином, що жодна дві вершини, які ділять ребро, також не матимуть однакового кольору. Зокрема, (абстрактні) вершини графа є багатокутниками. Дві вершини з'єднуються з (непрямим) ребром, коли вони перетинаються (як багатокутники). Якщо ми візьмемо будь-яке рішення проблеми - яка є послідовністю (скажімо, k ) роз'єднаних колекцій багатокутників - і присвоїмо унікальний колір кожній колекції в послідовності, то ми отримаємо k -кольорування графіка . Бажано знайти невелику k .

Ця проблема досить складна і залишається невирішеною для довільних графіків. Розглянемо приблизне рішення, яке просто кодувати. Послідовний алгоритм повинен зробити. Алгоритм Уельша-Пауелла - це жадібне рішення, засноване на низхідному впорядкуванні вершин за ступенем. Перекладені мовою оригінальних багатокутників, спочатку сортуйте багатокутники у порядку зменшення кількості інших багатокутників, які вони перетинаються. Працюючи в порядку, надайте першому багатокутнику початковий колір. На кожному наступному етапі намагайтеся пофарбувати наступний багатокутник наявним кольором: тобто виберіть колір, який не євже використовується будь-яким із сусідів цього полігону. (Існує багато способів вибрати серед доступних кольорів; спробуйте або найменш використаний, або виберіть один випадковим чином.) Якщо наступний багатокутник не можна пофарбувати наявним кольором, створіть новий колір і пофарбуйте його цим.

Після того як ви домоглися забарвлення з невеликою кількістю кольорів, виконайте зональнестатування кольором за кольором: за конструкцією ви гарантуєте, що жоден два полігони заданого кольору не перекриваються.


Ось зразок коду в R. (Код Python не дуже сильно відрізняється.) Спочатку ми описуємо перекриття серед семи показаних багатокутників.

Карта семи полігонів

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)

Тобто багатокутники 1 і 2 перекриваються, і так само багатокутники 2 і 3, 3 і 4, ..., 1 і 7.

Сортуйте вершини за низхідним ступенем:

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))]

Алгоритм (неочищеного) послідовного забарвлення використовує найраніший доступний колір, який вже не використовується жодним полігоном, що перекривається:

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
}

Ініціалізуйте структури даних ( colorsі color.next) та застосуйте алгоритм:

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

Розділіть багатокутники на групи за кольором:

split(vertices, colors)

Вихід у цьому прикладі використовує чотири кольори:

$`1`
[1] 2 4

$`2`
[1] 3 6 7

$`3`
[1] 5

$`4`
[1] 1

Чотириколірне багатокутники

Він розділив багатокутники на чотири групи, що не перекриваються. У цьому випадку рішення не є оптимальним ({{3,6,5}, {2,4}, {1,7}} є триколірним для цього графіка). Загалом, рішення, яке воно отримує, не повинно бути занадто поганим.


Я не впевнений, чи це відповідає на питання, чи в чому питання, але все-таки це гарна відповідь.
nagytech

@Geoist Чи є якимось чином я можу зробити ілюстрацію більш зрозумілою або пояснити проблему краще?
whuber

6

Методологія, рекомендована #whuber, надихнула мене на новий напрямок, і ось моє рішення щодо дугоподібних процесів на дві функції. Перші, звані countOverlaps, роблять два поля, "overlaps" та "ovlpCount", щоб записувати для кожного полі, який polys перекривався з ним і скільки перекриттів відбулося. Друга функція, explodeOverlaps, створює третє поле "expl", яке дає унікальне ціле число для кожної групи полісів, що не перекриваються. Потім користувач може експортувати нові ПК на основі цього поля. Процес розбивається на дві функції, тому що я думаю, що інструмент countOverlaps може виявитися корисним сам по собі. Вибачте про неохайність коду (та необережну умову іменування), оскільки це досить попередньо, але це працює. Також переконайтеся, що "idName" поле являє собою поле з унікальними ідентифікаторами (тестується лише з цілими ідентифікаторами). Дякую вам, що надали мені рамки, необхідні для вирішення цієї проблеми!

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
Щоб підключити це до мого рішення: ваш countOverlapsвідповідає двом рядкам nbrhoods <- sapply(vertices, neighbors); degrees <- sapply(nbrhoods, length)у моєму коду: degreesце кількість перекриттів. Звичайно, ваш код довший, тому що він відображає більшу частину ГІС-аналізу, який прийнято як належне в моєму рішенні: а саме ви спочатку визначите, які багатокутники перекриваються, і що в кінці ви використовуєте рішення для виведення наборів даних полігонів. Було б хорошою ідеєю інкапсулювати графо-теоретичні розрахунки, тож якщо ви коли-небудь знайдете кращий алгоритм фарбування, його буде легко підключити.
whuber

1

Минуло деякий час, але я використав цей код для власної програми, і він спрацював чудово - дякую. Я переписав частину його для оновлення, застосував до рядків (з допуском) і значно пришвидшив її (нижче - я запускаю її на 50 мільйонів пересічних буферів, і це займає лише пару годин).

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

У цьому випадку я зазвичай використовую наступний метод:

  • Пройти клас Feature через UNION; (Він розбиває багатокутники у всіх його перетинах)
  • Додайте поля X, Y та Area та обчисліть їх;
  • Результат розведіть по полях X, Y, Area.

Я вважаю, що результат буде таким, який ви хотіли, і ви навіть можете порахувати кількість перекриттів. Не знаю, чи буде з точки зору продуктивності вам це краще чи ні.


2
цей метод не приведе вас до потрібного продукту, який є мінімальною серією вибору або унікальними класами характеристик оригіналу, які не перетинаються. продукти будуть вноситись в зональну статистику, а тому дотримання первісної геометрії кожної особливості є життєво важливим.
ndimhypervol

Ви маєте рацію, вибачте. Я добре не зрозумів питання. У такому випадку, і залежно від розміру растра, я зазвичай перетворюю растр у тимчасовий клас особливості точки (кожна комірка - точка) та здійснюю просторове з'єднання між ним та шаром багатокутника. Можливо, це дуже спрощений і непривітний підхід до виконання, але працює і перекриті багатокутники не дадуть вам жодних проблем.
Олександр Нето

Якщо я правильно зрозумів, що ви маєте на увазі під цим просторовим з'єднанням, ваше друге рішення все одно не буде працювати, Олександре, тому що між точками та багатокутниками існує співвідношення багато-багато. Незважаючи на те, що для будь-якого масштабного растру цей векторний підхід буде вкрай неефективним, а для великих растерів неможливо здійснити.
whuber

@whuber Ви маєте рацію в тому, що це дуже повільний процес (відмічайте мене близько півгодини з растром 4284 x 3009 та полігонами 2401, в двокорректній 2,8 ГГц, 3 Гб оперативної пам’яті з видом). Але це працює, як я це вже перевірив. У просторовому приєднанні вам потрібно використовувати відношення один до одного та агрегувати растрові значення (як середнє значення, сума тощо). В результаті вийде векторний багатокутний шар, подібний до оригіналу, але з новою колоною із сукупними значеннями растру, які перетинають кожен багатокутник. Не будучи оптимальним рішенням, це може бути корисним для тих, хто має менші навички програмування (як я :-)).
Олександр Нето
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.