mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
Merge pull request #68 from jfirebaugh/refactor
Refactoring Graph manipulation
This commit is contained in:
@@ -47,6 +47,8 @@
|
||||
<script type='text/javascript' src='js/iD/actions/actions.js'></script>
|
||||
<script type='text/javascript' src='js/iD/actions/operations.js'></script>
|
||||
|
||||
<script type='text/javascript' src='js/iD/controller/controller.js'></script>
|
||||
|
||||
<script type='text/javascript' src='js/iD/format/format.js'></script>
|
||||
<script type='text/javascript' src='js/iD/format/GeoJSON.js'></script>
|
||||
<script type='text/javascript' src='js/iD/format/XML.js'></script>
|
||||
@@ -56,6 +58,7 @@
|
||||
<script type='text/javascript' src='js/iD/graph/Relation.js'></script>
|
||||
<script type='text/javascript' src='js/iD/graph/Way.js'></script>
|
||||
<script type='text/javascript' src='js/iD/graph/Graph.js'></script>
|
||||
<script type='text/javascript' src='js/iD/graph/History.js'></script>
|
||||
<script type='text/javascript' src='js/iD/Connection.js'></script>
|
||||
|
||||
<script type='text/javascript' src='js/iD/UI.js'></script>
|
||||
|
||||
@@ -39,7 +39,7 @@ iD.actions.AddPlace = {
|
||||
surface.on('click.addplace', function() {
|
||||
var ll = this.map.projection.invert(
|
||||
d3.mouse(surface.node()));
|
||||
iD.operations.addNode(this.map, iD.actions._node(ll));
|
||||
this.map.do(iD.operations.addNode(iD.actions._node(ll)));
|
||||
this.exit();
|
||||
}.bind(this));
|
||||
|
||||
@@ -95,7 +95,7 @@ iD.actions.AddRoad = {
|
||||
var node = iD.actions._node(ll);
|
||||
way.nodes.push(node.id);
|
||||
|
||||
iD.operations.changeWayNodes(this.map, way, node);
|
||||
this.map.do(iD.operations.changeWayNodes(way, node));
|
||||
this.controller.go(iD.actions.DrawRoad(way));
|
||||
}.bind(this));
|
||||
|
||||
@@ -119,7 +119,7 @@ iD.actions.DrawRoad = function(way) {
|
||||
|
||||
this.falsenode = iD.actions._node([0, 0]);
|
||||
|
||||
iD.operations.addTemporary(this.map, this.falsenode);
|
||||
this.map.do(iD.operations.addTemporary(this.falsenode));
|
||||
// way.nodes = way.nodes.slice();
|
||||
way.nodes.push(this.falsenode.id);
|
||||
|
||||
@@ -140,7 +140,7 @@ iD.actions.DrawRoad = function(way) {
|
||||
|
||||
way.nodes.push(node.id);
|
||||
|
||||
iD.operations.changeWayNodes(this.map, way, node);
|
||||
this.map.do(iD.operations.changeWayNodes(way, node));
|
||||
|
||||
way.nodes = way.nodes.slice();
|
||||
way.nodes.push(this.falsenode.id);
|
||||
@@ -152,7 +152,7 @@ iD.actions.DrawRoad = function(way) {
|
||||
}.bind(this));
|
||||
},
|
||||
exit: function() {
|
||||
iD.operations.addTemporary(this.map, this.falsenode);
|
||||
this.map.do(iD.operations.addTemporary(this.falsenode));
|
||||
this.map.surface.on('mousemove.drawroad', null);
|
||||
this.map.surface.on('click.drawroad', null);
|
||||
d3.select(document).on('.drawroad', null);
|
||||
@@ -177,23 +177,3 @@ iD.actions.Move = {
|
||||
},
|
||||
exit: function() { }
|
||||
};
|
||||
|
||||
// A controller holds a single action at a time and calls `.enter` and `.exit`
|
||||
// to bind and unbind actions.
|
||||
iD.controller = function(map) {
|
||||
var controller = { action: null };
|
||||
|
||||
controller.go = function(x) {
|
||||
x.controller = controller;
|
||||
x.map = map;
|
||||
if (controller.action) {
|
||||
controller.action.exit();
|
||||
}
|
||||
x.enter();
|
||||
controller.action = x;
|
||||
};
|
||||
|
||||
controller.go(iD.actions.Move);
|
||||
|
||||
return controller;
|
||||
};
|
||||
|
||||
@@ -1,67 +1,46 @@
|
||||
iD.operations = {};
|
||||
|
||||
// operations take a map, and arguments that they modify in the map's graph.
|
||||
// they use `graph.modify` to do this while keeping a previous version
|
||||
// of the graph the same.
|
||||
|
||||
iD.operations.addNode = function(map, node) {
|
||||
map.graph.modify(function(graph) {
|
||||
var o = {};
|
||||
o[node.id] = node;
|
||||
return graph.set(o);
|
||||
}, 'added a place');
|
||||
map.update();
|
||||
iD.operations.addNode = function(node) {
|
||||
return function(graph) {
|
||||
return graph.replace(node, 'added a place');
|
||||
}
|
||||
};
|
||||
|
||||
iD.operations.startWay = function(map, way) {
|
||||
map.graph.modify(function(graph) {
|
||||
var o = {};
|
||||
o[way.id] = way;
|
||||
return graph.set(o);
|
||||
}, 'started a road');
|
||||
map.update();
|
||||
iD.operations.startWay = function(way) {
|
||||
return function(graph) {
|
||||
return graph.replace(way, 'started a road');
|
||||
};
|
||||
};
|
||||
|
||||
iD.operations.remove = function(map, node) {
|
||||
map.graph.modify(function(graph) {
|
||||
return graph.remove(node.id);
|
||||
}, 'removed a feature');
|
||||
map.update();
|
||||
iD.operations.remove = function(node) {
|
||||
return function(graph) {
|
||||
return graph.remove(node, 'removed a feature');
|
||||
};
|
||||
};
|
||||
|
||||
iD.operations.changeWayNodes = function(map, way, node) {
|
||||
map.graph.modify(function(graph) {
|
||||
var o = {};
|
||||
iD.operations.changeWayNodes = function(way, node) {
|
||||
return function(graph) {
|
||||
way.nodes = way.nodes.slice();
|
||||
o[way.id] = pdata.object(way).get();
|
||||
o[node.id] = node;
|
||||
return graph.set(o);
|
||||
}, 'added to a road');
|
||||
map.update();
|
||||
way = pdata.object(way).get();
|
||||
return graph.replace(way).replace(node, 'added to a road');
|
||||
};
|
||||
};
|
||||
|
||||
iD.operations.addTemporary = function(map, node) {
|
||||
map.graph.modify(function(graph) {
|
||||
var o = {};
|
||||
o[node.id] = node;
|
||||
return graph.set(o);
|
||||
}, '');
|
||||
map.update();
|
||||
iD.operations.changeTags = function(node, tags) {
|
||||
return function(graph) {
|
||||
var node = pdata.object(node).set({ tags: tags }).get();
|
||||
return graph.replace(node, 'changed tags');
|
||||
};
|
||||
};
|
||||
|
||||
iD.operations.changeTags = function(map, node, tags) {
|
||||
map.graph.modify(function(graph) {
|
||||
var o = {};
|
||||
var copy = pdata.object(node).set({ tags: tags }).get();
|
||||
o[copy.id] = copy;
|
||||
return graph.set(o);
|
||||
}, 'changed tags');
|
||||
map.update();
|
||||
iD.operations.addTemporary = function(node) {
|
||||
return function(graph) {
|
||||
return graph.replace(node);
|
||||
};
|
||||
};
|
||||
|
||||
iD.operations.removeTemporary = function(map, node) {
|
||||
map.graph.modify(function(graph) {
|
||||
return graph.remove(node.id);
|
||||
}, '');
|
||||
map.update();
|
||||
iD.operations.removeTemporary = function(node) {
|
||||
return function(graph) {
|
||||
return graph.remove(node);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1 +1,19 @@
|
||||
iD.controller = {};
|
||||
// A controller holds a single action at a time and calls `.enter` and `.exit`
|
||||
// to bind and unbind actions.
|
||||
iD.controller = function(map) {
|
||||
var controller = { action: null };
|
||||
|
||||
controller.go = function(x) {
|
||||
x.controller = controller;
|
||||
x.map = map;
|
||||
if (controller.action) {
|
||||
controller.action.exit();
|
||||
}
|
||||
x.enter();
|
||||
controller.action = x;
|
||||
};
|
||||
|
||||
controller.go(iD.actions.Move);
|
||||
|
||||
return controller;
|
||||
};
|
||||
|
||||
@@ -1,102 +1,60 @@
|
||||
iD.Graph = function() { };
|
||||
iD.Graph = function(entities, annotation) {
|
||||
this.entities = entities || {};
|
||||
this.annotation = annotation;
|
||||
};
|
||||
|
||||
iD.Graph.prototype = {
|
||||
|
||||
// a pointer to the top of the stack.
|
||||
head: {},
|
||||
// a pointer to the latest annotation
|
||||
annotation: null,
|
||||
|
||||
// stack of previous versions of this datastructure
|
||||
prev: [],
|
||||
// stack of previous annotations
|
||||
annotations: [],
|
||||
entity: function(id) {
|
||||
return this.entities[id];
|
||||
},
|
||||
|
||||
// get all points that are not part of a way. this is an expensive
|
||||
// call that needs to be optimized.
|
||||
pois: function(head) {
|
||||
pois: function() {
|
||||
var included = [], pois = [], idx = {};
|
||||
for (var i in head) {
|
||||
if (head[i].nodes) {
|
||||
included = included.concat(head[i].nodes);
|
||||
for (var i in this.entities) {
|
||||
if (this.entities[i].nodes) {
|
||||
included = included.concat(this.entities[i].nodes);
|
||||
}
|
||||
}
|
||||
for (var j = 0; j < included.length; j++) { idx[included[j]] = true; }
|
||||
for (var k in head) {
|
||||
if (head[k].type === 'node' && !idx[head[k].id]) {
|
||||
pois.push(head[k]);
|
||||
for (var k in this.entities) {
|
||||
if (this.entities[k].type === 'node' && !idx[this.entities[k].id]) {
|
||||
pois.push(this.entities[k]);
|
||||
}
|
||||
}
|
||||
return pois;
|
||||
},
|
||||
|
||||
// rewind and fast-forward the graph. these preserve the other modes of the
|
||||
// graph. these attempt to skip over any edits that didn't have an annotation,
|
||||
// like 'invisible edits' and sub-edits.
|
||||
undo: function() {
|
||||
if (this.prev.length && this.prev[0] !== this.head) {
|
||||
for (var idx = this.prev.indexOf(this.head) - 1; idx > 0; idx--) {
|
||||
if (this.annotations[idx]) break;
|
||||
}
|
||||
this.head = this.prev[idx];
|
||||
this.annotation = this.annotations[idx];
|
||||
}
|
||||
},
|
||||
redo: function() {
|
||||
if (this.prev.length && this.prev[this.prev.length - 1] !== this.head) {
|
||||
for (var idx = this.prev.indexOf(this.head) + 1; idx < this.prev.length - 1; idx++) {
|
||||
if (this.annotations[idx]) break;
|
||||
}
|
||||
this.head = this.prev[idx];
|
||||
this.annotation = this.annotations[idx];
|
||||
}
|
||||
},
|
||||
|
||||
insert: function(a) {
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (this.head[a[i].id]) return;
|
||||
this.head[a[i].id] = a[i];
|
||||
if (this.entities[a[i].id]) return;
|
||||
this.entities[a[i].id] = a[i];
|
||||
}
|
||||
},
|
||||
|
||||
// the gist of all operations on the graph: the callback function
|
||||
// receives the current graph and returns a modified graph. the graph
|
||||
// given to the callback is guaranteed to be immutable at one level - the
|
||||
// key -> object mappings. the callback is responsible for keeping objects
|
||||
// in the graph immutable.
|
||||
modify: function(callback, annotation) {
|
||||
// create a pdata wrapper of current head
|
||||
var o = pdata.object(this.head);
|
||||
replace: function(entity, annotation) {
|
||||
var o = {};
|
||||
o[entity.id] = entity;
|
||||
return new iD.Graph(pdata.object(this.entities).set(o).get(), annotation);
|
||||
},
|
||||
|
||||
// Archive current version
|
||||
this.prev.push(o.get());
|
||||
|
||||
// Let the operation make modification of a safe
|
||||
// copy
|
||||
var modified = callback(o);
|
||||
|
||||
// Archive this version
|
||||
this.prev.push(modified.get());
|
||||
// Annotate this version
|
||||
this.annotations.push(annotation);
|
||||
|
||||
// Make head the top of the previous stack
|
||||
this.head = this.prev[this.prev.length - 1];
|
||||
this.annotation = this.annotations[this.annotations.length - 1];
|
||||
remove: function(entity, annotation) {
|
||||
return new iD.Graph(pdata.object(this.entities).remove(entity.id).get(), annotation);
|
||||
},
|
||||
|
||||
// get all objects that intersect an extent.
|
||||
intersects: function(extent) {
|
||||
var items = [];
|
||||
for (var i in this.head) {
|
||||
if (this.head[i]) items.push(this.head[i]);
|
||||
for (var i in this.entities) {
|
||||
if (this.entities[i]) items.push(this.entities[i]);
|
||||
}
|
||||
return items;
|
||||
},
|
||||
|
||||
// Resolve the id references in a way, replacing them with actual objects.
|
||||
fetch: function(id) {
|
||||
var o = this.head[id];
|
||||
var o = this.entities[id];
|
||||
var f = _.clone(o);
|
||||
if (!f.nodes || !f.nodes.length) return f;
|
||||
f.nodes = f.nodes.map(function(c) {
|
||||
|
||||
38
js/iD/graph/History.js
Normal file
38
js/iD/graph/History.js
Normal file
@@ -0,0 +1,38 @@
|
||||
iD.History = function() {
|
||||
this.stack = [new iD.Graph()];
|
||||
this.index = 0;
|
||||
};
|
||||
|
||||
iD.History.prototype = {
|
||||
graph: function() {
|
||||
return this.stack[this.index];
|
||||
},
|
||||
|
||||
do: function(operation) {
|
||||
this.stack = this.stack.slice(0, this.index + 1);
|
||||
this.stack.push(operation(this.graph()));
|
||||
this.index++;
|
||||
},
|
||||
|
||||
undo: function() {
|
||||
while (this.index > 0) {
|
||||
this.index--;
|
||||
if (this.stack[this.index].annotation) break;
|
||||
}
|
||||
},
|
||||
|
||||
redo: function() {
|
||||
while (this.index < this.stack.length - 1) {
|
||||
this.index++;
|
||||
if (this.stack[this.index].annotation) break;
|
||||
}
|
||||
},
|
||||
|
||||
entity: function(id) {
|
||||
return this.graph().entity(id);
|
||||
},
|
||||
|
||||
fetch: function(id) {
|
||||
return this.graph().fetch(id);
|
||||
}
|
||||
};
|
||||
@@ -22,9 +22,9 @@ iD.Map = function(elem) {
|
||||
width, height,
|
||||
dispatch = d3.dispatch('move', 'update'),
|
||||
// data
|
||||
graph = new iD.Graph(),
|
||||
connection = new iD.Connection(graph),
|
||||
inspector = iD.Inspector(graph),
|
||||
history = new iD.History(),
|
||||
connection = new iD.Connection(history.graph()),
|
||||
inspector = iD.Inspector(history),
|
||||
parent = d3.select(elem),
|
||||
selection = [],
|
||||
projection = d3.geo.mercator()
|
||||
@@ -38,13 +38,12 @@ iD.Map = function(elem) {
|
||||
// this is used with handles
|
||||
dragbehavior = d3.behavior.drag()
|
||||
.origin(function(d) {
|
||||
var data = (typeof d === 'string') ? graph.head[d] : d;
|
||||
graph.modify(function(o) {
|
||||
var c = {};
|
||||
c[data.id] = pdata.object(data).set({ modified: true }).get();
|
||||
return o.set(c);
|
||||
}, '');
|
||||
p = projection(ll2a(data));
|
||||
var entity = (typeof d === 'string') ? history.entity(d) : d;
|
||||
history.do(function(graph) {
|
||||
var node = pdata.object(entity).set({ modified: true }).get();
|
||||
return graph.replace(node);
|
||||
});
|
||||
var p = projection(ll2a(entity));
|
||||
return { x: p[0], y: p[1] };
|
||||
})
|
||||
.on('drag', function(d) {
|
||||
@@ -52,30 +51,27 @@ iD.Map = function(elem) {
|
||||
return 'translate(' + d3.event.x + ',' + d3.event.y + ')';
|
||||
});
|
||||
var ll = projection.invert([d3.event.x, d3.event.y]);
|
||||
graph.head[d].lon = ll[0];
|
||||
graph.head[d].lat = ll[1];
|
||||
history.entity(d).lon = ll[0];
|
||||
history.entity(d).lat = ll[1];
|
||||
drawVector();
|
||||
})
|
||||
.on('dragend', function(d) {
|
||||
var data = (typeof d === 'string') ? graph.head[d] : d;
|
||||
graph.modify(function(o) {
|
||||
var c = {};
|
||||
c[data.id] = pdata.object(c[data.id]).get();
|
||||
o.set(c);
|
||||
return o;
|
||||
}, 'moved an element');
|
||||
var entity = (typeof d === 'string') ? history.entity(d) : d;
|
||||
history.do(function(graph) {
|
||||
return graph.replace(entity, 'moved an element');
|
||||
});
|
||||
map.update();
|
||||
}),
|
||||
// geo
|
||||
linegen = d3.svg.line()
|
||||
.defined(function(d) {
|
||||
return !!graph.head[d];
|
||||
return !!history.entity(d);
|
||||
})
|
||||
.x(function(d) {
|
||||
return projection(ll2a(graph.head[d]))[0];
|
||||
return projection(ll2a(history.entity(d)))[0];
|
||||
})
|
||||
.y(function(d) {
|
||||
return projection(ll2a(graph.head[d]))[1];
|
||||
return projection(ll2a(history.entity(d)))[1];
|
||||
}),
|
||||
// Abstract linegen so that it pulls from `.children`. This
|
||||
// makes it possible to call simply `.attr('d', nodeline)`.
|
||||
@@ -119,7 +115,7 @@ iD.Map = function(elem) {
|
||||
var tileclient = iD.Tiles(tilegroup, projection);
|
||||
|
||||
function drawVector() {
|
||||
var all = graph.intersects(getExtent());
|
||||
var all = history.graph().intersects(getExtent());
|
||||
|
||||
var ways = all.filter(function(a) {
|
||||
return a.type === 'way' && !iD.Way.isClosed(a);
|
||||
@@ -127,7 +123,7 @@ iD.Map = function(elem) {
|
||||
areas = all.filter(function(a) {
|
||||
return a.type === 'way' && iD.Way.isClosed(a);
|
||||
}),
|
||||
points = graph.pois(graph.head);
|
||||
points = history.graph().pois();
|
||||
|
||||
var fills = fill_g.selectAll('path.area').data(areas, key),
|
||||
casings = casing_g.selectAll('path.casing').data(ways, key),
|
||||
@@ -192,7 +188,7 @@ iD.Map = function(elem) {
|
||||
.attr('r', 5)
|
||||
.call(dragbehavior);
|
||||
handles.attr('transform', function(d) {
|
||||
return 'translate(' + projection(ll2a(graph.head[d])) + ')';
|
||||
return 'translate(' + projection(ll2a(history.entity(d))) + ')';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -225,11 +221,11 @@ iD.Map = function(elem) {
|
||||
}
|
||||
|
||||
inspector.on('change', function(d, tags) {
|
||||
iD.operations.changeTags(map, d, tags);
|
||||
map.do(iD.operations.changeTags(d, tags));
|
||||
});
|
||||
|
||||
inspector.on('remove', function(d) {
|
||||
iD.operations.remove(map, d);
|
||||
map.do(iD.operations.remove(d));
|
||||
});
|
||||
|
||||
function zoomPan() {
|
||||
@@ -254,18 +250,23 @@ iD.Map = function(elem) {
|
||||
// -----------
|
||||
var undolabel = d3.select('button#undo small');
|
||||
dispatch.on('update', function() {
|
||||
undolabel.text(graph.annotation);
|
||||
undolabel.text(history.graph().annotation);
|
||||
redraw();
|
||||
});
|
||||
|
||||
function _do(operation) {
|
||||
history.do(operation);
|
||||
map.update();
|
||||
}
|
||||
|
||||
// Undo/redo
|
||||
function undo() {
|
||||
graph.undo();
|
||||
history.undo();
|
||||
map.update();
|
||||
}
|
||||
|
||||
function redo() {
|
||||
graph.redo();
|
||||
history.redo();
|
||||
map.update();
|
||||
}
|
||||
|
||||
@@ -354,9 +355,10 @@ iD.Map = function(elem) {
|
||||
map.projection = projection;
|
||||
map.setSize = setSize;
|
||||
|
||||
map.graph = graph;
|
||||
map.history = history;
|
||||
map.surface = surface;
|
||||
|
||||
map.do = _do;
|
||||
map.undo = undo;
|
||||
map.redo = redo;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user