modules/actions/merge_polygon.js (117 lines of code) (raw):

import { geomPolygonContainsPolygon } from '@id-sdk/math'; import { utilArrayGroupBy, utilArrayIntersection, utilObjectOmit } from '@id-sdk/util'; import { osmJoinWays, osmRelation } from '../osm'; 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 geomPolygonContainsPolygon( 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 var relation = entities.multipolygon[0] || osmRelation({ id: newRelationId, tags: { type: 'multipolygon' }}); entities.multipolygon.slice(1).forEach(function(m) { 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; }