Skip to content

Commit 3a31a0e

Browse files
authored
Fix crash when processing type=building with outline being a multipolygon (#124)
* #88 initial support type=building with multipolygon outline * support multiple ways in inner rings * add test
1 parent 2433a7a commit 3a31a0e

File tree

3 files changed

+177
-20
lines changed

3 files changed

+177
-20
lines changed

src/building.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,33 @@ class Building {
4242
type;
4343
options;
4444

45+
static async getRelationDataWithChildRelations(id) {
46+
const xmlData = new window.DOMParser().parseFromString(await Building.getRelationData(id), 'text/xml');
47+
await Promise.all(Array.from(xmlData.querySelectorAll('member[type=relation]')).map(async r => {
48+
const childId = r.getAttribute('ref');
49+
if (r.getAttribute('id') === childId) {
50+
return;
51+
}
52+
const childData = new window.DOMParser().parseFromString(await Building.getRelationData(childId), 'text/xml');
53+
childData.querySelectorAll('node, way, relation').forEach(i => {
54+
if (xmlData.querySelector(`${i.tagName}[id="${i.getAttribute('id')}"]`)) {
55+
return;
56+
}
57+
xmlData.querySelector('osm').appendChild(i);
58+
});
59+
}));
60+
return new XMLSerializer().serializeToString(xmlData);
61+
}
62+
4563
/**
4664
* Download data for new building
4765
*/
4866
static async downloadDataAroundBuilding(type, id) {
49-
var data;
67+
let data;
5068
if (type === 'way') {
5169
data = await Building.getWayData(id);
5270
} else {
53-
data = await Building.getRelationData(id);
71+
data = await Building.getRelationDataWithChildRelations(id);
5472
}
5573
let xmlData = new window.DOMParser().parseFromString(data, 'text/xml');
5674
const nodelist = Building.buildNodeList(xmlData);

src/multibuildingpart.js

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ import {BuildingPart} from './buildingpart.js';
77
*/
88
class MultiBuildingPart extends BuildingPart {
99

10+
makeRings(members) {
11+
const ways = [];
12+
for (let j = 0; j < members.length; j++) {
13+
const wayID = members[j].getAttribute('ref');
14+
const way = this.fullXmlData.getElementById(wayID);
15+
if (way) {
16+
ways.push(way.cloneNode(true));
17+
} else {
18+
window.printError(`Missing way ${wayID} for relation ${this.id}`);
19+
ways.push(this.augmentedWays[wayID].cloneNode(true));
20+
}
21+
}
22+
return BuildingShapeUtils.combineWays(ways);
23+
}
24+
1025
/**
1126
* Create the shape of the outer relation.
1227
*
@@ -16,27 +31,15 @@ class MultiBuildingPart extends BuildingPart {
1631
this.type = 'multipolygon';
1732
const innerMembers = this.way.querySelectorAll('member[role="inner"][type="way"]');
1833
const outerMembers = this.way.querySelectorAll('member[role="outer"][type="way"]');
19-
const innerShapes = [];
20-
var shapes = [];
21-
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));
24-
}
25-
const ways = [];
26-
for (let j = 0; j < outerMembers.length; j++) {
27-
const way = this.fullXmlData.getElementById(outerMembers[j].getAttribute('ref'));
28-
if (way === null) {
29-
throw `Incompleted way ${outerMembers[j].getAttribute('ref')}`;
30-
}
31-
ways.push(way.cloneNode(true));
32-
}
33-
const closedWays = BuildingShapeUtils.combineWays(ways);
34-
for (let k = 0; k < closedWays.length; k++) {
35-
const shape = BuildingShapeUtils.createShape(closedWays[k], this.nodelist);
34+
const shapes = [];
35+
const innerShapes = this.makeRings(innerMembers).map(ring => BuildingShapeUtils.createShape(ring, this.nodelist, this.augmentedNodelist));
36+
const closedOuterWays = this.makeRings(outerMembers);
37+
for (let k = 0; k < closedOuterWays.length; k++) {
38+
const shape = BuildingShapeUtils.createShape(closedOuterWays[k], this.nodelist, this.augmentedNodelist);
3639
shape.holes.push(...innerShapes);
3740
shapes.push(shape);
3841
}
39-
if (closedWays.length === 1) {
42+
if (closedOuterWays.length === 1) {
4043
return shapes[0];
4144
}
4245
// Multiple outer members

test/building.test.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,142 @@ test('Test with neighboring incomplete building:part relation', () => {
174174
expect(new Building('42', data).id).toBe('42');
175175
});
176176

177+
const typeBuildingWithMultipolygonOutline = `<?xml version='1.0' encoding='UTF-8'?>
178+
<osm version="0.6">
179+
<node id="1" lat="59.938127" lon="30.4980057"/>
180+
<node id="2" lat="59.9380365" lon="30.4992843"/>
181+
<node id="3" lat="59.9384134" lon="30.4993839"/>
182+
<node id="4" lat="59.9385087" lon="30.4981066"/>
183+
<node id="5" lat="59.9381203" lon="30.4989364"/>
184+
<node id="6" lat="59.93838" lon="30.499005"/>
185+
<node id="7" lat="59.9384221" lon="30.498439"/>
186+
<node id="8" lat="59.9381551" lon="30.4983684"/>
187+
<way id="20">
188+
<nd ref="4"/>
189+
<nd ref="3"/>
190+
<nd ref="2"/>
191+
<nd ref="1"/>
192+
</way>
193+
<way id="21">
194+
<nd ref="1"/>
195+
<nd ref="4"/>
196+
</way>
197+
<way id="22">
198+
<nd ref="6"/>
199+
<nd ref="7"/>
200+
</way>
201+
<way id="23">
202+
<nd ref="5"/>
203+
<nd ref="6"/>
204+
</way>
205+
<way id="24">
206+
<nd ref="8"/>
207+
<nd ref="5"/>
208+
</way>
209+
<way id="25">
210+
<nd ref="7"/>
211+
<nd ref="8"/>
212+
</way>
213+
<relation id="40">
214+
<member type="way" ref="20" role="outer"/>
215+
<member type="way" ref="21" role="outer"/>
216+
<member type="way" ref="22" role="inner"/>
217+
<member type="way" ref="23" role="inner"/>
218+
<member type="way" ref="24" role="inner"/>
219+
<member type="way" ref="25" role="inner"/>
220+
<tag k="building" v="school"/>
221+
<tag k="building:levels" v="3"/>
222+
<tag k="roof:shape" v="flat"/>
223+
<tag k="type" v="multipolygon"/>
224+
</relation>
225+
<relation id="42">
226+
<member type="relation" ref="40" role="outline"/>
227+
<tag k="type" v="building"/>
228+
</relation>
229+
</osm>
230+
`;
231+
const typeBuildingRelationFullResponse = `<?xml version='1.0' encoding='UTF-8'?>
232+
<osm version="0.6">
233+
<relation id="40">
234+
<member type="way" ref="20" role="outer"/>
235+
<member type="way" ref="21" role="outer"/>
236+
<member type="way" ref="22" role="inner"/>
237+
<member type="way" ref="23" role="inner"/>
238+
<member type="way" ref="24" role="inner"/>
239+
<member type="way" ref="25" role="inner"/>
240+
<tag k="building" v="school"/>
241+
<tag k="building:levels" v="3"/>
242+
<tag k="roof:shape" v="flat"/>
243+
<tag k="type" v="multipolygon"/>
244+
</relation>
245+
<relation id="42">
246+
<member type="relation" ref="40" role="outline"/>
247+
<tag k="type" v="building"/>
248+
</relation>
249+
</osm>
250+
`;
251+
const outlineRelationFullResponse = `<?xml version='1.0' encoding='UTF-8'?>
252+
<osm version="0.6">
253+
<node id="1" lat="59.938127" lon="30.4980057"/>
254+
<node id="2" lat="59.9380365" lon="30.4992843"/>
255+
<node id="3" lat="59.9384134" lon="30.4993839"/>
256+
<node id="4" lat="59.9385087" lon="30.4981066"/>
257+
<node id="5" lat="59.9381203" lon="30.4989364"/>
258+
<node id="6" lat="59.93838" lon="30.499005"/>
259+
<node id="7" lat="59.9384221" lon="30.498439"/>
260+
<node id="8" lat="59.9381551" lon="30.4983684"/>
261+
<way id="20">
262+
<nd ref="4"/>
263+
<nd ref="3"/>
264+
<nd ref="2"/>
265+
<nd ref="1"/>
266+
</way>
267+
<way id="21">
268+
<nd ref="1"/>
269+
<nd ref="4"/>
270+
</way>
271+
<way id="22">
272+
<nd ref="6"/>
273+
<nd ref="7"/>
274+
</way>
275+
<way id="23">
276+
<nd ref="5"/>
277+
<nd ref="6"/>
278+
</way>
279+
<way id="24">
280+
<nd ref="8"/>
281+
<nd ref="5"/>
282+
</way>
283+
<way id="25">
284+
<nd ref="7"/>
285+
<nd ref="8"/>
286+
</way>
287+
<relation id="40">
288+
<member type="way" ref="20" role="outer"/>
289+
<member type="way" ref="21" role="outer"/>
290+
<member type="way" ref="22" role="inner"/>
291+
<member type="way" ref="23" role="inner"/>
292+
<member type="way" ref="24" role="inner"/>
293+
<member type="way" ref="25" role="inner"/>
294+
<tag k="building" v="school"/>
295+
<tag k="building:levels" v="3"/>
296+
<tag k="roof:shape" v="flat"/>
297+
<tag k="type" v="multipolygon"/>
298+
</relation>
299+
</osm>
300+
`;
301+
302+
test('Test downloading type=building with multipolygon outline and multiple inner ways', async() => {
303+
fetch.mockResponses(
304+
[typeBuildingRelationFullResponse], // /relation/42/full
305+
[outlineRelationFullResponse], // /relation/40/full
306+
[typeBuildingWithMultipolygonOutline], // /map call
307+
);
308+
const innerData = await Building.downloadDataAroundBuilding('relation', '42');
309+
const building = new Building('42', innerData);
310+
expect(building.id).toBe('42');
311+
expect(building.outerElement.shape.holes.length).toBe(1);
312+
});
177313

178314
window.printError = printError;
179315

0 commit comments

Comments
 (0)