How to change the geometry of a line (lastpoint & startpoint)

1740
4
04-13-2011 08:55 AM
JoelCappello
New Contributor
I'm attempting to change the start point and last point of some lines (pipes). The code runs below without an error but when I check the lines in ArcMap I discover that the end points haven't moved at all. Shouldn't I be able to edit/update the geometry of these points this way?

  
 desc = arcpy.Describe(pipes2)
    PipeCur = arcpy.UpdateCursor(pipes2)
    desc= arcpy.Describe(pipes2)
    shapefield = desc.ShapeFieldName
    cnt = 0
    pipe = PipeCur.next()
    while pipe:
        lngIDSYS = pipe.IDSYS
        whereClause = "IDSYS = " + str(lngIDSYS)
        print whereClause
        pntCursor = arcpy.SearchCursor(EndPoints,whereClause)
        EndPnt = pntCursor.next()#return the only point
        PipeFeat = pipe.getValue(shapefield)# need the shape field name here
        Last = PipeFeat.lastPoint# returns the last point.
        print "Changing the x and y"
        print str(Last.X) + " is the x value before."
        print str(EndPnt.NEAR_X) + " is the x value of Near_X."
        Last.X = EndPnt.NEAR_X
        print str(Last.X) + " is the x value after."
        Last.Y = EndPnt.NEAR_Y
        PipeCur.updateRow(pipe)
        cnt = cnt + 1
        pipe = PipeCur.next()
Tags (2)
0 Kudos
4 Replies
ChrisSnyder
Regular Contributor III
As far as I know in Python - in order to "flip" a line segment, you would have to regurgitate ALL the vertices in reverse order (not just the start/end nodes).

Fortunately Python has some very convenient functions for doing just that. For example, after reading all the vertices into a list where the vertices are tuples, you can simply reverse the order, and then regurgitate. For example:

test = [(0,0),(1,1),(2,2),(3,3),(4,4)]
test.reverse()
test
[(4, 4), (3, 3), (2, 2), (1, 1), (0, 0)]


Another possible way is to select the feature you want to flip and than calc the Shape field to this using the field calculator (don't think you can use this via Python though):

'=========================
'polyline_Flip.cal
'Author: Ianko Tchoukanski
'http://www.ian-ko.com
'=========================
Dim pCurve As ICurve
If (Not IsNull([Shape])) Then
  Set pCurve = [Shape]
  If (Not pCurve.IsEmpty) Then
    pCurve.ReverseOrientation
  End If
End If
__esri_field_calculator_splitter__
pCurve
0 Kudos
JoelCappello
New Contributor
Chris,
Sorry for the confusion but I'm not wanting to flip the start with the end. I have some pipes that have shifted off their junctions about .5 feet in two directions for some unknown reason. So instead of manually editing them I'm using the near_analysis to find the closest junction from the end and start points of the pipes ( I created point layers of the start and end points). Now I'd like to 'move' those start and end points of the pipes to the x and y of their nearest junction. The edits are just not 'holding'.
Make sense?
0 Kudos
ChrisSnyder
Regular Contributor III
Okay I get it.

So maybe I'm wrong, but it looks like you are attempting to directly set the .firstpoint and .lastpoints coordinates. However these are read-only properties of an existing feature, and not directly "writable". You would have to cycle through all the vertices and alter them that way. Seems that maybe one method would be:

1. For each feature, you get the correct start/end coordinates (you got this from the near tool).
2. For each feature you retrieve their current (but offset) start/end coordinates
3. Cycle through the features points, and alter as necessary. Can you use the .replace method (part of the geometry Array) to quickly retrieve the geometry of the start and end? Obviously the start would be index of 0, but can you do an index of -1 like in Python (to get the last)? Dunno... Otherwise you could loop through all the vertices - the start point is obvious, but to find the end you might need to continuously compare each vertex with the known location (step #2).


So this might help, but I wrote some code a long while back that did something similar. In my case, I had an stream layer that, for complicated reasons, had some small "skips" in it, that basically made for a bunch of unconnected stream segments. My approach was to just insert little "connector arcs" instead of move the existing vertices, but... Here is an excerpt of that code:


#NOTE: Due to small inconsistencies in how the "SIMPLIFY" method of the StreamToFeature tool behaves, there are
#      some occasional gaps in the stream network along the fishnet boundary. We need to fix these issues
#      to preserve topological connectivity. My solution is to insert a connector arc (with the correct directionality)
#      to bridge the gap (which is occasionally larger than gp.cellsize!). I explored other methods such as using the
#      "integrate" tool (didn't fix everything with a tolerance of gp.cellsize, and many unintended problems as well).
#      I also thought of just moving the start or end node of the gaped features (quite a bit more complex than just inserting
#      a connector arc). Using the "SIMPLIFY" parameter is about a 25% disk space savings, but there would still be a few
#      disconnected arcs using the "NO_SIMPLIFY" method (where the stream exactly parallels the fishnet tile boundary.
#      Anyway, this is what I came up with...

#Process: Dissolves prelimStreamsFC by GRID_CODE (fixes 99% of the tile boundary issues)
streamDissolve1FC = gdbPath + "\\stream_dislv1"
gp.Dissolve_management(prelimStreamsFC, streamDissolve1FC, "GRID_CODE", "", "SINGLE_PART", ""); showGpMessage()

#Process: Make a baseline copy (before we mess with it!)
baseCopyFC = gdbPath + "\\baseline_copy"
gp.Select_analysis(streamDissolve1FC, baseCopyFC, ""); showGpMessage()

#Process: Figures out what stream reaches are still disconnected
streamIdFreqTbl = gdbPath + "\\stream_id_freq"
gp.Frequency_analysis(streamDissolve1FC, streamIdFreqTbl, "GRID_CODE", ""); showGpMessage()

#Process: Build an SQL string and also a Python dictionary so we can select the problem features
streamIdFreqString = ""
streamIdFreqDict = {}
searchRows = gp.searchcursor(streamIdFreqTbl, "FREQUENCY > 1")
searchRow = searchRows.next()
while searchRow:
    streamIdFreqString = streamIdFreqString + str(searchRow.GRID_CODE) + ","
    streamIdFreqDict[searchRow.GRID_CODE] = searchRow.FREQUENCY
    searchRow = searchRows.next()
del searchRow
del searchRows 

#Process: Loads the firstpoint and lastpoint XY pairs into a dictionary keyed by GRID_CODE
vectorSelectorDict = {}
searchRows = gp.searchcursor(streamDissolve1FC, "GRID_CODE in (" + streamIdFreqString[:-1] + ")")
searchRow = searchRows.next()
while searchRow:
    firstPointX = searchRow.getvalue(shapeFieldName).firstpoint.x
    firstPointY = searchRow.getvalue(shapeFieldName).firstpoint.y
    lastPointX = searchRow.getvalue(shapeFieldName).lastpoint.x
    lastPointY = searchRow.getvalue(shapeFieldName).lastpoint.y
    if vectorSelectorDict.has_key(searchRow.GRID_CODE) == False:
        vectorSelectorDict[searchRow.GRID_CODE] = []
        vectorSelectorDict[searchRow.GRID_CODE].append([firstPointX,firstPointY,lastPointX,lastPointY])
    else:
        vectorSelectorDict[searchRow.GRID_CODE].append([firstPointX,firstPointY,lastPointX,lastPointY])
    searchRow = searchRows.next()
del searchRow
del searchRows

#Process: Figures out what lastpoint and first point combinations are closest together (since we don't
#inherently know what arc segment is furthest upstream) Preserving correct directionality and connectivity is critical!!!
vectorConnectorDict = {}
for gridCode in vectorSelectorDict.keys():
    gridCodeFeatureCount = streamIdFreqDict[gridCode]
    if gridCodeFeatureCount == 2:
        distance1 = math.sqrt(math.pow(vectorSelectorDict[gridCode][0][2] - vectorSelectorDict[gridCode][1][0],2) + math.pow(vectorSelectorDict[gridCode][0][3] - vectorSelectorDict[gridCode][1][1],2))
        distance2 = math.sqrt(math.pow(vectorSelectorDict[gridCode][1][2] - vectorSelectorDict[gridCode][0][0],2) + math.pow(vectorSelectorDict[gridCode][1][3] - vectorSelectorDict[gridCode][0][1],2))
        if distance1 > float(gp.cellsize) and distance2 > float(gp.cellsize):
            message = "ERROR: Grid Code " + str(gridCode) + " - distance1 (" + str(distance1) + ") and distance2 (" + str(distance2) + ") are both more than gp.cellsize!"; showPyMessage() 
        if distance1 < distance2:
            vectorConnectorDict[gridCode] = [vectorSelectorDict[gridCode][0][2],vectorSelectorDict[gridCode][0][3],vectorSelectorDict[gridCode][1][0],vectorSelectorDict[gridCode][1][1]]
        else:
            vectorConnectorDict[gridCode] = [vectorSelectorDict[gridCode][1][2],vectorSelectorDict[gridCode][1][3],vectorSelectorDict[gridCode][0][0],vectorSelectorDict[gridCode][0][1]]
    else:
        message = "ERROR: The frequency count of Grid Code " + str(gridCode) + " is " + str(gridCodeFeatureCount) + "!"; showPyMessage()
    
#Process: Construct the connector arcs!
gp.AddField_management(streamDissolve1FC, "CON_FLG", "SHORT"); showGpMessage()
insertRows = gp.insertcursor(streamDissolve1FC)
for gridCode in vectorConnectorDict.keys():
    lineArray = gp.createobject("Array")
    startPoint = gp.createobject("Point")
    startPoint.x = vectorConnectorDict[gridCode][0]
    startPoint.y = vectorConnectorDict[gridCode][1]
    lineArray.add(startPoint)
    endPoint = gp.createobject("Point")
    endPoint.x = vectorConnectorDict[gridCode][2]
    endPoint.y = vectorConnectorDict[gridCode][3]
    lineArray.add(endPoint)
    insertRow = insertRows.newrow()
    insertRow.GRID_CODE = gridCode
    insertRow.CON_FLG = 1
    insertRow.setvalue(shapeFieldName,lineArray)
    insertRows.insertrow(insertRow)
    lineArray.removeall()
del insertRow
del insertRows
del startPoint
del endPoint
del lineArray 
0 Kudos
JoelCappello
New Contributor
Ah, I didn't catch that it was read-only. I'll consider your approach.
Thanks Chris.
0 Kudos