Select to view content in your preferred language

Insert X number of vertices in a line?

1900
6
06-23-2022 12:35 PM
anotgrass_cosprings
Emerging Contributor

Does anyone know if it is possible to insert a # of vertices to a line programmatically?  

Basically what I am looking for is a script/code that will take a variable # (from a field I calculate) and insert that many vertices to the line -- placement isn't too important at the moment.

 

In my case:

I have an area, and within that area I might have 4 lines.  I need each of those lines to have the same number of vertices. I have a model that will iterate through my features, find the line with the highest vertex count, store that number, then calculate the difference for the other lines.

 Basic example:

Line 1 has 100 vertices. Line 2 has 95 vertices.  The model will calculate a field to say that Line 2 needs "5" vertices to equal 100. 

Then I need to find a way to insert 5 vertices to that line. 

 

I have thousands of lines I need to do this for, so manually right clicking, and adding a vertex is not a solution. Also, Densify doesn't suit my needs either.

 

I did see this thread on stackexchange - arcgis desktop - Adding vertex set distance from first or last vertex in polyline? - Geographic Info...

but not sure how or if that could even be modified to do what I am looking for.

 

 

Tags (2)
0 Kudos
6 Replies
DavidSolari
MVP Regular Contributor

For manual vertex fiddling a good method is to extract the geometry object for each feature like

with arcpy.da.UpdateCursor(my_features, "SHAPE@") as update:
    for row in update:
        geom = row[0]

and then dump the geometry data to JSON by combining the loads function from the standard Python json library with the geometry object's JSON property. Once you're done manipulating the dictionary you get back you can use something like

new_geom = arcpy.AsShape(json_data)

and throw that into your update cursor. You'll probably need a few helper functions to deal with subsets of the JSON structure so you can deserialize individual points/segments as geometry objects without losing your spatial reference info.

As for your specific case, if you just need to add vertices to the lines, you can find the final two vertices in the line, construct a temporary line object from that, then call the line's positionAlongLine method with use_percentage=True to dump out new vertices that won't distort your original feature. No guarantee this'll work for complex segments but it's a starting point. Have fun!

0 Kudos
anotgrass_cosprings
Emerging Contributor

Thanks David,

That seems like really good information, it's just that I have no idea how to use that info lol

I am trying to learn more Python so I spent more time trying to figure out the first part of your post rather than finding the final two vertices and constructing a temp line.

This is as far as I got on that.  When trying to print/see what happens after the loads function I get an error about the JSON object being a polyline.

 

 

import arcpy, json

# Input Feature
myFeatures = "Lines"

with arcpy.da.UpdateCursor(myFeatures, "SHAPE@") as update:
    for row in update:
        geom = row[0]
 
geom_loads = json.loads(geom) 
print(geom_loads) 

 

anotgrass_cosprings_0-1656088794738.png

I tried printing just the geometry and did get this

anotgrass_cosprings_1-1656089053909.png

I'm assuming I need to store that as JSON and then use the loads function but I'm not sure really

0 Kudos
DavidSolari
MVP Regular Contributor

You're on the right track, all arcpy Geometry objects have a JSON property, so your line #10 should be:

 

geom_loads = json.loads(geom.JSON)

 

For simpler jobs you can skip JSON fiddling and use some of the properties and methods on the Geometry objects themselves, such as getPart and spatialReference. For example, here's a line of code that creates a new Polyline object from the last 2 points of another Polyline:

 

new_geom = arcpy.Polyline(geom.getPart(-1)[0][-2:], geom.spatialReference)

 

I highly recommend you keep the official docs on hand, there's a surprising amount of built-in methods on these objects so you can build up your own little GIS factory. Here's some key links:
Geometry (this is the base class for all the other Geometry types).
PointGeometry
Polyline
Polygon
Point (this is the simple 2D/3D/4D point object that all of the other Geometries are built out of. If you want the object that you deal with from a point feature class' shape field, you want "PointGeometry").
Array (this is like a Python list but is different somehow, Polyline and Polygon objects use this class to hold the Point objects they're built from. You can use standard Python index and slice syntax to extract data).

anotgrass_cosprings
Emerging Contributor

Thanks a lot for your help and the links to those resources. Very much appreciated!

0 Kudos
anotgrass_cosprings
Emerging Contributor

I'm struggling to figure how to do exactly what I need.  Do you know if it's possible to modify this code to add vertices by a a number in a field?  This does add a vertex at a specified distance, but only one - and I might need to add 1-50 depending on the line.

 

def insertV(dist,shp):
 newP=shp.positionAlongLine (dist).firstPoint
 n,arr,part=0,arcpy.Array(),shp.getPart(0)
 m=len(part)
 for i in range(m):
  p=part.getObject(i); L=shp.measureOnLine(p)
  if L>dist:
   n+=1
   if n==1:arr.add(newP)
  arr.add(p)
 return arcpy.Polyline(arr)

---------------------------

insertV(100, !Shape! )

 

0 Kudos
DavidSolari
MVP Regular Contributor

The good news is you've encapsulated the "add a vertex at a given distance to an existing line" code to a nice, easy to use function. You can use that in another function, maybe something like this:

 

 

def densify(shp, vertex_count):
    # Add 1 so the final vertex isn't coincident with the line end
    stride = shp.length / (vertex_count + 1)
    dist = 0.0
    for _ in range(vertex_count):
        dist += stride
        shp = insertV(dist, shp)
    return shp

 

 

As an aside, I misread your function as an attempt to clip off the first portion of a line, so here's some code that does that at a bonus:

 

 

def shortenLineReverse(dist, shp):
    newP = shp.positionAlongLine(dist).firstPoint
    arr = arcpy.Array()
    arr.add(newP)
    part = shp.getPart(0)
    for i in range(len(part)):
        p = part.getObject(i)
        l = shp.measureOnLine(p)
        if l > dist:
            arr.add(p)
    return arcpy.Polyline(arr, shp.spatialReference)

 

 

0 Kudos