import * as d3 from 'd3'; import _ from 'lodash'; /* iD.Difference represents the difference between two graphs. It knows how to calculate the set of entities that were created, modified, or deleted, and also contains the logic for recursively extending a difference to the complete set of entities that will require a redraw, taking into account child and parent relationships. */ export function coreDifference(base, head) { var changes = {}, difference = {}, length = 0; function changed(h, b) { return h !== b && !_.isEqual(_.omit(h, 'v'), _.omit(b, 'v')); } _.each(head.entities, function(h, id) { var b = base.entities[id]; if (changed(h, b)) { changes[id] = {base: b, head: h}; length++; } }); _.each(base.entities, function(b, id) { var h = head.entities[id]; if (!changes[id] && changed(h, b)) { changes[id] = {base: b, head: h}; length++; } }); function addParents(parents, result) { for (var i = 0; i < parents.length; i++) { var parent = parents[i]; if (parent.id in result) continue; result[parent.id] = parent; addParents(head.parentRelations(parent), result); } } difference.length = function() { return length; }; difference.changes = function() { return changes; }; difference.extantIDs = function() { var result = []; _.each(changes, function(change, id) { if (change.head) result.push(id); }); return result; }; difference.modified = function() { var result = []; _.each(changes, function(change) { if (change.base && change.head) result.push(change.head); }); return result; }; difference.created = function() { var result = []; _.each(changes, function(change) { if (!change.base && change.head) result.push(change.head); }); return result; }; difference.deleted = function() { var result = []; _.each(changes, function(change) { if (change.base && !change.head) result.push(change.base); }); return result; }; difference.summary = function() { var relevant = {}; function addEntity(entity, graph, changeType) { relevant[entity.id] = { entity: entity, graph: graph, changeType: changeType }; } function addParents(entity) { var parents = head.parentWays(entity); for (var j = parents.length - 1; j >= 0; j--) { var parent = parents[j]; if (!(parent.id in relevant)) addEntity(parent, head, 'modified'); } } _.each(changes, function(change) { if (change.head && change.head.geometry(head) !== 'vertex') { addEntity(change.head, head, change.base ? 'modified' : 'created'); } else if (change.base && change.base.geometry(base) !== 'vertex') { addEntity(change.base, base, 'deleted'); } else if (change.base && change.head) { // modified vertex var moved = !_.isEqual(change.base.loc, change.head.loc), retagged = !_.isEqual(change.base.tags, change.head.tags); if (moved) { addParents(change.head); } if (retagged || (moved && change.head.hasInterestingTags())) { addEntity(change.head, head, 'modified'); } } else if (change.head && change.head.hasInterestingTags()) { // created vertex addEntity(change.head, head, 'created'); } else if (change.base && change.base.hasInterestingTags()) { // deleted vertex addEntity(change.base, base, 'deleted'); } }); return d3.values(relevant); }; difference.complete = function(extent) { var result = {}, id, change; for (id in changes) { change = changes[id]; var h = change.head, b = change.base, entity = h || b; if (extent && (!h || !h.intersects(extent, head)) && (!b || !b.intersects(extent, base))) continue; result[id] = h; if (entity.type === 'way') { var nh = h ? h.nodes : [], nb = b ? b.nodes : [], diff, i; diff = _.difference(nh, nb); for (i = 0; i < diff.length; i++) { result[diff[i]] = head.hasEntity(diff[i]); } diff = _.difference(nb, nh); for (i = 0; i < diff.length; i++) { result[diff[i]] = head.hasEntity(diff[i]); } } addParents(head.parentWays(entity), result); addParents(head.parentRelations(entity), result); } return result; }; return difference; }