An arcpy solution for randomizing overlapping points within a shape? - python

I have a shapefile layer with polygons for the 50 U.S. states ("States"). I also have a point featureclass with potentially many overlapping points at the centroid of each state polygon ("Dots").
I want to use the CreateRandomPoints_management() function to find new placements for all the points in each state, essentially creating a 1:1 dot density map with clickable point features.
Here's the workflow I have envisioned:
Loop through and select each state, one by one
Get the count of points contained within each state polygon boundary
Use CreateRandomPoints_management to generate count number of randomized points within
each state boundary
Move the original points to the new random locations and then delete the random points layer
-or- Add fields and assign values to the newly created random points layer from the original points [Whichever's simpler and more reliable]
I've tried several times to write this code, but my arcpy chops aren't that great yet. Any ideas or examples would be greatly appreciated!

Someone shared the following code, which is a more direct solution to my problem:
#-------------------------------------------------------------------------------
# Name: Disperse3.py
# Purpose: Disperse points in multiple polygons
# Author: arcpy Team
# http://arcpy.wordpress.com/2013/06/07/disperse-overlapping-points/
# Created: 02-dec-2013
#-------------------------------------------------------------------------------
import arcpy
import random
def main():
fcPoints = r"C:\Project\_Forums\Disperse\test.gdb\points3"
fcPolygon = r"C:\Project\_Forums\Disperse\test.gdb\Polygons"
arcpy.env.overwriteOutput = True
with arcpy.da.SearchCursor(fcPolygon, ("SHAPE#")) as cursor:
for row in cursor:
polygon = row[0]
disperse_points(fcPoints, polygon)
del row
print "ready..."
def point_in_poly(poly, x, y):
pg = arcpy.PointGeometry(arcpy.Point(x, y), poly.spatialReference)
return poly.contains(pg)
def disperse_points(in_points, polygon):
lenx = polygon.extent.width
leny = polygon.extent.height
with arcpy.da.UpdateCursor(in_points, "SHAPE#XY") as points:
for p in points:
if point_in_poly(polygon, p[0][0], p[0][1]):
# I changed code here!
x = (random.random() * lenx) + polygon.extent.XMin
y = (random.random() * leny) + polygon.extent.YMin
inside = point_in_poly(polygon, x, y)
while not inside:
x = (random.random() * lenx) + polygon.extent.XMin
y = (random.random() * leny) + polygon.extent.YMin
inside = point_in_poly(polygon, x, y)
points.updateRow([(x, y)])
else:
pass # don't update location if point doesn't originally falls inside current polygon
if __name__ == '__main__':
main()

Related

Create a mesh with boundary markers in Python, Fenics

My aim is to create a mesh in Python and setting markers on some 1-dimensional subset.
Up until now, I have always creates a set, for example a rectangle in gmsh, then put for example a circle in it. Then gmsh puts a mesh on my structure and I can mark the boundary of the rectangle and of the circle as facets (as xdmf files). I can then let Python read my mesh and boundary facets and using it, f.e. to solve PDEs.
What I want to do now is the following: I still want to have my rectangle with a mesh, but instead of defining the facet markes in gmsh, I want to define them using the image of a function.
More precicely: Instead of creating a circle in gmsh, I want to consider, for example, the function
Then I want to use as my facet and mark it in my mesh.
Is there a way to do this? I feel kind of lost here.
I am not sure if I understood everything in your question correctly, but here is some code that might help you. One thing I do not know is how to add element edges to physical groups in gmsh. But maybe you can figure that out.
So here is the code:
import gmsh
import numpy as np
def mapping(point):
x = point[0]
y = point[1]
z = point[2]
result = [2*x,3*y,z]
return result
def inverseMapping(point):
x = point[0]
y = point[1]
z = point[2]
result = [(1/2)*x,(1/3)*y,z]
return result
def getNodes():
nodeTags, nodeCoord, _ = gmsh.model.mesh.getNodes()
nodeCoord = np.reshape(nodeCoord,(len(nodeTags),3))
return nodeTags, nodeCoord
def getEdgeNodeCoordinates():
edgeTags, edgeNodes = gmsh.model.mesh.getAllEdges()
edgeNodes = np.reshape(edgeNodes,(len(edgeTags),2))
nodeTags, nodeCoord = getNodes()
coord = []
for i in range(0,len(edgeTags)):
tagNode1 = edgeNodes[i][0]
tagNode2 = edgeNodes[i][1]
nodeIndex1 = list(nodeTags).index(tagNode1)
nodeIndex2 = list(nodeTags).index(tagNode2)
nodeCoord1 = nodeCoord[nodeIndex1]
nodeCoord2 = nodeCoord[nodeIndex2]
coord.append([nodeCoord1,nodeCoord2])
return edgeTags, edgeNodes, nodeTags, coord
def getInverseNodeCoordinates(edgeNodeCoordinates):
coord = []
for edgeNodes in edgeNodeCoordinates:
nodeCoord1 = edgeNodes[0]
nodeCoord2 = edgeNodes[1]
newCoord1 = inverseMapping(nodeCoord1)
newCoord2 = inverseMapping(nodeCoord2)
coord.append([newCoord1, newCoord2])
return coord
def checkIntersection(edgeTags, edgeNodeCoordinates, inverseCoordinates):
intersectingEdgeTags = []
intersectingEdgeNodeCoord = []
# 1 = inside, 0 = outside
for i in range(0,len(inverseCoordinates)):
pair = inverseCoordinates[i]
coord1 = pair[0]
coord2 = pair[1]
e1 = 1 if np.linalg.norm(coord1) <= 1 else 0
e2 = 1 if np.linalg.norm(coord2) <= 1 else 0
s = e1 + e2 # s = 0 --> both nodes outside of manifold
# s = 1 --> one node inside and one node outside of manifold
# s = 2 --> both nodes inside of manifold
if s == 1:
intersectingEdgeTags.append(edgeTags[i])
intersectingEdgeNodeCoord.append(edgeNodeCoordinates[i])
return intersectingEdgeTags, intersectingEdgeNodeCoord
def visualizeEdges(intersectingEdgeNodeCoord):
for pair in intersectingEdgeNodeCoord:
p1 = pair[0]
p2 = pair[1]
t1 = gmsh.model.occ.addPoint(p1[0],p1[1],p1[2])
t2 = gmsh.model.occ.addPoint(p2[0],p2[1],p2[2])
line = gmsh.model.occ.addLine(t1, t2)
gmsh.model.occ.synchronize()
gmsh.model.setColor([(1,line)], 255, 0, 0)
gmsh.model.occ.synchronize()
gmsh.initialize()
# Create a rectangle which will be meshed later.
tag_vol_1 = gmsh.model.occ.addRectangle(-3, -4, 0, 6, 8)
# Sample the S1 manifold with n_points
S1_sampling_points = []
n_points = 100
maxAngle = 2*np.pi
angle = maxAngle/n_points
z = 0
for i in range(0,n_points):
x = np.cos(i*angle)
y = np.sin(i*angle)
S1_sampling_points.append([x,y,z])
# Map the sampled S1 points to 2*x, 3*y, z.
# This is only for "visualization" via a spline.
mappedPoints = []
mappedPointTags = []
for point in S1_sampling_points:
mappedPoint = mapping(point)
tag = gmsh.model.occ.addPoint(mappedPoint[0], mappedPoint[1], mappedPoint[2])
mappedPointTags.append(tag)
# Here the spline fitting is performed
# You will see it visualized when gmsh opens.
tagMappedS1 = gmsh.model.occ.addSpline(mappedPointTags + [mappedPointTags[0]]) # make the spline periodic by setting the last point equal to the first one
gmsh.model.occ.synchronize()
# Mesh the rectangle and tell gmsh to create edges which we can access.
gmsh.model.mesh.generate(2)
gmsh.model.mesh.createEdges() # You need to call this before using gmsh.model.mesh.getAllEdges()
# Get all these self-explanatory things
edgeTags, edgeNodes, nodeTags, edgeNodeCoordinates = getEdgeNodeCoordinates()
# Calculate the inverse-mapped coordinates of all nodes.
# With this we can just check if the transformed nodes are inside a circle of radius 1 or outside.
# If for every egde one node is inside, and the other node is outside the circle, then the edge is
# intersected by the mapped manifold f(S1) --> (2*x, 3*y, z). We then save the tag of such an edge.
inverseCoordinates = getInverseNodeCoordinates(edgeNodeCoordinates)
intersectingEdgeTags, intersectingEdgeNodeCoord = checkIntersection(edgeTags, edgeNodeCoordinates, inverseCoordinates)
# Here all intersecting edges are created within gmsh so you can see it.
# This is for visualization only. A lot of nodes with the same coordinates are created here twice.
visualizeEdges(intersectingEdgeNodeCoord)
gmsh.fltk.run()
gmsh.finalize()
And the result in gmsh looks like this:

Return list of points (x-y coordinates) that one point is connected to, and check against a separate status list

I have two tuples in Python "lines_origin" and "lines_dest" generated in a probably very inefficient way! But they show connections: lines_origin[1] is connected to lines_dest[2] and so on.
What I need to do, is iterate through a separate 'master' list of all possible coordinates individually, return the points its connected to, then check that sublist of connections against another list of statuses (on/off). Here's my attempt thus far:
import random
import numpy as np
### my list of all points
coordinates = []
for x in range(10):
for y in range(20):
coordinates.append((x,y))
### the connections between points
lines = []
while len(lines) < (800): # limiting each point to an average of 4 connections
rnd_pair = random.sample(coordinates, 2)
rnd_pair_rev = [rnd_pair[1],rnd_pair[0]]
rnd_pair_x = [x for x,y in rnd_pair]
rnd_pair_y = [y for x,y in rnd_pair]
distance = abs(rnd_pair_x[0] - rnd_pair_x[1]) + abs(rnd_pair_y[0] - rnd_pair_y[1])
if distance <=2:
if rnd_pair not in lines and rnd_pair_rev not in lines:
lines.append(rnd_pair)
else:
rnd_pair = []
rnd_pair_rev = []
rnd_pair_x = []
rnd_pair_y = []
### splitting the connections into two tuples
lines_origin, lines_dest = zip(*lines)
### an array of binary statuses, these line up to coordinates by index (so if
# states[6] = "ON" then this denotes that coordinates[6] is on.
states = np.random.choice(["ON", "OFF"], size = len(coordinates), replace=True, p=[0.2, 0.8])
### my failed attempt to iterate over the coordinates
for i in coordinates:
if coordinates[i] in lines_origin:
connections = lines_dest # this should return the coordinates of the points that coordinates[i]
# is connected to
if coordinates[i] in lines_dest:
connections.append(lines_origin) # as above but checking the destination points
list(set(connections)) # dedupe the connections
### check whether a point is connected to an "ON" point
for point in connections:
return states[coordinates.index(connections[point])]

How to use vtkOBBTree and IntersectWithLine to find the intersection of a line and a PolyDataFilter in python using vtk?

I have an oriented cylinder generated with vtkCylinderSource and some transformations are applied on it to get the orientation that i want. Here is the code for creating this oriented-cylinder:
def cylinder_object(startPoint, endPoint, radius, my_color="DarkRed", opacity=1):
colors = vtk.vtkNamedColors()
# Create a cylinder.
# Cylinder height vector is (0,1,0).
# Cylinder center is in the middle of the cylinder
cylinderSource = vtk.vtkCylinderSource()
cylinderSource.SetRadius(radius)
cylinderSource.SetResolution(50)
# Generate a random start and end point
# startPoint = [0] * 3
# endPoint = [0] * 3
rng = vtk.vtkMinimalStandardRandomSequence()
rng.SetSeed(8775070) # For testing.8775070
# Compute a basis
normalizedX = [0] * 3
normalizedY = [0] * 3
normalizedZ = [0] * 3
# The X axis is a vector from start to end
vtk.vtkMath.Subtract(endPoint, startPoint, normalizedX)
length = vtk.vtkMath.Norm(normalizedX)
vtk.vtkMath.Normalize(normalizedX)
# The Z axis is an arbitrary vector cross X
arbitrary = [0] * 3
for i in range(0, 3):
rng.Next()
arbitrary[i] = rng.GetRangeValue(-10, 10)
vtk.vtkMath.Cross(normalizedX, arbitrary, normalizedZ)
vtk.vtkMath.Normalize(normalizedZ)
# The Y axis is Z cross X
vtk.vtkMath.Cross(normalizedZ, normalizedX, normalizedY)
matrix = vtk.vtkMatrix4x4()
# Create the direction cosine matrix
matrix.Identity()
for i in range(0, 3):
matrix.SetElement(i, 0, normalizedX[i])
matrix.SetElement(i, 1, normalizedY[i])
matrix.SetElement(i, 2, normalizedZ[i])
# Apply the transforms
transform = vtk.vtkTransform()
transform.Translate(startPoint) # translate to starting point
transform.Concatenate(matrix) # apply direction cosines
transform.RotateZ(-90.0) # align cylinder to x axis
transform.Scale(1.0, length, 1.0) # scale along the height vector
transform.Translate(0, .5, 0) # translate to start of cylinder
# Transform the polydata
transformPD = vtk.vtkTransformPolyDataFilter()
transformPD.SetTransform(transform)
transformPD.SetInputConnection(cylinderSource.GetOutputPort())
cylinderSource.Update()
# Create a mapper and actor for the arrow
mapper = vtk.vtkPolyDataMapper()
actor = vtk.vtkActor()
if USER_MATRIX:
mapper.SetInputConnection(cylinderSource.GetOutputPort())
actor.SetUserMatrix(transform.GetMatrix())
else:
mapper.SetInputConnection(transformPD.GetOutputPort())
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d(my_color))
actor.GetProperty().SetOpacity(opacity)
return actor, transformPD
Now i want to ray cast a line with this oriented cylinder. unfortunately, using the vtkCylinderSource as the dataset for vtkOBBTree produces the wrong points as the result. how can i use ray-casting with a PolyDataFilter?
I came up with a solution where i export my oriented-cylinder to a .stl file and then read it again to implement the ray-casting algorithm using IntersectWithLine. The problem is i have thousands of these oriented-cylinders and this method (exporting and reading) makes my code extremely slow.
def ray_cast(filename, p_source, p_target):
'''
:param filename: STL file to perform ray casting on.
:param p_source: first point
:param p_target: second point
:return: code --> 0 : No intersection.
:return: code --> +1 : p_source lies OUTSIDE the closed surface.
:return; code --> -1 : p_source lies INSIDE closed surface
'''
reader = vtk.vtkSTLReader()
reader.SetFileName(filename)
reader.Update()
mesh = reader.GetOutput()
obbtree = vtk.vtkOBBTree()
obbtree.SetDataSet(mesh)
obbtree.BuildLocator()
pointsVTKIntersection = vtk.vtkPoints()
code = obbtree.IntersectWithLine(p_source, p_target, pointsVTKIntersection, None)
# Extracting data
pointsVTKIntersectionData = pointsVTKIntersection.GetData()
noPointsVTKIntersection = pointsVTKIntersectionData.GetNumberOfTuples()
pointsIntersection = []
for idx in range(noPointsVTKIntersection):
_tup = pointsVTKIntersectionData.GetTuple3(idx)
pointsIntersection.append(_tup)
return code, pointsIntersection, noPointsVTKIntersection
Below image shows the desired result using export-stl method. (the green spheres are intersection points)
I would appreciate any suggestion and help..
With vedo:
from vedo import *
cyl = Cylinder() # vtkActor
cyl.alpha(0.5).pos(3,3,3).orientation([2,1,1])
p1, p2 = (0,0,0), (4,4,5)
ipts_coords = cyl.intersectWithLine(p1, p2)
print('hit coords are', ipts_coords)
pts = Points(ipts_coords, r=10).color("yellow")
# print(pts.polydata()) # is the vtkPolyData object
origin = Point()
ln = Line(p1,p2)
show(origin, cyl, ln, pts, axes=True)

Pythonocc/Opencascade | Create pipe along straight lines through points, profile wont change normal

My ultimate goal is as follows:
I have a huge data set of points, representing how a part will be 3D printed layer by layer. I need to create a line through these points and extrude a circle along this line (so rebuild the part as it will be printed later).
I initially tried doing a spline, however this attempts to create a smooth line and does not follow the points at all. I attempted changing the minDeg and maxDeg options but this still didn't help enough to create the actual curve I need.
See this result for the spline
See here the actual path (the above spline is one of the infill parts)
So I have attempted creating a spline between just two points at a time, then when creating a wire add them all together. This looks promising as now I do get actual sharp corners and the lines going through the exact points. However, now when I try to extrude along it the normal of the extruded profile does not change with the angle of wire.
This is what happens with the last thing I tried
I have spent my last 4 days on this problem, tried many forums and questions but feel completely lost in the world of pythonocc (opencascade).
My code reads as follows:
from __future__ import print_function
from OCC.gp import gp_Pnt, gp_Ax2, gp_Dir, gp_Circ
from OCC.GeomAPI import GeomAPI_PointsToBSpline
from OCC.TColgp import TColgp_Array1OfPnt
from OCC.BRepBuilderAPI import BRepBuilderAPI_MakeEdge,
BRepBuilderAPI_MakeWire, BRepBuilderAPI_MakeFace
from OCC.BRepOffsetAPI import BRepOffsetAPI_MakePipe
from OCC.Display.SimpleGui import init_display
display, start_display, add_menu, add_function_to_menu = init_display()
def pipe():
# the bspline path, must be a wire
# This will later be in a for loop but this is merely to validate the method
using three different points.
array = TColgp_Array1OfPnt(1,2)
makeWire = BRepBuilderAPI_MakeWire()
point1 = gp_Pnt(0,0,0)
point2 = gp_Pnt(0,0,1)
array.SetValue(1, point1)
array.SetValue(2, point2)
spline = GeomAPI_PointsToBSpline(array).Curve()
edge = BRepBuilderAPI_MakeEdge(spline).Edge()
makeWire.Add(edge)
point1 = gp_Pnt(0, 0, 1)
point2 = gp_Pnt(0, 1, 2)
array.SetValue(1, point1)
array.SetValue(2, point2)
spline = GeomAPI_PointsToBSpline(array).Curve()
edge = BRepBuilderAPI_MakeEdge(spline).Edge()
makeWire.Add(edge)
point1 = gp_Pnt(0, 1, 2)
point2 = gp_Pnt(0, 2, 2)
array.SetValue(1, point1)
array.SetValue(2, point2)
spline = GeomAPI_PointsToBSpline(array).Curve()
edge = BRepBuilderAPI_MakeEdge(spline).Edge()
makeWire.Add(edge)
makeWire.Build()
wire = makeWire.Wire()
# the bspline profile. Profile mist be a wire/face
point = gp_Pnt(0,0,0)
dir = gp_Dir(0,0,1)
circle = gp_Circ(gp_Ax2(point,dir), 0.2)
profile_edge = BRepBuilderAPI_MakeEdge(circle).Edge()
profile_wire = BRepBuilderAPI_MakeWire(profile_edge).Wire()
profile_face = BRepBuilderAPI_MakeFace(profile_wire).Face()
# pipe
pipe = BRepOffsetAPI_MakePipe(wire, profile_face).Shape()
display.DisplayShape(profile_edge, update=False)
display.DisplayShape(wire, update=True)
display.DisplayShape(pipe, update=True)
if __name__ == '__main__':
pipe()
start_display()
Though the edges of the wire connect, they don’t transition smoothly. BrepOffsetAPI_MakePipe:
Constructs a pipe by sweeping the shape Profile along the wire Spine. The angle made by the spine with the profile is maintained along the length of the pipe. Warning Spine must be G1 continuous; that is, on the connection vertex of two edges of the wire, the tangent vectors on the left and on the right must have the same direction, though not necessarily the same magnitude.
Another continuity description can be found here, we need tangency (G1). If two adjacent curves aren’t tangent at the ends, the sweep won’t be able to maintain the same angle (made by the spine with the profile).
The easiest solution is to cut the pipe.
def pipe(point1, point2):
makeWire = BRepBuilderAPI_MakeWire()
edge = BRepBuilderAPI_MakeEdge(point1, point2).Edge()
makeWire.Add(edge)
makeWire.Build()
wire = makeWire.Wire()
dir = gp_Dir(point2.X() - point1.X(), point2.Y() - point1.Y(), point2.Z() - point1.Z())
circle = gp_Circ(gp_Ax2(point1,dir), 0.2)
profile_edge = BRepBuilderAPI_MakeEdge(circle).Edge()
profile_wire = BRepBuilderAPI_MakeWire(profile_edge).Wire()
profile_face = BRepBuilderAPI_MakeFace(profile_wire).Face()
pipe = BRepOffsetAPI_MakePipe(wire, profile_face).Shape()
display.DisplayShape(pipe, update=True)
if __name__ == '__main__':
pipe(gp_Pnt(0,0,0), gp_Pnt(0,0,1))
pipe(gp_Pnt(0,0,1), gp_Pnt(0,1,2))
pipe(gp_Pnt(0,1,2), gp_Pnt(0,2,2))
start_display()
We can add spheres to bridge the gaps.
from OCC.BRepPrimAPI import BRepPrimAPI_MakeSphere
def sphere(centre, radius):
sphere = BRepPrimAPI_MakeSphere (centre, radius).Shape()
display.DisplayShape(sphere, update=True)
def pipe(point1, point2):
...
if __name__ == '__main__':
pipe(gp_Pnt(0,0,0), gp_Pnt(0,0,1))
sphere(gp_Pnt(0,0,1), 0.2)
pipe(gp_Pnt(0,0,1), gp_Pnt(0,1,2))
sphere(gp_Pnt(0,1,2), 0.2)
pipe(gp_Pnt(0,1,2), gp_Pnt(0,2,2))
start_display()
Alternatively, you could implement a fillet algorithm, such as those provided by the ChFi2d Class. Given the laser printing context and the planar character of the algorithm, I’ve mapped the points to the xy-plane.
from OCC.ChFi2d import ChFi2d_AnaFilletAlgo
def filletEdges(ed1, ed2):
radius = 0.3
f = ChFi2d_AnaFilletAlgo()
f.Init(ed1,ed2,gp_Pln())
f.Perform(radius)
return f.Result(ed1, ed2)
def pipe():
# the points
p1 = gp_Pnt(0,0,0)
p2 = gp_Pnt(0,1,0)
p3 = gp_Pnt(1,2,0)
p4 = gp_Pnt(2,2,0)
# the edges
ed1 = BRepBuilderAPI_MakeEdge(p1,p2).Edge()
ed2 = BRepBuilderAPI_MakeEdge(p2,p3).Edge()
ed3 = BRepBuilderAPI_MakeEdge(p3,p4).Edge()
# inbetween
fillet12 = filletEdges(ed1, ed2)
fillet23 = filletEdges(ed2, ed3)
# the wire
makeWire = BRepBuilderAPI_MakeWire()
makeWire.Add(ed1)
makeWire.Add(fillet12)
makeWire.Add(ed2)
makeWire.Add(fillet23)
makeWire.Add(ed3)
makeWire.Build()
wire = makeWire.Wire()
# the pipe
dir = gp_Dir(0,1,0)
circle = gp_Circ(gp_Ax2(p1,dir), 0.2)
profile_edge = BRepBuilderAPI_MakeEdge(circle).Edge()
profile_wire = BRepBuilderAPI_MakeWire(profile_edge).Wire()
profile_face = BRepBuilderAPI_MakeFace(profile_wire).Face()
pipe = BRepOffsetAPI_MakePipe(wire, profile_face).Shape()
display.DisplayShape(pipe, update=True)
if __name__ == '__main__':
pipe()
start_display()

sort points in order to have a continuous curve using python

I have a list of unsorted points:
List = [(-50.6261, 74.3683), (-63.2489, 75.0038), (-76.0384, 75.6219), (-79.8451, 75.7855), (-30.9626, 168.085), (-27.381, 170.967), (-22.9191, 172.928), (-16.5869, 173.087), (-4.813, 172.505), (-109.056, 92.0063), (-96.0705, 91.4232), (-83.255, 90.8563), (-80.7807, 90.7498), (-54.1694, 89.5087), (-41.6419, 88.9191), (-32.527, 88.7737), (-27.6403, 91.0134), (-22.3035, 95.141), (-18.0168, 100.473), (-15.3918, 105.542), (-13.6401, 112.373), (-13.3475, 118.988), (-14.4509, 125.238), (-17.1246, 131.895), (-21.6766, 139.821), (-28.5735, 149.98), (-33.395, 156.344), (-114.702, 83.9644), (-114.964, 87.4599), (-114.328, 89.8325), (-112.314, 91.6144), (-109.546, 92.0209), (-67.9644, 90.179), (-55.2013, 89.5624), (-34.4271, 158.876), (-34.6987, 161.896), (-33.6055, 164.993), (-87.0365, 75.9683), (-99.8007, 76.0889), (-105.291, 76.5448), (-109.558, 77.3525), (-112.516, 79.2509), (-113.972, 81.3335), (2.30014, 171.635), (4.40918, 169.691), (5.07165, 166.974), (5.34843, 163.817), (5.30879, 161.798), (-29.6746, 73.5082), (-42.5876, 74.0206)]
I want to sort those points to have a continuous curve passing by every point just once, starting from start = (-29.6746, 73.5082)
and end = (5.30879, 161.798)
This is what I tried so far:
import matplotlib.pyplot as plt
import numpy as np
from sklearn.neighbors import NearestNeighbors
import networkx as nx
for el in List:
X.append(el[0])
Y.append(el[1])
x = np.array(X)
y = np.array(Y)
points = np.c_[x, y]
# find 2 nearest neighbors
clf = NearestNeighbors(2).fit(points)
G = clf.kneighbors_graph()
T = nx.from_scipy_sparse_matrix(G)
# indexes of the new order
order = list(nx.dfs_preorder_nodes(T, 0))
# sorted arrays
new_x = x[order]
new_y = y[order]
plt.plot(new_x, new_y)
plt.show()
But I still get an unsorted list, and I couldn't find a way to determine the start point and end point.
We can see the problem as a Traveling salesman problem, that we can optimize by looking for the nearest point
def distance(P1, P2):
"""
This function computes the distance between 2 points defined by
P1 = (x1,y1) and P2 = (x2,y2)
"""
return ((P1[0] - P2[0])**2 + (P1[1] - P2[1])**2) ** 0.5
def optimized_path(coords, start=None):
"""
This function finds the nearest point to a point
coords should be a list in this format coords = [ [x1, y1], [x2, y2] , ...]
"""
if start is None:
start = coords[0]
pass_by = coords
path = [start]
pass_by.remove(start)
while pass_by:
nearest = min(pass_by, key=lambda x: distance(path[-1], x))
path.append(nearest)
pass_by.remove(nearest)
return path
# define a start point
start = [x0, y0]
path = optimized_path(List,start)
Not an answer, but too much for a comment
I plotted the data points as scatter and line
I see a visually smooth (low order local derivatve spline curve) with ~10% points 'out of order'
Is this typical of the problem?, is the data mostly in order?
How general or specific does the code have to be
I don't know the "big hammer" libs, but cleaned up the surounding code and did the same plot
List = [(-50.6261, 74.3683), (-63.2489, 75.0038), (-76.0384, 75.6219), (-79.8451, 75.7855), (-30.9626, 168.085), (-27.381, 170.967), (-22.9191, 172.928), (-16.5869, 173.087), (-4.813, 172.505), (-109.056, 92.0063), (-96.0705, 91.4232), (-83.255, 90.8563), (-80.7807, 90.7498), (-54.1694, 89.5087), (-41.6419, 88.9191), (-32.527, 88.7737), (-27.6403, 91.0134), (-22.3035, 95.141), (-18.0168, 100.473), (-15.3918, 105.542), (-13.6401, 112.373), (-13.3475, 118.988), (-14.4509, 125.238), (-17.1246, 131.895), (-21.6766, 139.821), (-28.5735, 149.98), (-33.395, 156.344), (-114.702, 83.9644), (-114.964, 87.4599), (-114.328, 89.8325), (-112.314, 91.6144), (-109.546, 92.0209), (-67.9644, 90.179), (-55.2013, 89.5624), (-34.4271, 158.876), (-34.6987, 161.896), (-33.6055, 164.993), (-87.0365, 75.9683), (-99.8007, 76.0889), (-105.291, 76.5448), (-109.558, 77.3525), (-112.516, 79.2509), (-113.972, 81.3335), (2.30014, 171.635), (4.40918, 169.691), (5.07165, 166.974), (5.34843, 163.817), (5.30879, 161.798), (-29.6746, 73.5082), (-42.5876, 74.0206)]
import matplotlib.pyplot as plt
import numpy as np
from sklearn.neighbors import NearestNeighbors
import networkx as nx
points = np.asarray(List)
# find 2 nearest neighbors
clf = NearestNeighbors(2).fit(points)
G = clf.kneighbors_graph()
T = nx.from_scipy_sparse_matrix(G)
# indexes of the new order
order = list(nx.dfs_preorder_nodes(T, 0))
# sorted arrays
new_points = points[order]
plt.scatter(*zip(*points))
plt.plot(*zip(*new_points), 'r')
plt.show()

Categories

Resources