Placing random line segments within polygon?

I would like to generate multiple line segments of a fixed length, a random bearing, and both segment endpoints within the polygon. It's not clear from the example, but it's fine if the segments intersect each other or span the indented features as long as both endpoints are in the polygon.

I also want the attributes table to include lat/lon for each segment endpoint and the azimuth between the endpoints.

Is this possible?

• Good question. This is certainly possible. However, could you please edit your question to include what software you're using?
– Fezter
May 20, 2016 at 0:08
• Also, do you have python experience?
– Fezter
May 20, 2016 at 1:29

Here is a Python ArcGIS arcpy solution. I've optimized this for speed by using in_memory workspace wherever possible--it is quite fast. This is the general workflow:

1. Negative Buffer the study area by the transect length.
2. Create Random Points inside the negative buffer (study area). These will be the starting points for the transects. Note that you can offset the points n distance from each other if you choose.
3. Add fields to random points attributes and use a da UpdateCursor to populate rows with x, y, distance, and bearing values.
4. Run Bearing Distance to Line to generate the transects.

import arcpy, os, random

arcpy.env.overwriteOutput = 1

outws = r"C:\path\to\your\filegeodatabase.gdb" # Where the output feature class and table will go. This assumes a file geodatabase
polygon = r"C:\path\to\your\filegeodatabase.gdb\utm15\polygon" # The study area polygon (Note this is in a feature dataset)
transect_distance = 50 # The transect distance
n_lines = 20 # How many transects

# Create random points offset from study area boundary by X distance
arcpy.Buffer_analysis(polygon, "in_memory/buffer", -transect_distance) # negative buffer study area to get correct offset
arcpy.CreateRandomPoints_management("in_memory", "rand_points", "in_memory/buffer", "", n_lines)

# Add fields x, y, distance, and bearing

# Run cursor to update attribute table with pertinent data for bearing distance tool
with arcpy.da.UpdateCursor("in_memory/rand_points", ["SHAPE@XY", "x", "y", "distance", "bearing"]) as cursor:
for row in cursor:
row[1] = row[0][0]
row[2] = row[0][1]
row[3] = transect_distance
row[4] = random.randint(1,360)
cursor.updateRow(row)

# Create a table to feed to Bearing Distance to line tool
arcpy.TableToTable_conversion("in_memory/rand_points",outws,"out_table")

# Generate the transects
arcpy.BearingDistanceToLine_management (os.path.join(outws, "out_table"), os.path.join(outws, "transects"), x_field = 'x', y_field = 'y', distance_field = 'distance', bearing_field = 'bearing', spatial_reference = "in_memory/rand_points")

# Clean up
arcpy.Delete_management(os.path.join(outws, "out_table"))

print "Processing complete."


Here's a rough stab with R.

library(sp)
library(raster)
## roughly digitized analog of your map (with raster::drawPoly)

d <- structure(list(x = c(0.08394638623612, 0.0758558910509947, 0.197213318827875,
0.209349061605562, 0.268005151697721, 0.280140894475409, 0.08394638623612,
0.08394638623612, 0.112263119384059, 0.120353614569184, 0.112263119384059,
0.100127376606371, 0.090014257624964, 0.0920368814212453, 0.41767931262254,
0.454086540955604, 0.454086540955604, 0.462177036140729, 0.504652135862637,
0.520833126232887, 0.545104611788263, 0.551172483177108, 0.553195106973389,
0.852543428823026, 0.959742490025936, 0.963787737618499, 0.907154271322622,
0.909176895118903, 0.937493628266842, 0.955697242433374, 0.941538875859404,
0.931425756877998, 0.927380509285435, 0.911199518915184, 0.899063776137496,
0.0859690100324013, 0.08394638623612), y = c(0.0984799434209303,
0.184633290921423, 0.173297324145043, 0.218641191250565, 0.218641191250565,
0.352405599211856, 0.356939985922409, 0.522445100857566, 0.529246680923394,
0.547384227765603, 0.567788967963088, 0.579124934739469, 0.585926514805297,
0.789973916780149, 0.862524104148985, 0.837584977240947, 0.801309883556529,
0.767301983227388, 0.751431629740455, 0.762767596516835, 0.776370756648492,
0.833050590530395, 0.867058490859537, 0.871592877570089, 0.864791297504261,
0.497505973949529, 0.474834040396767, 0.440826140067625, 0.418154206514864,
0.418154206514864, 0.313863312172162, 0.273053831777192, 0.216373997895289,
0.184633290921423, 0.141556617171177, 0.0962127500656542, 0.0984799434209303
)), .Names = c("x", "y"), row.names = c(NA, -37L), class = "data.frame")

## polygon

sp <- raster::spPolygons(cbind(d$x, d$y), attr = data.frame(p = 1))

pts <- spsample(sp, n = 50, type = "random")

## 25 lines
l <- split(as.data.frame(coordinates(pts)), rep(seq(length(pts)/2), each = 2))
lp <- raster::spLines(lapply(l, as.matrix))

op <- par(bg = "black")
plot(sp, border = "yellow", lwd =2 )
plot(lp, add = TRUE, col = "yellow", lty = 2)


• Very nice! Although, the fixed length requirement does not seem to be satisfied.
– Aaron
May 20, 2016 at 1:59
• Ah missed that, easy enough to modify though - perhaps use this start as a way to trim to one end or the other. That runs into distribution problems maybe? Stay tuned May 20, 2016 at 2:14