Skip to content

Commit f93b30e

Browse files
feat: add function for creating trail of coordinates (#176)
Co-authored-by: Markus Tacker <[email protected]>
1 parent b5a6f72 commit f93b30e

File tree

6 files changed

+370
-0
lines changed

6 files changed

+370
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import { createTrailOfCoordinates } from './createTrailOfCoordinates.js'
2+
3+
const coordinates = [
4+
{
5+
lat: 63.422214376965165,
6+
lng: 10.43763831347703,
7+
ts: 1691114567,
8+
},
9+
{
10+
lat: 63.422214376965165,
11+
lng: 10.43763831347703,
12+
ts: 1691114667,
13+
},
14+
{
15+
lat: 63.42161345025134,
16+
lng: 10.436310829032905,
17+
ts: 1691114687,
18+
},
19+
{
20+
lat: 63.42161345025134,
21+
lng: 10.436310829032905,
22+
ts: 1691114807,
23+
},
24+
{
25+
lat: 63.42161345025134,
26+
lng: 10.436310829032905,
27+
ts: 1691114987,
28+
},
29+
{
30+
lat: 63.42161345025134,
31+
lng: 10.436310829032905,
32+
ts: 1691115087,
33+
},
34+
{
35+
lat: 63.42154475460784,
36+
lng: 10.43729416236437,
37+
ts: 1691115187,
38+
},
39+
{
40+
lat: 63.42135082023726,
41+
lng: 10.436949061903944,
42+
ts: 1691115287,
43+
},
44+
45+
{
46+
lat: 63.42130758628152,
47+
lng: 10.436780652879253,
48+
ts: 1691115387,
49+
},
50+
{
51+
lat: 63.42130758628152,
52+
lng: 10.436780652879253,
53+
ts: 1691115487,
54+
},
55+
{
56+
lat: 63.42130758628152,
57+
lng: 10.436780652879253,
58+
ts: 1691115587,
59+
},
60+
{
61+
lat: 63.42130758628152,
62+
lng: 10.436780652879253,
63+
ts: 1691115687,
64+
},
65+
{
66+
lat: 63.42130758628152,
67+
lng: 10.436780652879253,
68+
ts: 1691115787,
69+
},
70+
{
71+
lat: 63.421482406605705,
72+
lng: 10.437975967525364,
73+
ts: 1691115887,
74+
},
75+
{
76+
lat: 63.421482406605705,
77+
lng: 10.437975967525364,
78+
ts: 1691115987,
79+
},
80+
]
81+
82+
describe('createTrailOfCoordinates()', () => {
83+
it('should return an empty list if no coordinate given', () => {
84+
expect(createTrailOfCoordinates(1, [])).toHaveLength(0)
85+
})
86+
it('should return the coordinate if only one coordinate is given', () => {
87+
expect(
88+
createTrailOfCoordinates(1, [
89+
{
90+
lat: 63.422214376965165,
91+
lng: 10.43763831347703,
92+
ts: 1691114567,
93+
},
94+
]),
95+
).toMatchObject([
96+
{
97+
lat: 63.422214376965165,
98+
lng: 10.43763831347703,
99+
ts: 1691114567,
100+
count: 1,
101+
},
102+
])
103+
})
104+
it('should return two coordinates if the distance between them is above 1km', () => {
105+
expect(
106+
createTrailOfCoordinates(1, [
107+
{
108+
lat: 63.422214376965165,
109+
lng: 10.43763831347703,
110+
ts: 1691114567,
111+
},
112+
{
113+
lat: 63.36316007133849,
114+
lng: 10.355729671057269,
115+
ts: 1691114567,
116+
},
117+
]),
118+
).toMatchObject([
119+
{
120+
lat: 63.422214376965165,
121+
lng: 10.43763831347703,
122+
ts: 1691114567,
123+
count: 1,
124+
},
125+
{
126+
lat: 63.36316007133849,
127+
lng: 10.355729671057269,
128+
ts: 1691114567,
129+
count: 1,
130+
},
131+
])
132+
})
133+
it('should only return one counted coordinate if the distance is less than 1km', () => {
134+
expect(
135+
createTrailOfCoordinates(1, [
136+
{
137+
lat: 63.422214376965165,
138+
lng: 10.43763831347703,
139+
ts: 1691114567,
140+
},
141+
{
142+
lat: 63.422214376965165,
143+
lng: 10.43763831347703,
144+
ts: 1691114667,
145+
},
146+
]),
147+
).toMatchObject([
148+
{
149+
lat: 63.422214376965165,
150+
lng: 10.43763831347703,
151+
ts: 1691114567,
152+
count: 2,
153+
},
154+
])
155+
})
156+
it('should return counted coordinates with a distance > 50 meters, and ignore coordinates with smaller distance. Every coordinate should be counted.', () => {
157+
const expectedResults50m = [
158+
{
159+
lat: 63.422214376965165,
160+
lng: 10.43763831347703,
161+
ts: 1691114567,
162+
count: 2,
163+
},
164+
{
165+
lat: 63.42161345025134,
166+
lng: 10.436310829032905,
167+
ts: 1691114687,
168+
count: 11,
169+
},
170+
{
171+
lat: 63.421482406605705,
172+
lng: 10.437975967525364,
173+
ts: 1691115887,
174+
count: 2,
175+
},
176+
]
177+
178+
expect(createTrailOfCoordinates(0.05, coordinates)).toEqual(
179+
expectedResults50m,
180+
)
181+
})
182+
183+
it('should return coordinates with a distance > 1 meter, and ignore coordinates with smaller distance. Every coordinate should be counted.', () => {
184+
const expectedResults1m = [
185+
{
186+
lat: 63.422214376965165,
187+
lng: 10.43763831347703,
188+
ts: 1691114567,
189+
count: 2,
190+
},
191+
{
192+
lat: 63.42161345025134,
193+
lng: 10.436310829032905,
194+
ts: 1691114687,
195+
count: 4,
196+
},
197+
{
198+
lat: 63.42154475460784,
199+
lng: 10.43729416236437,
200+
ts: 1691115187,
201+
count: 1,
202+
},
203+
{
204+
lat: 63.42135082023726,
205+
lng: 10.436949061903944,
206+
ts: 1691115287,
207+
count: 1,
208+
},
209+
{
210+
lat: 63.42130758628152,
211+
lng: 10.436780652879253,
212+
ts: 1691115387,
213+
count: 5,
214+
},
215+
216+
{
217+
lat: 63.421482406605705,
218+
lng: 10.437975967525364,
219+
ts: 1691115887,
220+
count: 2,
221+
},
222+
]
223+
expect(createTrailOfCoordinates(0.0001, coordinates)).toEqual(
224+
expectedResults1m,
225+
)
226+
})
227+
it('should return coordinates with a distance > 1 km, and ignore coordinates with smaller distance. Every coordinates should be counted.', () => {
228+
const expectedResults1Km = [
229+
{
230+
lat: 63.422214376965165,
231+
lng: 10.43763831347703,
232+
ts: 1691114567,
233+
count: 15,
234+
},
235+
]
236+
expect(createTrailOfCoordinates(1, coordinates)).toEqual(expectedResults1Km)
237+
})
238+
it('should return coordinates with a distance > 1 km and count all positions even if the function only gets one coordinate', () => {
239+
const oneCoordinate = [
240+
{
241+
lat: 63.422214376965165,
242+
lng: 10.43763831347703,
243+
ts: 1691114567,
244+
},
245+
{
246+
lat: 63.422214376965165,
247+
lng: 10.43763831347703,
248+
ts: 1691114567,
249+
},
250+
{
251+
lat: 63.422214376965165,
252+
lng: 10.43763831347703,
253+
ts: 1691114567,
254+
},
255+
]
256+
257+
const expectedResults1coordinate = [
258+
{
259+
lat: 63.422214376965165,
260+
lng: 10.43763831347703,
261+
ts: 1691114567,
262+
count: 3,
263+
},
264+
]
265+
expect(createTrailOfCoordinates(1, oneCoordinate)).toEqual(
266+
expectedResults1coordinate,
267+
)
268+
})
269+
})

util/createTrailOfCoordinates.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { getDistanceFromLatLngInKm } from './getDistanceFromLatLngInKm.js'
2+
3+
export type Coordinate = {
4+
lat: number
5+
lng: number
6+
ts: number
7+
}
8+
9+
export type TrailCoordinates = Coordinate & {
10+
count: number
11+
}
12+
13+
export const createTrailOfCoordinates = (
14+
maxDistanceInKm: number,
15+
listOfCoordinates: Coordinate[],
16+
): TrailCoordinates[] => {
17+
const result: TrailCoordinates[] = []
18+
for (const coordinate of listOfCoordinates) {
19+
const prev = result[result.length - 1]
20+
if (prev === undefined) {
21+
result.push({
22+
...coordinate,
23+
count: 1,
24+
})
25+
} else {
26+
if (
27+
getDistanceFromLatLngInKm({ pointA: prev, pointB: coordinate }) >
28+
maxDistanceInKm
29+
) {
30+
result.push({
31+
...coordinate,
32+
count: 1,
33+
})
34+
} else {
35+
prev.count += 1
36+
}
37+
}
38+
}
39+
return result
40+
}

util/deg2rad.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { deg2rad } from './deg2rad.js'
2+
3+
describe('deg2rad()', () => {
4+
it('should convert degrees to radians', () => expect(deg2rad(180)).toEqual(Math.PI))
5+
})

util/deg2rad.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const deg2rad = (deg: number): number => deg * (Math.PI / 180)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { getDistanceFromLatLngInKm } from './getDistanceFromLatLngInKm.js'
2+
3+
describe('getDistanceFromLatLngInKm()', () => {
4+
it('should calculate the distance between two coordinates in km.', () => {
5+
const pointA = {
6+
lat: 63.422214376965165,
7+
lng: 10.43763831347703,
8+
ts: 1691114567,
9+
}
10+
const pointB = {
11+
lat: 59.92117247790821,
12+
lng: 10.688614657210739,
13+
ts: 1691114667,
14+
}
15+
expect(getDistanceFromLatLngInKm({ pointA, pointB })).toEqual(
16+
389.52247455218924,
17+
)
18+
})
19+
it('should return 0 if the coordinate is the same', () => {
20+
const pointA = {
21+
lat: 63.422214376965165,
22+
lng: 10.43763831347703,
23+
ts: 1691114567,
24+
}
25+
const pointB = {
26+
lat: 63.422214376965165,
27+
lng: 10.43763831347703,
28+
ts: 1691114577,
29+
}
30+
expect(getDistanceFromLatLngInKm({ pointA, pointB })).toEqual(0)
31+
})
32+
})

util/getDistanceFromLatLngInKm.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Coordinate } from './createTrailOfCoordinates.js'
2+
import { deg2rad } from './deg2rad.js'
3+
4+
export const getDistanceFromLatLngInKm = ({
5+
pointA,
6+
pointB,
7+
}: {
8+
pointA: Coordinate
9+
pointB: Coordinate
10+
}): number => {
11+
const earthRadius = 6371
12+
const distLat = deg2rad(pointB.lat - pointA.lat)
13+
const distLng = deg2rad(pointB.lng - pointA.lng)
14+
const a =
15+
Math.sin(distLat / 2) * Math.sin(distLat / 2) +
16+
Math.cos(deg2rad(pointA.lat)) *
17+
Math.cos(deg2rad(pointB.lat)) *
18+
Math.sin(distLng / 2) *
19+
Math.sin(distLng / 2)
20+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
21+
const dist = earthRadius * c
22+
return dist
23+
}

0 commit comments

Comments
 (0)