mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
159 lines
5.7 KiB
JavaScript
159 lines
5.7 KiB
JavaScript
import { geoPolygonContainsPolygon } from '../geo';
|
|
import { osmJoinWays, osmRelation } from '../osm';
|
|
import { utilArrayGroupBy, utilArrayIntersection, utilObjectOmit, utilOldestID } from '../util';
|
|
|
|
|
|
export function actionMergePolygon(ids, newRelationId) {
|
|
|
|
function groupEntities(graph) {
|
|
var entities = ids.map(function (id) { return graph.entity(id); });
|
|
var geometryGroups = utilArrayGroupBy(entities, function(entity) {
|
|
if (entity.type === 'way' && entity.isClosed()) {
|
|
return 'closedWay';
|
|
} else if (entity.type === 'relation' && entity.isMultipolygon()) {
|
|
return 'multipolygon';
|
|
} else {
|
|
return 'other';
|
|
}
|
|
});
|
|
|
|
return Object.assign(
|
|
{ closedWay: [], multipolygon: [], other: [] },
|
|
geometryGroups
|
|
);
|
|
}
|
|
|
|
|
|
var action = function(graph) {
|
|
var entities = groupEntities(graph);
|
|
|
|
// An array representing all the polygons that are part of the multipolygon.
|
|
//
|
|
// Each element is itself an array of objects with an id property, and has a
|
|
// locs property which is an array of the locations forming the polygon.
|
|
var polygons = entities.multipolygon.reduce(function(polygons, m) {
|
|
return polygons.concat(osmJoinWays(m.members, graph));
|
|
}, []).concat(entities.closedWay.map(function(d) {
|
|
var member = [{id: d.id}];
|
|
member.nodes = graph.childNodes(d);
|
|
return member;
|
|
}));
|
|
|
|
// contained is an array of arrays of boolean values,
|
|
// where contained[j][k] is true iff the jth way is
|
|
// contained by the kth way.
|
|
var contained = polygons.map(function(w, i) {
|
|
return polygons.map(function(d, n) {
|
|
if (i === n) return null;
|
|
return geoPolygonContainsPolygon(
|
|
d.nodes.map(function(n) { return n.loc; }),
|
|
w.nodes.map(function(n) { return n.loc; })
|
|
);
|
|
});
|
|
});
|
|
|
|
// Sort all polygons as either outer or inner ways
|
|
var members = [];
|
|
var outer = true;
|
|
|
|
while (polygons.length) {
|
|
extractUncontained(polygons);
|
|
polygons = polygons.filter(isContained);
|
|
contained = contained.filter(isContained).map(filterContained);
|
|
}
|
|
|
|
function isContained(d, i) {
|
|
return contained[i].some(function(val) { return val; });
|
|
}
|
|
|
|
function filterContained(d) {
|
|
return d.filter(isContained);
|
|
}
|
|
|
|
function extractUncontained(polygons) {
|
|
polygons.forEach(function(d, i) {
|
|
if (!isContained(d, i)) {
|
|
d.forEach(function(member) {
|
|
members.push({
|
|
type: 'way',
|
|
id: member.id,
|
|
role: outer ? 'outer' : 'inner'
|
|
});
|
|
});
|
|
}
|
|
});
|
|
outer = !outer;
|
|
}
|
|
|
|
// Move all tags to one relation.
|
|
// Keep the oldest multipolygon alive if it exists.
|
|
var relation;
|
|
if (entities.multipolygon.length > 0) {
|
|
var oldestID = utilOldestID(entities.multipolygon.map((entity) => entity.id));
|
|
relation = entities.multipolygon.find((entity) => entity.id === oldestID);
|
|
} else {
|
|
relation = osmRelation({ id: newRelationId, tags: { type: 'multipolygon' }});
|
|
}
|
|
|
|
entities.multipolygon.forEach(function(m) {
|
|
if (m.id !== relation.id) {
|
|
relation = relation.mergeTags(m.tags);
|
|
graph = graph.remove(m);
|
|
}
|
|
});
|
|
|
|
entities.closedWay.forEach(function(way) {
|
|
function isThisOuter(m) {
|
|
return m.id === way.id && m.role !== 'inner';
|
|
}
|
|
if (members.some(isThisOuter)) {
|
|
relation = relation.mergeTags(way.tags);
|
|
graph = graph.replace(way.update({ tags: {} }));
|
|
}
|
|
});
|
|
|
|
return graph.replace(relation.update({
|
|
members: members,
|
|
tags: utilObjectOmit(relation.tags, ['area'])
|
|
}));
|
|
};
|
|
|
|
|
|
action.disabled = function(graph) {
|
|
var entities = groupEntities(graph);
|
|
if (entities.other.length > 0 ||
|
|
entities.closedWay.length + entities.multipolygon.length < 2) {
|
|
return 'not_eligible';
|
|
}
|
|
if (!entities.multipolygon.every(function(r) { return r.isComplete(graph); })) {
|
|
return 'incomplete_relation';
|
|
}
|
|
|
|
if (!entities.multipolygon.length) {
|
|
var sharedMultipolygons = [];
|
|
entities.closedWay.forEach(function(way, i) {
|
|
if (i === 0) {
|
|
sharedMultipolygons = graph.parentMultipolygons(way);
|
|
} else {
|
|
sharedMultipolygons = utilArrayIntersection(sharedMultipolygons, graph.parentMultipolygons(way));
|
|
}
|
|
});
|
|
sharedMultipolygons = sharedMultipolygons.filter(function(relation) {
|
|
return relation.members.length === entities.closedWay.length;
|
|
});
|
|
if (sharedMultipolygons.length) {
|
|
// don't create a new multipolygon if it'd be redundant
|
|
return 'not_eligible';
|
|
}
|
|
} else if (entities.closedWay.some(function(way) {
|
|
return utilArrayIntersection(graph.parentMultipolygons(way), entities.multipolygon).length;
|
|
})) {
|
|
// don't add a way to a multipolygon again if it's already a member
|
|
return 'not_eligible';
|
|
}
|
|
};
|
|
|
|
|
|
return action;
|
|
}
|