Déplacer la légende si elle chevauche des entités dans la trame de données à l'aide d'ArcPy

8

Essayer de trouver un moyen par programme (arcpy) de déplacer la légende si elle intercepte des entités dans un bloc de données, dans le scénario ci-dessous, si la légende obscurcit la vue de l'AOI, alors je veux qu'elle se déplace vers un autre coin jusqu'à ce que ce ne soit pas un problème. Cela doit être au-dessus du bloc de données au lieu de réduire le bloc de données et de le mettre de côté.

entrez la description de l'image ici

Slevy
la source
1
Si vous utilisez des pages dynamiques, vous trouverez peut-être de l'aide à ce sujet: gis.stackexchange.com/questions/167975/… . Plus généralement, je rechercherais sur Google quelque chose comme "déplacer la légende dans les pages dynamiques" pour d'autres suggestions. Avec des légendes fixes, je les ai converties en une image et j'ai utilisé les éléments suivants pour les déplacer: support.esri.com/en/technical-article/000011951 Aucune de ces réponses n'est une réponse, juste des solutions de contournement.
johns
Oui, j'utilise actuellement des pages axées sur les données, merci pour le lien Johns
Slevy

Réponses:

7

Entrées: entrez la description de l'image ici Script:

import arcpy, traceback, os, sys, time
from arcpy import env
import numpy as np
env.overwriteOutput = True
outFolder=arcpy.GetParameterAsText(0)
env.workspace = outFolder
dpi=2000
tempf=r'in_memory\many'
sj=r'in_memory\sj'
## ERROR HANDLING
def showPyMessage():
    arcpy.AddMessage(str(time.ctime()) + " - " + message)
try:
    mxd = arcpy.mapping.MapDocument("CURRENT")
    allLayers=arcpy.mapping.ListLayers(mxd,"*")
    ddp = mxd.dataDrivenPages
    df = arcpy.mapping.ListDataFrames(mxd)[0]
    SR = df.spatialReference
##  GET LEGEND ELEMENT
    legendElm = arcpy.mapping.ListLayoutElements(mxd, "LEGEND_ELEMENT", "myLegend")[0]
#   GET PAGES INFO
    thePagesLayer = arcpy.mapping.ListLayers(mxd,ddp.indexLayer.name)[0]
    fld = ddp.pageNameField.name
#   SHUFFLE THROUGH PAGES
    for pageID in range(1, ddp.pageCount+1):
        ddp.currentPageID = pageID
        aPage=ddp.pageRow.getValue(fld)
        arcpy.RefreshActiveView()
##      DEFINE WIDTH OF legend IN MAP UNITS..
        E=df.extent
        xmin=df.elementPositionX;xmax=xmin+df.elementWidth
        x=[xmin,xmax];y=[E.XMin,E.XMax]
        aX,bX=np.polyfit(x, y, 1)
        w=aX*legendElm.elementWidth
##      and COMPUTE NUMBER OF ROWS FOR FISHNET
        nRows=(E.XMax-E.XMin)//w
##      DEFINE HEIGHT OF legend IN MAP UNITS
        ymin=df.elementPositionY;ymax=ymin+df.elementHeight
        x=[ymin,ymax];y=[E.YMin,E.YMax]
        aY,bY=np.polyfit(x, y, 1)
        h=aY*legendElm.elementHeight
##      and COMPUTE NUMBER OF COLUMNS FOR FISHNET
        nCols=(E.YMax-E.YMin)//h
##      CREATE FISHNET WITH SLIGHTLY BIGGER CELLS (due to different aspect ratio between legend and dataframe)
        origPoint='%s %s' %(E.XMin,E.YMin)
        yPoint='%s %s' %(E.XMin,E.YMax)
        endPoint='%s %s' %(E.XMax,E.YMax)
        arcpy.CreateFishnet_management(tempf, origPoint,yPoint,
                                       "0", "0", nCols, nRows,endPoint,
                                       "NO_LABELS", "", "POLYGON")
        arcpy.DefineProjection_management(tempf, SR)
##      CHECK CORNER CELLS ONLY
        arcpy.SpatialJoin_analysis(tempf, tempf, sj, "JOIN_ONE_TO_ONE",
                                   match_option="SHARE_A_LINE_SEGMENT_WITH")
        nCorners=0
        with arcpy.da.SearchCursor(sj, ("Shape@","Join_Count")) as cursor:
            for shp, neighbours in cursor:
                if neighbours!=3:continue
                nCorners+=1; N=0
                for lyr in allLayers:
                    if not lyr.visible:continue
                    if lyr.isGroupLayer:continue
                    if not lyr.isFeatureLayer:continue
##      CHECK IF THERE ARE FEATURES INSIDE CORNER CELL
                    arcpy.Clip_analysis(lyr, shp, tempf)
                    result=arcpy.GetCount_management(tempf)
                    n=int(result.getOutput(0))
                    N+=n
                    if n>0: break
##      IF NONE, CELL FOUND; COMPUTE PAGE COORDINATES FOR LEGEND AND BREAK
                if N==0:
                    tempRaster=outFolder+os.sep+aPage+".png"
                    e=shp.extent;X=e.XMin;Y=e.YMin
                    x=(X-bX)/aX;y=(Y-bY)/aY
                    break
        if nCorners==0: N=1
##      IF NO CELL FOUND PLACE LEGEND OUTSIDE DATAFRAME
        if N>0:
            x=df.elementPositionX+df.elementWidth
            y=df.elementPositionY
        legendElm.elementPositionY=y
        legendElm.elementPositionX=x
        outFile=outFolder+os.sep+aPage+".png"
        arcpy.AddMessage(outFile)
        arcpy.mapping.ExportToPNG(mxd,outFile)
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()

PRODUCTION: entrez la description de l'image ici

REMARQUES: Pour chaque page des pages axées sur les données, le script tente de trouver suffisamment d'espace dans les coins du cadre de données pour placer la légende (appelée myLegend) sans couvrir aucune couche d'entités visible. Le script utilise un filet pour identifier les cellules des coins. La dimension de cellule est légèrement supérieure à la dimension de légende dans les unités d'affichage des données. La cellule d'angle est celle qui partage une frontière avec 3 voisins. Si aucun coin ou pièce n'est trouvé, Legend place le cadre de données extérieur sur la page de disposition.

Malheureusement, je ne sais pas comment gérer la requête de définition de page. Les points affichés étaient à l'origine dispersés tout autour de l'étendue RECTANGLE, certains d'entre eux n'ayant aucune association avec les pages. Arcpy voit toujours la couche entière, bien que j'aie appliqué une requête de définition (correspondance) aux points.

FelixIP
la source
Merci pour l'excellent compte rendu sur ce Felix, bien que je rencontre des problèmes pour implémenter cette solution afin de fonctionner aussi fluide que votre exemple, aussi détaillé soit-il, y a-t-il quelque chose que je devrais savoir lors de la création du document ArcMap, des points d'ancrage de légende, etc. ?
Slevy
1
Les points d'ancrage sont en bas à gauche pour la légende et le bloc de données. Comment ai-je oublié ça?
FelixIP
Oui, ça a définitivement fait une différence dans le test ici. Si je voulais changer le point d'ancrage au milieu (pour la trame de données), je suppose que toute la logique est hors de contrôle? quelle partie aurais-je besoin de configurer. Juste les lignes 33 à 44?
Slevy
1
Calculez xmin et xmax à travers la largeur et la position x. Similaire à l'axe y. Je ne sais pas pourquoi vous en avez besoin ...
FelixIP
Une partie d'un autre workflow, merci Felix, un grand pas en avant ici
Slevy
3

La façon de procéder serait de créer une classe d'entités "élément de légende" qui représente votre élément de légende dans le même système de coordonnées que ces entités.

De cette façon, vous pouvez utiliser Sélectionner un calque par emplacement pour tester si votre élément de légende chevauche des entités et le déplacer si c'est le cas.

Son non trivial mais éminemment faisable et il y a un Q&A sur ce site ( Convertir le point XY en unités de page XY en utilisant arcpy? ) Qui pourrait être utilisé pour résoudre la partie la plus difficile de la conversion entre les coordonnées de page et de carte.

PolyGeo
la source
1
La partie la plus difficile est de trouver un écart assez grand pour s'adapter à la boîte de légende.
FelixIP
1
@FelixIP Pourquoi? Il semble que le demandeur se limite à tester uniquement les quatre coins de la trame de données. Je suppose qu'ils ont une règle pour ce qui se passe si aucun coin ne convient.
PolyGeo
Je pense que c'est la voie à suivre, même si l'écart dans la légende sera probablement le moindre de mes problèmes. Idéalement, l'échelle de la carte continuera de changer jusqu'à ce que la légende n'intercepte pas le polygone d'intérêt. J'aimerais bien entendre ou voir des exemples pratiques que les gens ont tentés!
Slevy
2

Vous trouverez ci-dessous le code que j'ai utilisé pour déplacer les légendes et les cartes en encart afin de ne pas masquer les données. Vous avez posé des questions sur la fonction de vérification d'intersection sur un autre thread. Ceci est mon implémentation du code de quelqu'un d'autre. Je ne me souviens pas exactement d'où ça vient. C'était un script pour déplacer une carte en médaillon pour un état de la Nouvelle-Angleterre, je pense.

encart est le handle de la légende ou de l'élément de carte en encart.

#check intersect function


def checkIntersect(MovableObject):

    #get absolute x and y disatnce of MovableObject in page units
    PageOriginDistX = (inset.elementPositionX + inset.elementWidth) - DataFrame.elementPositionX #Xmax in page units
    PageOriginDistY = (inset.elementPositionY + inset.elementHeight) - DataFrame.elementPositionY #absolute y disatnce of element


    #Generate x/y pairs for new tempfile used to test intersection of original MovableObject placement
    Xmax = DataFrame.extent.XMin + ((DataFrame.extent.XMax - DataFrame.extent.XMin) *
                                    (PageOriginDistX / DataFrame.elementWidth))
    Xmin = DataFrame.extent.XMin + ((DataFrame.extent.XMax - DataFrame.extent.XMin) *
                                    ((inset.elementPositionX - DataFrame.elementPositionX) / DataFrame.elementWidth))
    Ymax = DataFrame.extent.YMin + ((DataFrame.extent.YMax - DataFrame.extent.YMin) *
                                    (PageOriginDistY / DataFrame.elementHeight))
    Ymin = DataFrame.extent.YMin + ((DataFrame.extent.YMax - DataFrame.extent.YMin) *
                                    ((inset.elementPositionY - DataFrame.elementPositionY) / DataFrame.elementHeight))


    #list of coords for temp polygon
    coordList = [[[Xmax,Ymax], [Xmax,Ymin], [Xmin,Ymin], [Xmin,Ymax]]]
    #create empty temp poly as tempShape, give it a spatial ref, make it into a featureclass so it works
    #with intersect
    tempShape = os.path.join(sys.path[0], "temp.shp")
    arcpy.CreateFeatureclass_management(sys.path[0], "temp.shp","POLYGON")
    array = arcpy.Array()
    point = arcpy.Point()
    featureList = []

    arcpy.env.overwriteOutput = True
    for feature in coordList:
        for coordPair in feature:
            point.X = coordPair[0]
            point.Y = coordPair[1]
            array.add(point)     
        array.add(array.getObject(0))    
        polygon = arcpy.Polygon(array)    
        array.removeAll()
        featureList.append(polygon)

    arcpy.CopyFeatures_management(featureList, tempShape)
    arcpy.MakeFeatureLayer_management(tempShape, "tempShape_lyr")

    #check for intersect
    arcpy.SelectLayerByLocation_management("unobscured_lyr", "INTERSECT",   "tempShape_lyr", "", "NEW_SELECTION")

    #initiate search and count
    polyCursor = arcpy.SearchCursor("unobscured_lyr")
    polyRow = polyCursor.next()
    count = 0

    #Clear Selection
    arcpy.SelectLayerByAttribute_management("unobscured_lyr","CLEAR_SELECTION")

    #Delete the temporary shapefile.
    arcpy.Delete_management(tempShape)

    #count
    while polyRow:
        count = count + 1
        polyRow = polyCursor.next()


    #Clear Selection
    arcpy.SelectLayerByAttribute_management("unobscured_lyr","CLEAR_SELECTION")

    #Delete the temporary shapefile.
    arcpy.Delete_management(tempShape)

    #Return the count value to main part of script to determine placement of locator map.
    return count

Ensuite, le code ci-dessous de cet article ( Pages dynamiques avec carte mobile de légende / encart ) devrait avoir plus de sens.

for pageNum in range(1, mxd.dataDrivenPages.pageCount + 1):
#setup naming and path for output maps
path = mxd.filePath
bn = os.path.basename(path)[:-4]
mxd.dataDrivenPages.currentPageID = pageNum   

insetDefaultX = inset.elementPositionX
insetDefaultY = inset.elementPositionY

#check defualt position for intersect
intersect = checkIntersect(inset)

if intersect == 0: #if it doesn't intersect, print the map
    arcpy.mapping.ExportToEPS(mxd, exportFolder + "\\" + bn + "_"+ str(pageNum) + ".eps", "Page_Layout",640,480,300,"BETTER","RGB",3,"ADAPTIVE","RASTERIZE_BITMAP",True,False)

else: #intersect != 0: #move inset to SE corner
    inset.elementPositionX = (DataFrame.elementPositionX + DataFrame.elementWidth) - inset.elementWidth
    inset.elementPositionY = DataFrame.elementPositionY
CSB
la source
1
devrait mentionner: dans cet exemple, l'élément est ancré en bas à gauche.
CSB
Merci CSB, oui pour mon cas, j'ai besoin que le bloc de données soit ancré au milieu, donc je suis juste en train de personnaliser votre formule d'étendue de l'origine de la page, je posterai l'exemple une fois que j'y serai. Sinon, cela semble très prometteur lors des tests initiaux. En outre, il y a une référence à "unobscured_lyr", en supposant que cela est référencé en dehors du script comme couche à éviter?
Slevy
correct, le "unobscured_lyr" est celui que nous essayons de ne pas couvrir. bien sûr, vous pouvez également le faire fonctionner avec plusieurs couches.
CSB