Skip to content

Commit 964dd63

Browse files
authored
test for infinite loop in combineWays() / rewrite combineWays (#96)
* test for infinite loop in combineWays() * rewrite combineWays()
1 parent f71c2e6 commit 964dd63

File tree

3 files changed

+85
-53
lines changed

3 files changed

+85
-53
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ jobs:
2424
- name: Lint
2525
run: yarn run eslint .
2626
- name: Test
27-
run: CI=true NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npm test
27+
run: CI=true NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" timeout 20s npm test
2828
- name: Coveralls
2929
uses: coverallsapp/github-action@v2

src/extras/BuildingShapeUtils.js

Lines changed: 73 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -81,59 +81,83 @@ class BuildingShapeUtils extends ShapeUtils {
8181
* @return {[DOM.Element]} array of closed ways.
8282
*/
8383
static combineWays(ways) {
84-
var closedWays = [];
85-
var openWays = [];
86-
var changed = true;
87-
while (changed) {
88-
changed = false;
89-
for (let i = 0; i < ways.length - 1; i++) {
90-
if (BuildingShapeUtils.isClosed(ways[i])) {
91-
closedWays.push(ways[i]);
92-
} else {
93-
// These are HTMLCollections of nodes, not ways.
94-
const way1 = ways[i].getElementsByTagName('nd');
95-
const way2 = ways[i + 1].getElementsByTagName('nd');
96-
97-
// If the first node of way2 is the same as the last in way one, they can be combined
98-
// Or if the first node of way1 is the same as the last in way2
99-
// Need to extend this to tip-to-tip connections as well.
100-
// Need to add a "reverse way" function somewhere.
101-
if (way2[0].getAttribute('ref') === way1[way1.length - 1].getAttribute('ref')) {
102-
const result = BuildingShapeUtils.joinWays(ways[i], ways[i + 1]);
103-
openWays.push(result);
104-
i++;
105-
changed = true;
106-
} else if (way1[0].getAttribute('ref') === way2[way2.length - 1].getAttribute('ref')) {
107-
const result = BuildingShapeUtils.joinWays(ways[i + 1], ways[i]);
108-
openWays.push(result);
109-
i++;
110-
changed = true;
111-
} else if (way1[way1.length - 1].getAttribute('ref') === way2[way2.length - 1].getAttribute('ref')) {
112-
const tempway = BuildingShapeUtils.reverseWay(ways[i + 1]);
113-
const result = BuildingShapeUtils.joinWays(ways[i], tempway);
114-
openWays.push(result);
115-
i++;
116-
changed = true;
117-
} else if (way1[0].getAttribute('ref') === way2[0].getAttribute('ref')) {
118-
const tempway = BuildingShapeUtils.reverseWay(ways[i+1]);
119-
const result = BuildingShapeUtils.joinWays(tempway, ways[i]);
120-
openWays.push(result);
121-
i++;
122-
changed = true;
123-
} else {
124-
openWays.push(ways[i]);
125-
}
126-
}
84+
const closedWays = [];
85+
const wayBegins = {};
86+
const wayEnds = {};
87+
88+
ways.forEach(w => {
89+
const firstNodeID = w.querySelector('nd').getAttribute('ref');
90+
if (wayBegins[firstNodeID]) {
91+
wayBegins[firstNodeID].push(w);
92+
} else {
93+
wayBegins[firstNodeID] = [w];
12794
}
128-
const lastWay = ways[ways.length - 1];
129-
if (BuildingShapeUtils.isClosed(lastWay)) {
130-
closedWays.push(lastWay);
95+
96+
const lastNodeID = w.querySelector('nd:last-of-type').getAttribute('ref');
97+
if (wayEnds[lastNodeID]) {
98+
wayEnds[lastNodeID].push(w);
13199
} else {
132-
openWays.push(lastWay);
100+
wayEnds[lastNodeID] = [w];
101+
}
102+
});
103+
104+
const usedWays = new Set();
105+
106+
function tryMakeRing(currentRingWays) {
107+
if (currentRingWays[0].querySelector('nd').getAttribute('ref') ===
108+
currentRingWays[currentRingWays.length - 1].querySelector('nd:last-of-type').getAttribute('ref')) {
109+
return currentRingWays;
110+
}
111+
112+
const lastWay = currentRingWays[currentRingWays.length - 1];
113+
const lastNodeID = lastWay.querySelector('nd:last-of-type').getAttribute('ref');
114+
for (let way of wayBegins[lastNodeID] ?? []) {
115+
const wayID = way.getAttribute('id');
116+
if (usedWays.has(wayID)) {
117+
continue;
118+
}
119+
usedWays.add(wayID);
120+
currentRingWays.push(way);
121+
if (tryMakeRing(currentRingWays).length) {
122+
return currentRingWays;
123+
}
124+
currentRingWays.pop();
125+
usedWays.delete(wayID);
126+
}
127+
128+
for (let way of wayEnds[lastNodeID] ?? []) {
129+
const wayID = way.getAttribute('id');
130+
if (usedWays.has(wayID)) {
131+
continue;
132+
}
133+
usedWays.add(wayID);
134+
currentRingWays.push(BuildingShapeUtils.reverseWay(way));
135+
if (tryMakeRing(currentRingWays).length) {
136+
return currentRingWays;
137+
}
138+
currentRingWays.pop();
139+
usedWays.delete(wayID);
133140
}
134-
ways = openWays;
135-
openWays = [];
141+
142+
return [];
136143
}
144+
145+
ways.forEach(w => {
146+
const wayID = w.getAttribute('id');
147+
if (usedWays.has(wayID)){
148+
return;
149+
}
150+
usedWays.add(wayID);
151+
const result = tryMakeRing([w]);
152+
if (result.length) {
153+
let ring = result[0];
154+
result.slice(1).forEach(w => {
155+
ring = this.joinWays(ring, w);
156+
});
157+
closedWays.push(ring);
158+
}
159+
});
160+
137161
return closedWays;
138162
}
139163

test/combine_ways.test.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ describe('Combine Ways', () => {
5959
'<way id="4"><nd ref="2"/><nd ref="3"/></way>',
6060
], 1, 5, 'Test combining 4 ways',
6161
],
62+
[
63+
[
64+
'<way id="1"><nd ref="1"/><nd ref="2"/></way>',
65+
'<way id="2"><nd ref="3"/><nd ref="2"/></way>',
66+
], 0, 0, 'Test combining 2 open ways',
67+
],
6268
])('${description}', (ways, length, nodes, description) => {
6369
let parser = new window.DOMParser();
6470
const xml = [];
@@ -67,8 +73,10 @@ describe('Combine Ways', () => {
6773
}
6874
let result = BuildingShapeUtils.combineWays(xml);
6975
expect(result.length).toBe(length);
70-
expect(BuildingShapeUtils.isClosed(result[0]));
71-
expect(BuildingShapeUtils.isSelfIntersecting(result[0])).toBe(false);
72-
expect(result[0].getElementsByTagName('nd').length).toBe(nodes);
76+
if (length) {
77+
expect(BuildingShapeUtils.isClosed(result[0]));
78+
expect(BuildingShapeUtils.isSelfIntersecting(result[0])).toBe(false);
79+
expect(result[0].getElementsByTagName('nd').length).toBe(nodes);
80+
}
7381
});
7482
});

0 commit comments

Comments
 (0)