-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathgeojson-tidy.js
142 lines (109 loc) · 4.33 KB
/
geojson-tidy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
var haversine = require('haversine');
module.exports.tidy = tidy;
function shuffle(arr) {
// Fisher-Yates shuffle
for (let i = arr.length -1; i > 0; i--) {
let j = Math.floor(Math.random() * (i+1));
let k = arr[i];
arr[i] = arr[j];
arr[j] = k;
}
return arr;
}
// Public function
function tidy(geojson, options) {
options = options || {};
// Set the minimum distance in metres and time interval in seconds between successive coordinates
var filter = {
minimumDistance: options.minimumDistance || 0,
minimumTime: options.minimumTime || 0,
maximumPoints: options.maximumPoints || 100
};
// Create the tidy output feature collection
var tidyOutput = {
"type": "FeatureCollection",
"features": []
};
var emptyFeature = {
"type": "Feature",
"properties": {
"coordTimes": [],
},
"geometry": {
"type": "LineString",
"coordinates": []
}
};
// Helper to pass an object by value instead of reference
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
//Loop through input features
for (var featureIndex = 0; featureIndex < geojson.features.length; featureIndex++) {
// Skip non LineString features in the collections
if (geojson.features[featureIndex].geometry.type != 'LineString') {
continue;
}
var lineString = geojson.features[featureIndex].geometry.coordinates,
timeStamp = geojson.features[featureIndex].properties.coordTimes;
tidyOutput.features.push(clone(emptyFeature));
// Loop through the coordinate array of the noisy linestring and build a tidy linestring
var keepIdxs = [];
for (var i = 0; i < lineString.length; i++) {
// Add first and last points
if (i === 0 || i == lineString.length - 1) {
keepIdxs.push(i);
continue;
}
// Calculate distance between this point and the last point we included
var point1 = {
latitude: lineString[keepIdxs[keepIdxs.length - 1]][1],
longitude: lineString[keepIdxs[keepIdxs.length - 1]][0]
};
var point2 = {
latitude: lineString[i][1],
longitude: lineString[i][0]
};
var Dx = haversine(point1, point2, {
unit: 'km'
}) * 1000;
// Skip point if its too close to each other
if (Dx < filter.minimumDistance) {
continue;
}
// Calculate sampling time diference between successive points in seconds
if (timeStamp) {
var time1 = new Date(timeStamp[keepIdxs[keepIdxs.length - 1]]);
var time2 = new Date(timeStamp[i]);
var Tx = (time2 - time1) / 1000;
// Skip point if sampled to close to each other
if (Tx < filter.minimumTime) {
continue;
}
}
keepIdxs.push(i)
}
// If we have > maximumPoints points, take a random sample.
// Otherwise just return the entire set
if (keepIdxs.length > filter.maximumPoints) {
// Randomly remove points until we hit maxLength
// Split off the first and last indices as we always want to keep these
const firstIdx = keepIdxs[0]
const lastIdx = keepIdxs[keepIdxs.length - 1]
keepIdxs = keepIdxs.slice(1, -1)
// Shuffle the array and take the number of points we want
keepIdxs = shuffle(keepIdxs).slice(0, filter.maximumPoints - 2)
// Add back the first/last points
keepIdxs = [firstIdx, ...keepIdxs, lastIdx]
}
// Now that we know which indices we want to keep, save the corresponding points
keepIdxs.forEach(function (item, index) {
tidyOutput.features[tidyOutput.features.length - 1].geometry.coordinates.push(lineString[item]);
if (timeStamp) {
tidyOutput.features[tidyOutput.features.length - 1].properties.coordTimes.push(timeStamp[item]);
}
});
}
// Your tidy geojson is served
return tidyOutput;
}