Diviser les polygones en * n * nombre de groupes de nombres égaux avec ArcPy?


10

L'une de mes tâches professionnelles consiste à diviser les colis en groupes. Ces groupes seront utilisés par les agents pour parler aux propriétaires. L'objectif est de faciliter le travail de l'agent en regroupant les colis qui sont proches les uns des autres, ainsi que de diviser les colis en nombres égaux afin que le travail soit réparti uniformément. Le nombre d'agents peut varier d'un couple à 10+.

Actuellement, j'exécute cette tâche manuellement, mais j'aimerais automatiser le processus si possible. J'ai exploré divers outils ArcGIS, mais aucun ne semble répondre à mes besoins. J'ai essayé un script (en python) qui utilise near_analysiset sélectionne des polygones, mais il est plutôt aléatoire et prend une éternité pour obtenir un résultat semi-correct qui me prend plus de temps à corriger que si je faisais tout manuellement depuis le début.

Existe-t-il une méthode fiable pour automatiser cette tâche?

Exemple de résultats (si tout va bien sans la division que nous voyons en jaune):

Colis divisés


Avez-vous étudié l'analyse de la répartition géographique? help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#/...
liber

Avez-vous essayé l'analyse de regroupement (statistiques spatiales)?
FelixIP

J'ai également publié un pseudo-code de la procédure réelle que j'utilise, voir si cela pourrait aider gis.stackexchange.com/questions/123289/…
FelixIP

@crmackey J'apprécie le lien vers ma réponse, mais je ne sais pas comment vous pouvez modifier le code lié (fractionnement des polygones) pour répondre à ce problème (regroupement des polygones).
phloème

Réponses:


4

Ensemble d'origine:

entrez la description de l'image ici

Créez une pseudo-copie (CNTRL-faites glisser dans la table des matières) et faites une jointure spatiale un à plusieurs avec le clone. Dans ce cas, j'ai utilisé une distance de 500 m. Tableau de sortie:

entrez la description de l'image ici

  1. Supprimez les enregistrements de cette table où PAR_ID = PAR_ID_1 - facile.

  2. Parcourez la table et supprimez les enregistrements où (PAR_ID, PAR_ID_1) = (PAR_ID_1, PAR_ID) de tout enregistrement au-dessus. Pas si facile, utilisez acrpy.

Calculer les centroïdes du bassin versant (UniqID = PAR_ID). Ce sont des nœuds ou un réseau. Connectez-les par des lignes à l'aide de la table de jointure spatiale. C'est un sujet distinct qui est sûrement couvert quelque part sur ce forum.

entrez la description de l'image ici

Le script ci-dessous suppose que la table des nœuds ressemble à ceci: entrez la description de l'image ici

où MUID provenait de colis, P2013 est le champ à résumer. Dans ce cas = 1 pour compter uniquement. [rcvnode] - sortie de script pour stocker l'ID de groupe NODEREC égal au premier nœud du groupe / cluster défini.

Lie la structure de la table avec les champs importants mis en évidence

entrez la description de l'image ici

Le temps stocke le poids de liaison / bord, c'est-à-dire le coût de déplacement d'un nœud à l'autre. Égal à 1 dans ce cas pour que le coût du voyage vers tous les voisins soit le même. [fi] et [ti] sont le nombre séquentiel de nœuds connectés. Pour remplir ce tableau, recherchez dans ce forum comment affecter des nœuds à partir de et vers des liens.

Script personnalisé pour mon propre atelier mxd. Doit être modifié, codé en dur avec votre nom des champs et des sources:

import arcpy, traceback, os, sys,time
import itertools as itt
scriptsPath=os.path.dirname(os.path.realpath(__file__))
os.chdir(scriptsPath)
import COMMON
sys.path.append(r'C:\Users\felix_pertziger\AppData\Roaming\Python\Python27\site-packages')
import networkx as nx
RATIO = int(arcpy.GetParameterAsText(0))

try:
    def showPyMessage():
        arcpy.AddMessage(str(time.ctime()) + " - " + message)
mxd = arcpy.mapping.MapDocument("CURRENT")
theT=COMMON.getTable(mxd)

TROUVER LA COUCHE DES NOEUDS

theNodesLayer = COMMON.getInfoFromTable(theT,1)
theNodesLayer = COMMON.isLayerExist(mxd,theNodesLayer)

OBTENIR UNE COUCHE DE LIENS

    theLinksLayer = COMMON.getInfoFromTable(theT,9)
    theLinksLayer = COMMON.isLayerExist(mxd,theLinksLayer)
    arcpy.SelectLayerByAttribute_management(theLinksLayer, "CLEAR_SELECTION")        
    linksFromI=COMMON.getInfoFromTable(theT,14)
    linksToI=COMMON.getInfoFromTable(theT,13)
    G=nx.Graph()
    arcpy.AddMessage("Adding links to graph")
    with arcpy.da.SearchCursor(theLinksLayer, (linksFromI,linksToI,"Times")) as cursor:
            for row in cursor:
                (f,t,c)=row
                G.add_edge(f,t,weight=c)
            del row, cursor
    pops=[]
    pops=arcpy.da.TableToNumPyArray(theNodesLayer,("P2013"))
    length0=nx.all_pairs_shortest_path_length(G)
    nNodes=len(pops)
    aBmNodes=[]
    aBig=xrange(nNodes)
    host=[-1]*nNodes
    while True:
            RATIO+=-1
            if RATIO==0:
                    break
            aBig = filter(lambda x: x not in aBmNodes, aBig)
            p=itt.combinations(aBig, 2)
            pMin=1000000
            small=[]
            for a in p:
                    S0,S1=0,0
                    for i in aBig:
                            p=pops[i][0]
                            p0=length0[a[0]][i]
                            p1=length0[a[1]][i]
                            if p0<p1:
                                    S0+=p
                            else:
                                    S1+=p
                    if S0!=0 and S1!=0:
                            sMin=min(S0,S1)                        
                            sMax=max(S0,S1)
                            df=abs(float(sMax)/sMin-RATIO)
                            if df<pMin:
                                    pMin=df
                                    aBest=a[:]
                                    arcpy.AddMessage('%s %i %i' %(aBest,sMax,sMin))
                            if df<0.005:
                                    break
            lSmall,lBig,S0,S1=[],[],0,0
            arcpy.AddMessage ('Ratio %i' %RATIO)
            for i in aBig:
                    p0=length0[aBest[0]][i]
                    p1=length0[aBest[1]][i]
                    if p0<p1:
                            lSmall.append(i)
                            S0+=p0
                    else:
                            lBig.append(i)
                            S1+=p1
            if S0<S1:
                    aBmNodes=lSmall[:]
                    for i in aBmNodes:
                            host[i]=aBest[0]
                    for i in lBig:
                            host[i]=aBest[1]
            else:
                    aBmNodes=lBig[:]
                    for i in aBmNodes:
                            host[i]=aBest[1]
                    for i in lSmall:
                            host[i]=aBest[0]

    with arcpy.da.UpdateCursor(theNodesLayer, "rcvnode") as cursor:
            i=0
            for row in cursor:
                    row[0]=host[i]
                    cursor.updateRow(row)
                    i+=1

            del row, cursor
except:
    message = "\n*** PYTHON ERRORS *** "; showPyMessage()
    message = "Python Traceback Info: " + traceback.format_tb(sys.exc_info()[2])[0]; showPyMessage()
    message = "Python Error Info: " +  str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"; showPyMessage()

Exemple de sortie pour 6 groupes:

entrez la description de l'image ici

Vous aurez besoin du package de site NETWORKX http://networkx.github.io/documentation/development/install.html

Le script prend le nombre requis de clusters comme paramètre (6 dans l'exemple ci-dessus). Il utilise des nœuds et des tables de liens pour créer un graphique avec un poids / une distance égaux des bords de déplacement (Times = 1). Il considère la combinaison de tous les nœuds par 2 et calcule le total de [P2013] dans deux groupes de voisins. Lorsque le ratio requis est atteint, par exemple (6-1) / 1 à la première itération, continue avec un objectif de ratio réduit, c'est-à-dire 4, etc. jusqu'à 1. Les points de départ sont d'une grande importance, alors assurez-vous que vos nœuds «finaux» sont assis en haut de votre table de nœuds (tri?) Voir les 3 premiers groupes dans l'exemple de sortie. Cela permet d'éviter les «coupures de branches» à chaque nouvelle itération.

Personnalisation du script pour travailler à partir de mxd:

  1. vous n'avez pas besoin d'importer COMMUN. C'est mon propre truc, qui lit ma propre table d'environnement, où theNodesLayer, theLinksLayer, linksFromI, linksToI sont spécifiés. Remplacez les lignes pertinentes par votre propre nom des couches de nœuds et de liens.
  2. Notez que le champ P2013 peut stocker n'importe quoi, par exemple le nombre de locataires ou la superficie de la parcelle. Si c'est le cas, vous pourriez regrouper des polygones pour contenir un nombre de personnes à peu près égal, etc.

En réalité, les couches de nœuds et de liens ne sont que des éléments visuels. La table nettoyée de la jointure spatiale peut facilement remplacer la table de liens, car les nœuds de et vers sont déjà affectés. La table des polygones peut facilement servir de table de nœuds, il suffit d'ajouter le champ ReceivingNode et de transférer les numéros séquentiels de celui-ci vers les «liens» [FromI] et [ToI].
FelixIP

Cela semble bon. Merci beaucoup pour la réponse. Pouvez-vous expliquer davantage le pourquoi et pas seulement le comment? Les commentaires sur votre code seraient énormes.
Emil Brundage

Veuillez suivre l'hyperlien dans mon commentaire précédent à votre question. J'ai essayé d'expliquer l'approche, si c'est ce que signifie «pourquoi». Je retire mon commentaire concernant l'importance de démarrer le nœud, car après avoir publié la réponse à votre Q, j'ai changé l'ordre des enregistrements au hasard en essayant de tuer le script. Rien ne s'est passé, cela a quand même produit des résultats raisonnables.
FelixIP

Pour nettoyer la table de jointure spatiale, il suffit de supprimer PAR_ID = PAR_ID_1, car bord / lien [0,2] dans le graphique non orienté de NETWORKX bord égal [2,0]. Je peux poster un script mis à jour, je ne sais pas si cela affectera ma réputation
FelixIP

@EmilBrundage jetez un oeil, cela pourrait aider à savoir pourquoi question gis.stackexchange.com/questions/165057/…
FelixIP

2

Vous devez utiliser l'outil "Analyse de groupe" pour atteindre votre objectif. Cet outil est un excellent outil de la boîte à outils "statistiques spatiales" comme l'a souligné @phloem. Cependant, vous devez affiner l'outil pour l'adapter à vos données et à votre problème. J'ai créé un scénario similaire à celui que vous avez publié et j'ai obtenu une réponse proche de votre objectif.

Astuce: En utilisant ArcGIS 10.2, lorsque j'ai exécuté l'outil, il se plaignait du package python manquant, "six". Assurez-vous donc de l'avoir installé en premier Lien

Pas:

  1. Ajoutez un champ à votre classe de polygones pour contenir une valeur unique
  2. Ajoutez un autre champ de type Short avec le nom par exemple "SameGroup"
  3. vous calculatrice de champ pour attribuer 1 à ce champ pour toutes les lignes. changez simplement une ligne en 2. Champ ajouté

  4. Définissez les paramètres de l'outil "Analyse de groupe" comme ceci: Analyse de groupe

essayez de modifier le paramètre "Nombre de voisins" selon vos besoins.

Instantanés des résultats:

Exemples de polygones d'entrée

Résultat de l'analyse de groupe


2
J'ai étudié l'Analyse de Groupe auparavant. Cela concerne l'espace, mais pas autant que je sache. Toute mon expérience de la lecture de la documentation, de l'examen de votre exemple et de l'exécution de mes propres tests ne permet pas de regrouper par nombre égal de polygones.
Emil Brundage

Pourquoi devez-vous faire l'égalité (hors cours pour les agents)? Mais si nous ajoutons cette contrainte, alors pourquoi regrouper (grouper) les données en fonction de la relation spatiale!?
Farid Cheraghi

1
Parce que le patron le dit. Réduisez également le temps de trajet.
Emil Brundage

1

En gros, vous voulez une méthode de clustering de taille égale, vous pouvez donc rechercher avec ces mots clés sur le Web. Pour moi, il y a une bonne réponse sur stats.SE avec une implémentation Python dans l'une des réponses. Si vous connaissez arcpy, vous devriez pouvoir l'utiliser avec vos données.

Vous devez d'abord calculer les X et Y des centroïdes de vos polygones, puis vous pouvez entrer ces coordonnées dans le script et mettre à jour leur table d'attributs à l'aide d'un curseur .da.


Le lien que vous fournissez semble être sur la bonne voie, mais il est essentiellement dans une langue que je ne comprends pas. Pour le script, je ne sais pas quelles sont les entrées et je ne peux déchiffrer aucun codage pour comprendre exactement ce qui se passe. Il y a très peu d'explications.
Emil Brundage

0

Salut, j'ai eu un problème similaire à celui-ci auparavant, donc je lui en ai donné quelques-uns, je n'en ai jamais commencé, mais juste du côté de la pensée, je pensais

FORME D'ENTRÉE

Forme d'entrée

je pensais que vous pourriez créer un filet de pêche sur la forme d'entrée

résille résille avec une intersection de votre forme d'entrée serait alors

entrée dans la zone

Vous pouvez ensuite calculer l'aire de ces parcelles à l'intérieur du polygone nouvellement traité

Au début de votre script, le polygone d'entrée de zone / nième quantité de tailles égales voulues

Vous auriez alors besoin d'un moyen de relier les colis afin qu'ils sachent ceux qui sont bordés.

Ensuite, vous pouvez parcourir un curseur de ligne pour résumer les colis

Les règles étant

* Il partage une frontière avec le dernier été * Il n'a pas été additionné * Une fois qu'il dépasse la valeur calculée comme la surface égale, il reculerait et ce serait un groupe * Le processus recommencerait * Le dernier groupe pourrait être la somme des colis restants

je pense que l'établissement de la relation entre les colis pourrait être la chose délicate mais une fois cela fait, je pense qu'il pourrait être possible de l'automatiser


J'ai bien peur de ne pas comprendre ce que cela a à voir avec mon problème. Qu'est-ce que la découpe d'un polygone avec un filet de pêche a à voir avec le regroupement des polygones dans l'espace et en nombres égaux? Vous semblez concentré sur le domaine, sans compter. La superficie (taille) des polygones de parcelle n'est pas un facteur. Quelle que soit la taille d'un colis, quelle que soit sa taille, ce n'est toujours qu'un propriétaire à qui parler. Voir mon exemple où le rouge est une zone rurale et s'étend largement, tandis que l'orange est urbain et couvre donc une superficie totale beaucoup plus petite.
Emil Brundage

salut vous, désolé, j'ai mal lu votre question. je pense que le post de radouxju pourrait être le chemin à parcourir, mais le lien passe un peu au-dessus de ma tête. Transformer les polygones en points semble logique, puis les regrouper. Il pourrait y avoir un moyen d'introduire le système routier car la distance du point à la route et le point suivant pourraient définir l'élément spatial
Jack Walker


0

Ceci est ma solution pour les événements ponctuels. Aucune garantie que cela fonctionnera toujours ...

  1. Sur votre couche d'événements ponctuelle (appelez la couche 1), ajoutez des colonnes pour x (double), y (double) et uniqueid (entier long)
  2. Ouvrir la table attributaire pour la couche 1. Calculer le point de coordonnées x pour x, le point de coordonnées y pour y et le FID pour l'identifiant unique
  3. Outil d'exécution de statistiques spatiales> Mappage de clusters> Analyse de regroupement
    • définir layer1 comme entités en entrée
    • définir uniqueid comme ID de champ unique
    • Définissez le nombre de groupes (nous dirons 10)
    • Sélectionnez x et y pour les champs d'analyse
    • Choisissez "NO_SPATIAL_CONSTRAINT" pour les contraintes spatiales
    • Cliquez sur OK
  4. Outils d'exécution de statistiques spatiales> Mesure des distributions géographiques> Centre moyen
    • Sélectionnez la sortie de # 3 comme classe de caractéristiques d'entrée
    • Sélectionnez SS_Group comme champ de cas
    • Cliquez sur OK
  5. Open Network Analyst> Outil d'allocation d'emplacement
    • Charger la sortie de # 4 en tant qu'installations
    • Charger layer1 en tant que points de demande
    • Ouvrir les attributs et définir
      • Type de problème en maximisant la couverture capacitaire
      • Installations à choisir comme 10 (à partir du # 3 ci-dessus)
      • Capacité par défaut comme le nombre total d'entités dans la couche 1 divisé par les installations à choisir arrondi (donc si 145 entités et 10 installations / zones, définissez-les comme 15)
      • Cliquez sur OK
        • Résoudre
        • Vos points de demande devraient être plus ou moins également répartis en 10 clusters géographiques

Je suis bloqué à la cinquième étape de votre méthode. J'ai vérifié l'extension Network Analyst et ajouté la barre d'outils Network Analyst. Mais la majeure partie est grisée et je ne vois pas "Outil d'allocation de localisation". J'utilise 10.1.
Emil Brundage

0

Vous devrez d'abord créer un ensemble de données réseau en utilisant vos rues. J'ai essayé cette méthode proposée et j'ai jusqu'à présent eu plus de chance de faire la même chose avec le regroupement (étape 3) lui-même, en utilisant les coordonnées X, Y et les k-moyennes pour les champs de saisie (pas parfait, mais plus rapide et plus proche de ce que je suis) besoin). Je suis ouvert aux autres commentaires et retours.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.