Вы можете использовать первую точку в качестве смещения, которое будет вычтено из всех точек. Это сдвигает край круга к началу координат. Теперь представьте касательную к окружности (в любой точке, но конкретно мы будем использовать начало координат), тогда окружность полностью лежит на одной стороне касательной. Сам тангенс охватывает 180 градусов, и если мы будем идти по кругу, начиная с начала координат, всегда измеряя угол между последовательными векторами, мы будем измерять в общей сложности 180 градусов, как только вернемся к началу координат (в случае, если точки на круге бесконечно малые расстояния). Это позволяет вычислить накопленную сумму углов и остановиться, когда она достигнет 180 градусов (= пи). Теперь, поскольку на самом деле точки имеют конечный интервал, мы пропустим некоторую часть 180 градусов в начале и в конце круга (относительно начала координат). Это означает, что когда мы достигнем 180 градусов, мы наберем немного больше очков, чем необходимо для замыкания круга; OP указывает, что это желаемое поведение, то есть круг должен быть закрыт (лучше, чтобы некоторые перекрывались, а не закрывались).
Это соответствующий код для алгоритма:
angle = 0
offset = next(generator) # 'generator' produces the data points
points = [next(generator) - offset]
while angle <= pi:
points.append(next(generator) - offset)
angle += np.arccos(
np.dot(points[-2], points[-1]) /
(np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
)
del points[-1] # optionally delete the last point in order to stay below pi
Следующее На графике показан пример, где радиус каждой точки в точности совпадает, меняется только полярный угол:
![Example](https://i.stack.imgur.com/AZycy.png)
Это полный пример кода:
from math import pi
import random
import matplotlib.pyplot as plt
import numpy as np
def generate():
angle = pi/4
angle_upper_lim = 0.002
while True:
angle += 2*pi * random.uniform(0.001, angle_upper_lim)
radius = 1
yield np.array([3 + radius*np.cos(angle), 5 + radius*np.sin(angle)])
angle_upper_lim *= 1.03 # make the circle fill faster
generator = generate()
angle = 0
offset = next(generator) # 'generator' produces the data points
points = [next(generator) - offset]
while angle <= pi:
points.append(next(generator) - offset)
angle += np.arccos(
np.dot(points[-2], points[-1]) /
(np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
)
del points[-1] # optionally delete the last point in order to stay below pi
fig, ax = plt.subplots(figsize=(4.8, 4.8))
ax.scatter(*np.stack(points, axis=1), s=5, c=np.arange(len(points)))
ax.set_title(f'Total angle: {angle/pi:.2f} pi')
ax.plot(*points[ 0], 's', ms=8, label='First point', color='#2ca02c')
ax.plot(*points[-1], '*', ms=12, label='Last point', color='#ff7f0e')
ax.legend()
plt.show()
Изменяющийся радиус
Если, однако, точки данных также имеют изменяющийся радиус, метод страдает от этих изменений, если смотреть с начала координат. В этом случае последовательные точки данных можно сгруппировать, а затем использовать среднее значение для большей стабильности:
![Example with window](https://i.stack.imgur.com/s8ecI.png)
Этот код использует группировку данных очки для большей стабильности:
from math import pi
import random
import matplotlib.pyplot as plt
import numpy as np
def generate():
angle = pi/4
angle_upper_lim = 0.002
while True:
angle += 2*pi * random.uniform(0.001, angle_upper_lim)
radius = random.uniform(0.95, 1.05)
yield np.array([3 + radius*np.cos(angle), 5 + radius*np.sin(angle)])
angle_upper_lim *= 1.03 # make the circle fill faster
generator = generate()
def next_point(n=5):
"""n: number of points per group"""
return sum(next(generator) for __ in range(n)) / n
angle = 0
offset = next(generator) # 'generator' produces the data points
points = [next_point() - offset]
while angle <= pi:
points.append(next_point() - offset)
angle += np.arccos(
np.dot(points[-2], points[-1]) /
(np.linalg.norm(points[-2]) * np.linalg.norm(points[-1]))
)
del points[-1] # optionally delete the last point in order to stay below pi
fig, ax = plt.subplots(figsize=(4.8, 4.8))
ax.scatter(*np.stack(points, axis=1), s=5, c=np.arange(len(points)))
ax.set_title(f'Total angle: {angle/pi:.2f} pi')
ax.plot(*points[ 0], 's', ms=8, label='First point', color='#2ca02c')
ax.plot(*points[-1], '*', ms=12, label='Last point', color='#ff7f0e')
ax.legend(loc='center')
plt.show()