I have 20 points, 10 of them are roughly on an imaginary line, roughly equidistant from each other. This is a simplified version of a real world problem. The following Python script provides a reproducible example.
import matplotlib.pyplot as plt
import geopandas as gpd
import numpy as np
import shapely
np.random.seed(4)
rand = np.random.uniform
xstep=rand(0,1)
ystep=rand(0,1)
points = [(i*xstep,i*ystep) for i in range(10)] # generate 10 points appearing in a sequence
points += [(rand(0,10),rand(0,10)) for _ in range(10)] # add some other points
noise = 0.01
points = [(x+rand(0,noise),y+rand(0,noise)) for (x,y) in points] # add some noise
points = [shapely.geometry.Point(each) for each in points]
gs = gpd.GeoSeries(points)
gs = gs.sample(frac=1).reset_index(drop=True) # reshuffle
ax = gs.plot()
The above script produces this plot:
while gs
is:
0 POINT (8.70724 4.93133)
1 POINT (6.98104 2.16592)
2 POINT (8.63631 9.83592)
3 POINT (9.73452 7.14995)
4 POINT (0.96876 0.54798)
5 POINT (1.94007 1.09614)
6 POINT (9.76961 0.06484)
7 POINT (1.64340 5.97516)
8 POINT (2.53537 4.35416)
9 POINT (4.84037 2.73724)
10 POINT (0.09904 3.87003)
11 POINT (0.00786 0.00866)
12 POINT (7.74025 4.38436)
13 POINT (7.80278 1.98048)
14 POINT (5.80376 3.28885)
15 POINT (6.77445 3.83700)
16 POINT (4.37051 9.49382)
17 POINT (2.90842 1.64578)
18 POINT (0.44990 9.57070)
19 POINT (3.87340 2.19830)
To a human, it is obvious which 10 points are on a line. These 10 orange points:
I would like to find these colinear points in a scriptable way, knowing only the coordinates of the 20 points. I don't need the solution to be using Python at all.
How can I find which points lie on a line?
One possible approach
This option is quite tedious. One can calculate the distance matrix for gs
:
distance_matrix = gs.geometry.apply(lambda g: gs.distance(g))
We can round the results, and observe which values are repated:
import pandas as pd
s = pd.Series(distance_matrix.to_numpy().flatten().round(int(np.abs(np.log10(noise))-1)).flatten())
s[s.gt(0)].value_counts().head(10)
Giving us:
3.3 20
2.2 18
1.1 18
4.4 16
5.6 14
5.5 12
7.1 10
7.8 10
6.7 10
5.0 8
dtype: int64
From here, we can deduce that the distance between the sought after points is roughly 1.1
. We can then select the special 10 points from gs
:
index_of_points_on_line = distance_matrix[distance_matrix.round(int(np.abs(np.log10(noise))-1)).eq(1.1).apply(any)].index
pointsOnLine = gpd.GeoSeries([gs[e] for e in index_of_points_on_line])
ax = gs.plot()
pointsOnLine.plot(ax=ax)
Giving us the plot above with the orange points. (index_of_points_on_line
is Int64Index([0, 4, 5, 9, 11, 12, 14, 15, 17, 19], dtype='int64')
, the index of the 10 on-line points in gs
.)
Motivation
I read this Bellingcat article about geolocation of wind turbines (using this image). I would like to see if there is a way to make these kind of geolocations easier by using the fact that the wind turbines we are after are not just distributed randomly, but are roughly along a straight line.