mirror of
https://github.com/FoggedLens/iD.git
synced 2026-04-22 11:46:11 +02:00
ddc5e324f6
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. Additionally, all history mutators now return a difference.
237 lines
7.5 KiB
JavaScript
237 lines
7.5 KiB
JavaScript
iD.Graph = function(other, mutable) {
|
|
if (!(this instanceof iD.Graph)) return new iD.Graph(other, mutable);
|
|
|
|
if (other instanceof iD.Graph) {
|
|
var base = other.base();
|
|
this.entities = _.assign(Object.create(base.entities), other.entities);
|
|
this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays);
|
|
this._parentRels = _.assign(Object.create(base.parentRels), other._parentRels);
|
|
this.inherited = true;
|
|
|
|
} else {
|
|
if (_.isArray(other)) {
|
|
var entities = {};
|
|
for (var i = 0; i < other.length; i++) {
|
|
entities[other[i].id] = other[i];
|
|
}
|
|
other = entities;
|
|
}
|
|
this.entities = Object.create({});
|
|
this._parentWays = Object.create({});
|
|
this._parentRels = Object.create({});
|
|
this.rebase(other || {});
|
|
}
|
|
|
|
this.transients = {};
|
|
this._childNodes = {};
|
|
this.getEntity = _.bind(this.entity, this);
|
|
|
|
if (!mutable) {
|
|
this.freeze();
|
|
}
|
|
};
|
|
|
|
iD.Graph.prototype = {
|
|
entity: function(id) {
|
|
return this.entities[id];
|
|
},
|
|
|
|
transient: function(entity, key, fn) {
|
|
var id = entity.id,
|
|
transients = this.transients[id] ||
|
|
(this.transients[id] = {});
|
|
|
|
if (transients[key] !== undefined) {
|
|
return transients[key];
|
|
}
|
|
|
|
return transients[key] = fn.call(entity);
|
|
},
|
|
|
|
parentWays: function(entity) {
|
|
return _.map(this._parentWays[entity.id], this.getEntity);
|
|
},
|
|
|
|
isPoi: function(entity) {
|
|
var parentWays = this._parentWays[entity.id];
|
|
return !parentWays || parentWays.length === 0;
|
|
},
|
|
|
|
isShared: function(entity) {
|
|
var parentWays = this._parentWays[entity.id];
|
|
return parentWays && parentWays.length > 1;
|
|
},
|
|
|
|
parentRelations: function(entity) {
|
|
return _.map(this._parentRels[entity.id], this.getEntity);
|
|
},
|
|
|
|
childNodes: function(entity) {
|
|
if (this._childNodes[entity.id])
|
|
return this._childNodes[entity.id];
|
|
|
|
var nodes = [];
|
|
for (var i = 0, l = entity.nodes.length; i < l; i++) {
|
|
nodes[i] = this.entity(entity.nodes[i]);
|
|
}
|
|
|
|
return (this._childNodes[entity.id] = nodes);
|
|
},
|
|
|
|
base: function() {
|
|
return {
|
|
'entities': iD.util.getPrototypeOf(this.entities),
|
|
'parentWays': iD.util.getPrototypeOf(this._parentWays),
|
|
'parentRels': iD.util.getPrototypeOf(this._parentRels)
|
|
};
|
|
},
|
|
|
|
// Unlike other graph methods, rebase mutates in place. This is because it
|
|
// is used only during the history operation that merges newly downloaded
|
|
// data into each state. To external consumers, it should appear as if the
|
|
// graph always contained the newly downloaded data.
|
|
rebase: function(entities) {
|
|
var base = this.base(),
|
|
i, k, child, id, keys;
|
|
// Merging of data only needed if graph is the base graph
|
|
if (!this.inherited) {
|
|
for (i in entities) {
|
|
if (!base.entities[i]) {
|
|
base.entities[i] = entities[i];
|
|
this._updateCalculated(undefined, entities[i],
|
|
base.parentWays, base.parentRels);
|
|
}
|
|
}
|
|
}
|
|
|
|
keys = Object.keys(this._parentWays);
|
|
for (i = 0; i < keys.length; i++) {
|
|
child = keys[i];
|
|
if (base.parentWays[child]) {
|
|
for (k = 0; k < base.parentWays[child].length; k++) {
|
|
id = base.parentWays[child][k];
|
|
if (this.entity(id) && !_.contains(this._parentWays[child], id)) {
|
|
this._parentWays[child].push(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
keys = Object.keys(this._parentRels);
|
|
for (i = 0; i < keys.length; i++) {
|
|
child = keys[i];
|
|
if (base.parentRels[child]) {
|
|
for (k = 0; k < base.parentRels[child].length; k++) {
|
|
id = base.parentRels[child][k];
|
|
if (this.entity(id) && !_.contains(this._parentRels[child], id)) {
|
|
this._parentRels[child].push(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// Updates calculated properties (parentWays, parentRels) for the specified change
|
|
_updateCalculated: function(oldentity, entity, parentWays, parentRels) {
|
|
|
|
parentWays = parentWays || this._parentWays;
|
|
parentRels = parentRels || this._parentRels;
|
|
|
|
var type = entity && entity.type || oldentity && oldentity.type,
|
|
removed, added, ways, rels, i;
|
|
|
|
|
|
if (type === 'way') {
|
|
|
|
// Update parentWays
|
|
if (oldentity && entity) {
|
|
removed = _.difference(oldentity.nodes, entity.nodes);
|
|
added = _.difference(entity.nodes, oldentity.nodes);
|
|
} else if (oldentity) {
|
|
removed = oldentity.nodes;
|
|
added = [];
|
|
} else if (entity) {
|
|
removed = [];
|
|
added = entity.nodes;
|
|
}
|
|
for (i = 0; i < removed.length; i++) {
|
|
parentWays[removed[i]] = _.without(parentWays[removed[i]], oldentity.id);
|
|
}
|
|
for (i = 0; i < added.length; i++) {
|
|
ways = _.without(parentWays[added[i]], entity.id);
|
|
ways.push(entity.id);
|
|
parentWays[added[i]] = ways;
|
|
}
|
|
} else if (type === 'node') {
|
|
|
|
} else if (type === 'relation') {
|
|
|
|
// Update parentRels
|
|
if (oldentity && entity) {
|
|
removed = _.difference(oldentity.members, entity.members);
|
|
added = _.difference(entity.members, oldentity);
|
|
} else if (oldentity) {
|
|
removed = oldentity.members;
|
|
added = [];
|
|
} else if (entity) {
|
|
removed = [];
|
|
added = entity.members;
|
|
}
|
|
for (i = 0; i < removed.length; i++) {
|
|
parentRels[removed[i].id] = _.without(parentRels[removed[i].id], oldentity.id);
|
|
}
|
|
for (i = 0; i < added.length; i++) {
|
|
rels = _.without(parentRels[added[i].id], entity.id);
|
|
rels.push(entity.id);
|
|
parentRels[added[i].id] = rels;
|
|
}
|
|
}
|
|
},
|
|
|
|
replace: function(entity) {
|
|
return this.update(function () {
|
|
this._updateCalculated(this.entities[entity.id], entity);
|
|
this.entities[entity.id] = entity;
|
|
});
|
|
},
|
|
|
|
remove: function(entity) {
|
|
return this.update(function () {
|
|
this._updateCalculated(entity, undefined);
|
|
this.entities[entity.id] = undefined;
|
|
});
|
|
},
|
|
|
|
update: function() {
|
|
var graph = this.frozen ? iD.Graph(this, true) : this;
|
|
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
arguments[i].call(graph, graph);
|
|
}
|
|
|
|
return this.frozen ? graph.freeze() : this;
|
|
},
|
|
|
|
freeze: function() {
|
|
this.frozen = true;
|
|
|
|
if (iD.debug) {
|
|
Object.freeze(this.entities);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
// get all objects that intersect an extent.
|
|
intersects: function(extent) {
|
|
var items = [];
|
|
for (var i in this.entities) {
|
|
var entity = this.entities[i];
|
|
if (entity && entity.intersects(extent, this)) {
|
|
items.push(entity);
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
};
|