Skip to content

Commit 0e402a9

Browse files
committed
Rewrite ways combine, skip non-way members
1 parent ddde11f commit 0e402a9

File tree

4 files changed

+100
-66
lines changed

4 files changed

+100
-66
lines changed

src/building.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Building {
4343
const nodelist = Building.buildNodeList(xmlData);
4444
const extents = Building.getExtents(id, xmlData, nodelist);
4545
const innerData = await Building.getInnerData(...extents);
46-
const [augmentedNodelist, augmentedWays] = await Building.buildAugmentedData(innerData)
46+
const [augmentedNodelist, augmentedWays] = await Building.buildAugmentedData(innerData);
4747
return new Building(id, innerData, augmentedNodelist, augmentedWays);
4848
}
4949

@@ -138,16 +138,16 @@ class Building {
138138
await Promise.all(Array.from(memberWays).map(async currentWay => {
139139
const wayID = currentWay.getAttribute('ref');
140140
if (completedWays.has(wayID)) {
141-
return
141+
return;
142142
}
143-
printError('Additional downloading way ' + wayID);
143+
window.printError('Additional downloading way ' + wayID);
144144
const wayData = new DOMParser().parseFromString(await Building.getWayData(wayID), 'text/xml');
145-
printError(`Way ${wayID} was downloaded`);
145+
window.printError(`Way ${wayID} was downloaded`);
146146
waysList[wayID] = wayData.querySelector('way');
147147
wayData.querySelectorAll('node').forEach(i => {
148148
nodeList[i.getAttribute('id')] = [i.getAttribute('lon'), i.getAttribute('lat')];
149149
});
150-
}))
150+
}));
151151
return [nodeList, waysList];
152152
}
153153

@@ -211,7 +211,11 @@ class Building {
211211
for (let i = 0; i < parts.length; i++) {
212212
if (parts[i].querySelector('[k="building:part"]')) {
213213
const id = parts[i].getAttribute('id');
214-
this.parts.push(new MultiBuildingPart(id, this.fullXmlData, this.nodelist, this.augmentedNodelist, this.augmentedWays, this.outerElement.options));
214+
try {
215+
this.parts.push(new MultiBuildingPart(id, this.fullXmlData, this.nodelist, this.augmentedNodelist, this.augmentedWays, this.outerElement.options));
216+
} catch (e) {
217+
window.printError(e);
218+
}
215219
}
216220
}
217221
}

src/buildingpart.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@ class BuildingPart {
6666
* @param {number} id - the OSM id of the way or multipolygon.
6767
* @param {XMLDocument} fullXmlData - XML for the region.
6868
* @param {[[number, number],...]} nodelist - Cartesian coordinates of each node keyed by node refID
69-
* @param {object} options - default values for the building part.
69+
* @param augmentedNodelist - list of nodes outside bbox
70+
* @param augmentedWays - list of ways outside bbox
71+
* @param {object} defaultOptions - default values for the building part.
7072
*/
71-
constructor(id, fullXmlData, nodelist, augmentedNodelist, augmentedWays, defaultOptions = {}) {
73+
constructor(id, fullXmlData, nodelist, augmentedNodelist = {}, augmentedWays = {}, defaultOptions = {}) {
7274
this.options = this.blankOptions;
7375
if (Object.keys(defaultOptions).length === 0) {
7476
defaultOptions = this.blankOptions;

src/extras/BuildingShapeUtils.js

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class BuildingShapeUtils extends ShapeUtils {
1010
*
1111
* @param {DOM.Element} way - OSM XML way element.
1212
* @param {[number, number]} nodelist - list of all nodes
13+
* @param augmentedNodelist - list of nodes outside bbox
1314
*
1415
* @return {THREE.Shape} shape - the shape
1516
*/
@@ -59,62 +60,82 @@ class BuildingShapeUtils extends ShapeUtils {
5960
*/
6061
static combineWays(ways) {
6162
const closedWays = [];
62-
let openWays = [];
63-
let changed = true;
64-
let watchDogCounter = 0
65-
while (changed) {
66-
if (watchDogCounter++ > 2_000_000) {
67-
throw 'Infinite loop in combineWays';
63+
const wayBegins = {};
64+
const wayEnds = {};
65+
66+
ways.forEach(w => {
67+
const firstNodeID = w.querySelector('nd').getAttribute('ref');
68+
if (wayBegins[firstNodeID]) {
69+
wayBegins[firstNodeID].push(w);
70+
} else {
71+
wayBegins[firstNodeID] = [w];
72+
}
73+
74+
const lastNodeID = w.querySelector('nd:last-of-type').getAttribute('ref');
75+
if (wayEnds[lastNodeID]) {
76+
wayEnds[lastNodeID].push(w);
77+
} else {
78+
wayEnds[lastNodeID] = [w];
6879
}
69-
changed = false;
70-
for (let i = 0; i < ways.length - 1; i++) {
71-
if (BuildingShapeUtils.isClosed(ways[i])) {
72-
closedWays.push(ways[i]);
73-
} else {
74-
// These are HTMLCollections of nodes, not ways.
75-
const way1 = ways[i].getElementsByTagName('nd');
76-
const way2 = ways[i + 1].getElementsByTagName('nd');
80+
});
7781

78-
// If the first node of way2 is the same as the last in way one, they can be combined
79-
// Or if the first node of way1 is the same as the last in way2
80-
// Need to extend this to tip-to-tip connections as well.
81-
// Need to add a "reverse way" function somewhere.
82-
if (way2[0].getAttribute('ref') === way1[way1.length - 1].getAttribute('ref')) {
83-
const result = BuildingShapeUtils.joinWays(ways[i], ways[i + 1]);
84-
openWays.push(result);
85-
i++;
86-
changed = true;
87-
} else if (way1[0].getAttribute('ref') === way2[way2.length - 1].getAttribute('ref')) {
88-
const result = BuildingShapeUtils.joinWays(ways[i + 1], ways[i]);
89-
openWays.push(result);
90-
i++;
91-
changed = true;
92-
} else if (way1[way1.length - 1].getAttribute('ref') === way2[way2.length - 1].getAttribute('ref')) {
93-
const tempway = BuildingShapeUtils.reverseWay(ways[i + 1]);
94-
const result = BuildingShapeUtils.joinWays(ways[i], tempway);
95-
openWays.push(result);
96-
i++;
97-
changed = true;
98-
} else if (way1[0].getAttribute('ref') === way2[0].getAttribute('ref')) {
99-
const tempway = BuildingShapeUtils.reverseWay(ways[i]);
100-
const result = BuildingShapeUtils.joinWays(tempway, ways[i + 1]);
101-
openWays.push(result);
102-
i++;
103-
changed = true;
104-
} else {
105-
openWays.push(ways[i]);
106-
}
82+
const usedWays = new Set();
83+
84+
function tryMakeRing(currentRingWays) {
85+
if (currentRingWays[0].querySelector('nd').getAttribute('ref') ===
86+
currentRingWays[currentRingWays.length - 1].querySelector('nd:last-of-type').getAttribute('ref')) {
87+
return currentRingWays;
88+
}
89+
90+
const lastWay = currentRingWays[currentRingWays.length - 1];
91+
const lastNodeID = lastWay.querySelector('nd:last-of-type').getAttribute('ref');
92+
for (let way of wayBegins[lastNodeID] ?? []) {
93+
const wayID = way.getAttribute('id');
94+
if (usedWays.has(wayID)) {
95+
continue;
96+
}
97+
usedWays.add(wayID);
98+
currentRingWays.push(way);
99+
if (tryMakeRing(currentRingWays).length) {
100+
return currentRingWays;
107101
}
102+
currentRingWays.pop();
103+
usedWays.delete(wayID);
108104
}
109-
const lastWay = ways[ways.length - 1];
110-
if (BuildingShapeUtils.isClosed(lastWay)) {
111-
closedWays.push(lastWay);
112-
} else {
113-
openWays.push(lastWay);
105+
106+
for (let way of wayEnds[lastNodeID] ?? []) {
107+
const wayID = way.getAttribute('id');
108+
if (usedWays.has(wayID)) {
109+
continue;
110+
}
111+
usedWays.add(wayID);
112+
currentRingWays.push(BuildingShapeUtils.reverseWay(way));
113+
if (tryMakeRing(currentRingWays).length) {
114+
return currentRingWays;
115+
}
116+
currentRingWays.pop();
117+
usedWays.delete(wayID);
114118
}
115-
ways = openWays;
116-
openWays = [];
119+
120+
return [];
117121
}
122+
123+
ways.forEach(w => {
124+
const wayID = w.getAttribute('ref');
125+
if (usedWays.has(wayID)){
126+
return;
127+
}
128+
usedWays.add(wayID);
129+
const result = tryMakeRing([w]);
130+
if (result.length) {
131+
let ring = result[0];
132+
result.slice(1).forEach(w => {
133+
ring = this.joinWays(ring, w);
134+
});
135+
closedWays.push(ring);
136+
}
137+
});
138+
118139
return closedWays;
119140
}
120141

src/multibuildingpart.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,29 @@ class MultiBuildingPart extends BuildingPart {
1414
*/
1515
buildShape() {
1616
this.type = 'multipolygon';
17-
const innerMembers = this.way.querySelectorAll('member[role="inner"]');
18-
const outerMembers = this.way.querySelectorAll('member[role="outer"]');
17+
const innerMembers = this.way.querySelectorAll('member[role="inner"][type="way"]');
18+
const outerMembers = this.way.querySelectorAll('member[role="outer"][type="way"]');
1919
const innerShapes = [];
20-
let shapes = [];
20+
const shapes = [];
2121
for (let i = 0; i < innerMembers.length; i++) {
22-
const way = this.fullXmlData.getElementById(innerMembers[i].getAttribute('ref'));
23-
innerShapes.push(BuildingShapeUtils.createShape(way, this.nodelist, this.augmentedNodelist));
22+
const wayID = innerMembers[i].getAttribute('ref');
23+
const way = this.fullXmlData.getElementById(wayID);
24+
if (way) {
25+
innerShapes.push(BuildingShapeUtils.createShape(way, this.nodelist, this.augmentedNodelist));
26+
} else {
27+
window.printError(`Missing way ${wayID} for relation ${this.id} for inner members`);
28+
innerShapes.push(BuildingShapeUtils.createShape(this.augmentedWays[wayID], this.nodelist, this.augmentedNodelist));
29+
}
2430
}
2531
const ways = [];
2632
for (let j = 0; j < outerMembers.length; j++) {
27-
const way = this.fullXmlData.getElementById(outerMembers[j].getAttribute('ref'));
33+
const wayID = outerMembers[j].getAttribute('ref');
34+
const way = this.fullXmlData.getElementById(wayID);
2835
if (way) {
2936
ways.push(way);
3037
} else {
31-
printError(`Missing way ${outerMembers[j].getAttribute('ref')} for relation ${this.id}`);
32-
ways.push(this.augmentedWays[outerMembers[j].getAttribute('ref')])
38+
window.printError(`Missing way ${wayID} for relation ${this.id}`);
39+
ways.push(this.augmentedWays[wayID]);
3340
}
3441
}
3542
const closedWays = BuildingShapeUtils.combineWays(ways);

0 commit comments

Comments
 (0)