-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathindex.js
196 lines (171 loc) · 6.18 KB
/
index.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Find self-intersections in geojson polygon (possibly with interior rings)
var rbush = require('rbush');
var merge = function(){
var output = {};
Array.prototype.slice.call(arguments).forEach(function(arg){
if(arg){
Object.keys(arg).forEach(function(key){
output[key]=arg[key];
});
}
});
return output;
};
var defaults = {
useSpatialIndex: true,
epsilon: 0,
reportVertexOnVertex: false,
reportVertexOnEdge: false
};
module.exports = function(feature, filterFn, options0) {
var options;
if("object" === typeof options0){
options = merge(defaults,options0);
} else {
options = merge(defaults,{useSpatialIndex:options0});
}
if (feature.geometry.type != "Polygon") throw new Error("The input feature must be a Polygon");
var coord = feature.geometry.coordinates;
var output = [];
var seen = {};
if (options.useSpatialIndex) {
var allEdgesAsRbushTreeItems = [];
for (var ring0 = 0; ring0 < coord.length; ring0++) {
for (var edge0 = 0; edge0 < coord[ring0].length-1; edge0++) {
allEdgesAsRbushTreeItems.push(rbushTreeItem(ring0, edge0))
}
}
var tree = rbush();
tree.load(allEdgesAsRbushTreeItems);
}
for (var ring0 = 0; ring0 < coord.length; ring0++) {
for (var edge0 = 0; edge0 < coord[ring0].length-1; edge0++) {
if (options.useSpatialIndex) {
var bboxOverlaps = tree.search(rbushTreeItem(ring0, edge0));
bboxOverlaps.forEach(function(bboxIsect) {
var ring1 = bboxIsect.ring;
var edge1 = bboxIsect.edge;
ifIsectAddToOutput(ring0, edge0, ring1, edge1);
});
}
else {
for (var ring1 = 0; ring1 < coord.length; ring1++) {
for (var edge1 = 0 ; edge1 < coord[ring1].length-1; edge1++) {
// TODO: speedup possible if only interested in unique: start last two loops at ring0 and edge0+1
ifIsectAddToOutput(ring0, edge0, ring1, edge1);
}
}
}
}
}
if (!filterFn) output = {type: "Feature", geometry: {type: "MultiPoint", coordinates: output}};
return output;
// true if frac is (almost) 1.0 or 0.0
function isBoundaryCase(frac){
var e2 = options.epsilon * options.epsilon;
return e2 >= (frac-1)*(frac-1) || e2 >= frac*frac;
}
function isOutside(frac){
return frac < 0 - options.epsilon || frac > 1 + options.epsilon;
}
// Function to check if two edges intersect and add the intersection to the output
function ifIsectAddToOutput(ring0, edge0, ring1, edge1) {
var start0 = coord[ring0][edge0];
var end0 = coord[ring0][edge0+1];
var start1 = coord[ring1][edge1];
var end1 = coord[ring1][edge1+1];
var isect = intersect(start0, end0, start1, end1);
if (isect == null) return; // discard parallels and coincidence
frac0, frac1;
if (end0[0] != start0[0]) {
var frac0 = (isect[0]-start0[0])/(end0[0]-start0[0]);
} else {
var frac0 = (isect[1]-start0[1])/(end0[1]-start0[1]);
};
if (end1[0] != start1[0]) {
var frac1 = (isect[0]-start1[0])/(end1[0]-start1[0]);
} else {
var frac1 = (isect[1]-start1[1])/(end1[1]-start1[1]);
};
// There are roughly three cases we need to deal with.
// 1. If at least one of the fracs lies outside [0,1], there is no intersection.
if (isOutside(frac0) || isOutside(frac1)) {
return; // require segment intersection
}
// 2. If both are either exactly 0 or exactly 1, this is not an intersection but just
// two edge segments sharing a common vertex.
if (isBoundaryCase(frac0) && isBoundaryCase(frac1)){
if(! options.reportVertexOnVertex) return;
}
// 3. If only one of the fractions is exactly 0 or 1, this is
// a vertex-on-edge situation.
if (isBoundaryCase(frac0) || isBoundaryCase(frac1)){
if(! options.reportVertexOnEdge) return;
}
var key = isect;
var unique = !seen[key];
if (unique) {
seen[key] = true;
}
if (filterFn) {
output.push(filterFn(isect, ring0, edge0, start0, end0, frac0, ring1, edge1, start1, end1, frac1, unique));
} else {
output.push(isect);
}
}
// Function to return a rbush tree item given an ring and edge number
function rbushTreeItem(ring, edge) {
var start = coord[ring][edge];
var end = coord[ring][edge+1];
if (start[0] < end[0]) {
var minX = start[0], maxX = end[0];
} else {
var minX = end[0], maxX = start[0];
};
if (start[1] < end[1]) {
var minY = start[1], maxY = end[1];
} else {
var minY = end[1], maxY = start[1];
}
return {minX: minX, minY: minY, maxX: maxX, maxY: maxY, ring: ring, edge: edge};
}
}
// Function to compute where two lines (not segments) intersect. From https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
function intersect(start0, end0, start1, end1) {
if (equalArrays(start0,start1) || equalArrays(start0,end1) || equalArrays(end0,start1) || equalArrays(end1,start1)) return null;
var x0 = start0[0],
y0 = start0[1],
x1 = end0[0],
y1 = end0[1],
x2 = start1[0],
y2 = start1[1],
x3 = end1[0],
y3 = end1[1];
var denom = (x0 - x1) * (y2 - y3) - (y0 - y1) * (x2 - x3);
if (denom == 0) return null;
var x4 = ((x0 * y1 - y0 * x1) * (x2 - x3) - (x0 - x1) * (x2 * y3 - y2 * x3)) / denom;
var y4 = ((x0 * y1 - y0 * x1) * (y2 - y3) - (y0 - y1) * (x2 * y3 - y2 * x3)) / denom;
return [x4, y4];
}
// Function to compare Arrays of numbers. From http://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript
function equalArrays(array1, array2) {
// if the other array is a falsy value, return
if (!array1 || !array2)
return false;
// compare lengths - can save a lot of time
if (array1.length != array2.length)
return false;
for (var i = 0, l=array1.length; i < l; i++) {
// Check if we have nested arrays
if (array1[i] instanceof Array && array2[i] instanceof Array) {
// recurse into the nested arrays
if (!equalArrays(array1[i],array2[i]))
return false;
}
else if (array1[i] != array2[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}