Verschieben Sie die Legende, wenn sie Features in Dataframe mit ArcPy überlappt

8

Wenn Sie versuchen, einen Weg zu finden, wie Sie die Legende programmgesteuert (arcpy) verschieben können, wenn sie Features innerhalb eines Datenrahmens abfängt. Wenn die Legende im folgenden Szenario die Ansicht des AOI verdeckt, möchte ich, dass sie in eine andere Ecke verschoben wird, bis sie nicht mehr angezeigt wird Problem. Dies muss über dem Datenrahmen liegen, anstatt den Datenrahmen zu verkleinern und nur zur Seite zu legen.

Geben Sie hier die Bildbeschreibung ein

Slevy
quelle
1
Wenn Sie datengesteuerte Seiten verwenden, finden Sie möglicherweise Unterstützung: gis.stackexchange.com/questions/167975/… . Im Allgemeinen würde ich Google nach etwas wie "Legende in datengesteuerten Seiten verschieben" durchsuchen, um weitere Vorschläge zu erhalten. Mit festen Legenden habe ich sie in ein Bild konvertiert und sie wie folgt verschoben: support.esri.com/de/technical-article/000011951 Keine dieser Antworten ist nur eine Problemumgehung.
Johns
Ja,
ich verwende

Antworten:

7

Eingaben: Geben Sie hier die Bildbeschreibung ein Skript:

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

AUSGABE: Geben Sie hier die Bildbeschreibung ein

HINWEISE: Für jede Seite in Datenseite - Skript versucht angetrieben genug Platz in Datenrahmen finden Ecken ohne platzieren Legend (genannt myLegend) sichtbares Merkmal Schicht bedeckt. Das Skript verwendet Fischnetz, um Eckzellen zu identifizieren. Die Zellendimension ist in Datenansichtseinheiten geringfügig größer als die Legendendimension. Eckzelle ist diejenige, die eine Grenze mit 3 Nachbarn teilt. Wenn keine Ecken oder Räume gefunden wurden, wurde die Legende außerhalb des Datenrahmens auf der Layoutseite platziert.

Leider weiß ich nicht, wie man die Seitendefinitionsabfrage verwaltet. Die gezeigten Punkte waren ursprünglich über die gesamte RECHTECK-Ausdehnung verteilt, wobei einige von ihnen keine Zuordnung zu Seiten hatten. Arcpy sieht immer noch die gesamte Ebene, obwohl ich eine Definitionsabfrage (Übereinstimmung) auf die Punkte angewendet habe.

FelixIP
quelle
Vielen Dank für das großartige Schreiben zu diesem Felix, obwohl ich Probleme habe, diese Lösung so flüssig wie Ihr Beispiel zu implementieren, so detailliert dies auch ist, gibt es etwas, das ich beim Erstellen des Kartendokuments, der Legendenankerpunkte usw. Beachten sollte ?
Slevy
1
Die Ankerpunkte befinden sich sowohl für die Legende als auch für den Datenrahmen unten links. Wie habe ich das vergessen?
FelixIP
Ja, hier hat sich definitiv ein Unterschied im Test gemacht. Wenn ich den Ankerpunkt auf die Mitte (für den Datenrahmen) schalten wollte, gehe ich davon aus, dass die gesamte Logik aus dem Ruder läuft? Welchen Teil müsste ich konfigurieren? Nur die Zeilen 33 bis 44?
Slevy
1
Berechnen Sie xmin und xmax durch Breite und Position x. Ähnlich wie bei der y-Achse. Ich bin mir nicht sicher, warum du es brauchst ...
FelixIP
Teil eines anderen Workflows, danke Felix, großer Schritt vorwärts hier
Slevy
3

Die Art und Weise, wie ich dies tun würde, wäre, eine Feature-Class "Legendenelement" zu erstellen, die Ihr Legendenelement im selben Koordinatensystem wie diese Features darstellt.

Auf diese Weise können Sie mithilfe von Ebene nach Ort auswählen testen, ob sich Ihr Legendenelement mit Features überschneidet, und es gegebenenfalls verschieben.

Es ist nicht trivial, aber hervorragend machbar, und auf dieser Site gibt es Fragen und Antworten ( Punkt XY mit arcpy in Seiteneinheiten XY konvertieren? ), Mit denen der schwierigste Teil der Konvertierung zwischen Seiten- und Kartenkoordinaten ermittelt werden kann.

PolyGeo
quelle
1
Das Schwierigste ist, eine Lücke zu finden, die groß genug ist, um in die Legendenbox zu passen.
FelixIP
1
@FelixIP Warum so? Es hört sich so an, als würde sich der Fragesteller darauf beschränken, nur vier Ecken des Datenrahmens zu testen. Ich gehe davon aus, dass sie eine Regel haben, was passiert, wenn keine Ecke geeignet ist.
PolyGeo
Ich denke, das ist der richtige Weg, obwohl die Lücke in der Legende wahrscheinlich das geringste meiner Probleme sein wird. Im Idealfall ändert sich der Maßstab der Karte so lange, bis die Legende das interessierende Polygon nicht mehr abfängt. Ich würde gerne einige praktische Beispiele hören oder sehen, die die Leute versucht haben!
Slevy
2

Unten ist Code, den ich verwendet habe, um Legenden zu verschieben und Karten einzufügen, um Daten nicht zu verdecken. Sie haben nach der Funktion zum Überprüfen von Schnittpunkten in einem anderen Thread gefragt . Dies ist meine Implementierung des Codes eines anderen. Ich erinnere mich nicht genau, woher es kommt. Ich glaube, es war ein Skript, um eine Karte für einen Staat in Neuengland zu verschieben.

Inset ist das Handle für die Legende oder das Inset-Map-Element.

#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

Dann sollte der folgende Code aus diesem Beitrag ( datengesteuerte Seiten mit beweglicher Legende / eingefügter Karte ) sinnvoller sein.

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
quelle
1
sollte erwähnen: In diesem Beispiel ist das Element unten links verankert.
CSB
Vielen Dank, CSB. Ja, für meinen Fall muss der Datenrahmen in der Mitte verankert sein. Ich bin gerade dabei, die Formel für die Ausdehnung von Seitenursprüngen anzupassen. Sobald ich dort bin, werde ich das Beispiel veröffentlichen. Ansonsten sieht es bei ersten Tests sehr vielversprechend aus. Es gibt auch einen Verweis auf "unobscured_lyr", vorausgesetzt, dieser wird außerhalb des Skripts als zu vermeidende Ebene bezeichnet.
Slevy
Richtig, das "unobscured_lyr" ist das, was wir nicht abdecken wollen. Natürlich können Sie es auch mit mehreren Ebenen arbeiten lassen.
CSB