Wednesday, 3 May 2017

geoprocessing - ArcGIS: Create a line layer from a point layer and csv data file


UPDATED DECEMBER 20


I have a Regular point layer and a csv file that has the same ids as the point layer. The csv file is organized as


ID     From    To     Weight
-- ---- -- ------
01 1234 1235 2

02 1234 1236 4
03 1234 1237 1
04 1235 1234 4
05 1236 1235 3

// etc

the point layer is organized in the following way:


MY_ID
-----

1234
1235
1236
1237

the From / To fields correspond to a my_ID in the point layer. I'd like to create a new line layer, such that the From, To, Weight correspond to both the point and csv files



Answer



We had a similar need, so I wrote up the following python script. It has been tested with CSV, shapefiles, PGDB*, and FGDB. It works on 9.3 and 10. It can be run from the OS command line or imported into toolbox and run from toolbox or the ArcGIS command line.


# ---------------------------------------------------------------------------
# Table2Lines.py

# Created: 2011-01-12
# Author: Regan Sarwas, National Park Service
#
# Builds a line feature class from a line data table and a point feature class
# The line table must have three fields for the line Id, point1 Id, and point2 Id
# The point feature class must be a point (not multi-point) shape type and have a
# field with the point id that matches the type and domain of the point ids in the line table.
# The names of all fields are provided in the input arguments
#
# usage:

# python Table2Lines.py path_to_line_table Line_ID_Field From_Point_ID_Field To_Point_ID_Field
# path_to_point_FC Point_ID_Field output_line_FC
# example:
# python Table2Lines.py "c:\tmp\lines.csv" "ID" "From" "To" "c:\tmp\pts.shp" "MY_ID" "c:\tmp\lines.shp"
#
# License:
# Public Domain
# Disclaimer:
# This software is provide "as is" and the National Park Service gives
# no warranty, expressed or implied, blah, blah, blah

# ---------------------------------------------------------------------------

import os, arcgisscripting
gp = arcgisscripting.create(9.3)
gp.Overwriteoutput = 1

#I use one search cursor and cache all the points in a dictionary.
#This avoids creating a search cursor for each point as lines are processed
#Assumes Python is more efficient and faster than ArcGIS. Should be tested.
def GetPoints(pointFC,pointIDField):

points = {}
pointDescription = gp.Describe(pointFC)
pointShapeField = pointDescription.ShapeFieldName
pointIdFieldDelimited = gp.AddFieldDelimiters(pointFC, pointIdField)
where = pointIdFieldDelimited + " is not null"
spatialRef = ""
fields = pointIDField +"; " + pointShapeField
sort = ""
pts = gp.SearchCursor(pointFC, where, spatialRef, fields, sort)
pt = pts.Next()

while pt != None:
points[pt.GetValue(pointIDField)] = pt.Shape.getPart()
pt = pts.Next()
return points

def MakeLine(pt1, pt2):
""" pt1 and pt2 should be gp point objects or None """
if (pt1 == None or pt2 == None):
return None
pts = gp.createobject("Array")

pts.add(pt1)
pts.add(pt2)
line = gp.createobject("geometry", "polyline", pts)
if (line == None) or (line.FirstPoint == None) or (line.LastPoint == None):
return None
return line

# Input field types must be in mapType (defined below).
# Point id type in both input data sets must map to the same type, i.e. OID and Integer


# Maps the string returned by gp.describe.Field.Type to the string required by gp.AddField()
mapType = {"SmallInteger" : "SHORT",
"Integer" : "LONG",
"Single" : "FLOAT",
"Double" : "DOUBLE",
"String" : "TEXT",
"Date" : "DATE",
"OID" : "LONG", #Not usually creatable with AddField() - use with Caution
"Geometry" : "BLOB", #Not usually creatable with AddField() - use with Caution
"BLOB" : "BLOB"}


#GET INPUT
lineTable = gp.GetParameterAsText(0)
lineIdField = gp.GetParameterAsText(1)
fromPointIdField = gp.GetParameterAsText(2)
toPointIdField = gp.GetParameterAsText(3)
pointFC = gp.GetParameterAsText(4)
pointIdField = gp.GetParameterAsText(5)
lineFC = gp.GetParameterAsText(6)


#VERIFY INPUT (mostly for command line. Toolbox does some validation for us)
lineIdFieldType = ""
fromPointIdFieldType = ""
toPointIdFieldType = ""
tableDescription = gp.Describe(lineTable)
for field in tableDescription.Fields:
if field.Name == lineIdField:
lineIdFieldType = mapType[field.Type]
if field.Name == fromPointIdField:
fromPointIdFieldType = mapType[field.Type]

if field.Name == toPointIdField:
toPointIdFieldType = mapType[field.Type]

if lineIdFieldType == "":
raise ValueError("Field '" + lineIdField + "' not found in " + lineTable)
if fromPointIdFieldType == "":
raise ValueError("Field '" + fromPointIdField + "' not found in " + lineTable)
if toPointIdFieldType == "":
raise ValueError("Field '" + toPointIdField + "' not found in " + lineTable)


pointDescription = gp.Describe(pointFC)
if pointDescription.shapeType != "Point":
raise ValueError(pointFC + " is a " + pointDescription.shapeType +
" not a Point Feature Class.")

pointIdFieldType = ""
for field in pointDescription.Fields:
if field.Name == pointIdField:
pointIdFieldType = mapType[field.Type]
break


if pointIdFieldType == "":
raise ValueError("Field '" + pointIdField + "' not found in " + pointFC)
if (pointIdFieldType != fromPointIdFieldType or
pointIdFieldType != fromPointIdFieldType):
raise ValueError("Field types do not match - cannot link points to lines.")

gp.AddMessage("Input validated")

# Create Feature Class...

outSpatialRef = pointDescription.SpatialReference
outPath, outName = os.path.split(lineFC)
gp.CreateFeatureclass_management(outPath, outName, "POLYLINE", "", "DISABLED", "DISABLED", outSpatialRef, "", "0", "0", "0")

gp.AddMessage("Created the output feature class")

# Add Fields...
lineIdFieldValid = gp.ValidateFieldName(lineIdField,outPath)
gp.AddField_management(lineFC, lineIdFieldValid, lineIdFieldType, "", "", "", "", "NON_NULLABLE", "REQUIRED", "")
fromPointIdFieldValid = gp.ValidateFieldName(fromPointIdField,outPath)

gp.AddField_management(lineFC, fromPointIdFieldValid, fromPointIdFieldType, "", "", "", "", "NON_NULLABLE", "REQUIRED", "")
toPointIdFieldValid = gp.ValidateFieldName(toPointIdField,outPath)
gp.AddField_management(lineFC, toPointIdFieldValid, toPointIdFieldType, "", "", "", "", "NON_NULLABLE", "REQUIRED", "")

gp.AddMessage("Added the fields to the output feature class")

points = GetPoints(pointFC,pointIdField)

fromPointIdFieldDelimited = gp.AddFieldDelimiters(lineTable, fromPointIdField)
toPointIdFieldDelimited = gp.AddFieldDelimiters(lineTable, toPointIdField)

where = fromPointIdFieldDelimited + " is not null and " + toPointIdFieldDelimited + " is not null"
spatialRef = ""
#fields = lineIdField +"; " + fromPointIdField +"; " +toPointIdField
fields = ""
sort = ""
#Create the input(search) and output(insert) cursors.
lines = gp.SearchCursor(lineTable, where, spatialRef, fields, sort)
newLines = gp.InsertCursor(lineFC)

line = lines.Next()

while line != None:
pt1Id = line.GetValue(fromPointIdField)
pt2Id = line.GetValue(toPointIdField)
try:
lineGeom = MakeLine(points[pt1Id],points[pt2Id])
except:
lineGeom = None
if lineGeom == None:
gp.AddWarning("Unable to create line " + str(line.GetValue(lineIdField)))
else:

# Create a new feature in the feature class
newLine = newLines.NewRow()
newLine.Shape = lineGeom
newLine.SetValue(lineIdFieldValid, line.GetValue(lineIdField))
newLine.SetValue(fromPointIdFieldValid, pt1Id)
newLine.SetValue(toPointIdFieldValid, pt2Id)
newLines.insertRow(newLine)
line = lines.Next()

#Closes the insert cursor, and releases the exclusive lock

del newLine
del newLines
gp.AddMessage("Done.")

*output to PGDB does not work due to a schema lock problem


Also, it does not copy any attributes from the line table, so those must be joined after the line feature class is created.


No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...