11

I have a number non-georeferenced CAD layers (see this question) that have text annotation features. I have created a model to convert the text to points, but after converting the annotation to a Point featureclass, I see that the CAD text anchor points do not coincide with the center of the CAD text (which is where the points belong).

Therefore, I would like to programatically (using ArcPy or ModelBuilder) [move] a feature relative to its current location (delta x,y) using a measured X,Y value that I will provide.

This would allow me to move the GIS points back to where they belong, instead of the offset CAD anchor point.

How can I accomplish this task?


@PolyGeo gave an excellent answer using SHAPE@XY IN 10.1, but currently I am running 10.0. Any 10.0 ideas?

RyanKDalton
  • 23,068
  • 17
  • 110
  • 178

6 Answers6

17

This code should do it using the SHAPE@XY token that came with arcpy.da.UpdateCursor in ArcGIS 10.1.

import arcpy
# Set some variables
fc = r"C:\temp\test.gdb\testFC"
fc2 = r"C:\temp\test.gdb\testFCcopy"
xOffset = 0.001
yOffset = 0.001
# Code to make a copy which will have its coordinates moved (and can be compared with original)
if arcpy.Exists(fc2):
    arcpy.Delete_management(fc2)
arcpy.Copy_management(fc,fc2)
# Perform the move
with arcpy.da.UpdateCursor(fc2, ["SHAPE@XY"]) as cursor:
    for row in cursor:
        cursor.updateRow([[row[0][0] + xOffset,row[0][1] + yOffset]])

The coding pattern used here came from ArcPy Café.

PolyGeo
  • 65,136
  • 29
  • 109
  • 338
  • Ugh! It took me until this morning to realize that SHAPE@XY is only available in 10.1, and my company is still using 10.0. This is a great answer (going forward), but I'm going to wait and see if anyone has any suggestions for 10.0. Thanks! – RyanKDalton Jul 15 '13 at 14:20
  • More information on a similar process for anybody reading this. Still 10.1. http://arcpy.wordpress.com/2013/06/07/disperse-overlapping-points/ – theJones Jul 15 '13 at 15:03
  • Is this actually setting the values anywhere? Never used the UpdateCursor like that before. Usually I do += and then update the row. Otherwise only thing I do different in my version is the UpdateCursor uses ['SHAPE@X', 'SHAPE@Y'] so you can access them as row[0] and row[1] instead of having to do the row[0][0] and row[0][1]. Think its just a bit easier to read for me. – eseglem Jul 15 '13 at 16:03
  • Yes, that is a valid way to update the rows. Actually, I had never seen a value passed in updateRow() until a few weeks ago. It was actually an example for updating the geometry. – Paul Jul 15 '13 at 17:34
  • Thanks so much for your answer PolyGeo! I was actually pretty impressed that the code worked without modifications. I'm running ArcGIS Desktop 10.6 – Rie Mino Mar 02 '19 at 02:51
10

I credit @artwork21 for leading me to my final solution. I actually found a nearly complete script in the ArcGIS 10.0 online help article called "Calculate Field examples", listed under the subcategory "Code samples—geometry" and "For a point feature class, shift the x coordinate of each point by 100"

The final script that I used within the ModelBuilder "Calculate Field" tool was:

Expression:

shiftXYCoordinates(!SHAPE!,%ShiftX%,%ShiftY%)

where ShiftX and ShiftY are variables (as parameters) defined on the ModelBuilder canvas.

Expression Type:

PYTHON_9.3

Code Block:

def shiftXYCoordinates(shape,x_shift,y_shift):
   point = shape.getPart(0)
   point.X += float(x_shift)
   point.Y += float(y_shift)
   return point

Since all models work on a selected set, you should also be able to create this as a generic tool that will work in conjunction with other models/tools in other modelbuilder sessions. The very simple model I created (as a "plugin" to other models to shift coordinate values) looks like this. That way I can control the shift on a per-selection-set basis (as defined in other models):

ShiftXY Model

It worked like a charm, thank you all for your input!

RyanKDalton
  • 23,068
  • 17
  • 110
  • 178
5

I adapted the solution to move / shift points point into a certain direction (angle) and a given distance.

Looks like:

def shiftXYCoordinates(shape,angle,distance):
point = shape.getPart(0)
point.Y += distance * math.cos(math.radians(angle))
point.X += distance * math.sin(math.radians(angle))
return point

and be called like shiftXYCoordinates(!SHAPE!, !Angle! ,5000), if you have a field “angle” for your points features (or with a constant of course). Angle should be given in decimal degrees. 0 will shift “up”, 90 “right” etc. I got them after creating strip map index features and converting those to points.

Also make sure to select Field Name “Shape” before running :)

(Solution tested in ArcMap 10.0 SP5)

PolyGeo
  • 65,136
  • 29
  • 109
  • 338
kgl
  • 51
  • 1
  • 1
5

You may also use this field calculator script to move feature locations:

def XYsetVALUE( shape, X_value, Y_value): 
  myMoveX = 0.001
  myMoveY = 0.001
  point = shape.getPart(0) 
  point.X = X_value + myMoveX
  point.Y = Y_value + myMoveY
  return point 

XYsetVALUE ( !SHAPE!, !X_COORD!, !Y_COORD! )

You could include an extra Calculate Field method within your model using the function above.

artwork21
  • 35,114
  • 8
  • 66
  • 134
  • Thats an interesting way to do it, I didn't actually know you could field calculate on the shape field. This may actually be the easiest way to get it done if it is a set offset for all points. It would probably be faster to do point.X += myMoveX and point.Y += myMoveY instead of needing to pass in X and Y coordinates for it though. – eseglem Jul 15 '13 at 15:58
4

As you can see, it's a lot easier in 10.1 when you get access to cursor tokens.

import arcpy
# Code to move features in copy of same dataset
fc = r"C:\temp\test.gdb\testFC"
fc2 = r"C:\temp\test.gdb\testFCcopy"
xOffset = 0.001
yOffset = 0.001
if arcpy.Exists(fc2):
    arcpy.Delete_management(fc2)
arcpy.Copy_management(fc, fc2)

shape = arcpy.Describe(fc2).ShapeFieldName

cursor = arcpy.UpdateCursor(fc2)
for row in cursor:    
    point = row.getValue(shape).getPart()
    row.setValue(shape, arcpy.Point(point.X + xOffset, point.Y + yOffset))
    cursor.updateRow(row) 

del point, row, cursor
Paul
  • 11,608
  • 1
  • 29
  • 47
2

This works for 10.0:

# Featureclass here
FC = r'featureclass'

fcount = 0
shapefield = arcpy.Describe(FC).shapeFieldName
featureUpdate = arcpy.UpdateCursor(FC)
for f in featureUpdate:
    # Hard coded shifts but easy enough to set up a lookup function if needed
    sLon = 0.001
    sLat = 0.001
    # Optional but I like to count to see where it is at in the process
    if fcount % 1000 == 0:
        print('Updating feature %s...' %(fcount))
    # Get the original value
    cF = f.getValue(shapefield)
    cPNT = cF.getPart()
    # Create a new point with the shifted value
    sPNT = arcpy.Point(cPNT.X - sLon, cPNT.Y - sLAT)
    # Set the shapefield to the new point and update feature
    f.setValue(shapefield, sPNT)
    featureUpdate.updateRow(f)
    fcount += 1
del featureUpdate
PolyGeo
  • 65,136
  • 29
  • 109
  • 338
eseglem
  • 800
  • 1
  • 5
  • 18