From 948a5c600c5b49c951e4710c228c3e73ce334a6c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 23 Oct 2012 14:33:22 -0400 Subject: [PATCH 1/3] Eliminate controllerstate abstraction --- css/app.css | 2 +- index.html | 15 +- js/iD/Controller.js | 10 +- js/iD/actions/CreateEntityAction.js | 5 +- js/iD/actions/CreatePOIAction.js | 15 +- js/iD/actions/MoveNodeAction.js | 18 +- js/iD/actions/UndoStack.js | 190 ++++++++++------------ js/iD/actions/UndoableAction.js | 43 +---- js/iD/controller/ControllerState.js | 62 ------- js/iD/controller/edit/EditBaseState.js | 10 +- js/iD/controller/edit/NoSelection.js | 2 +- js/iD/controller/edit/SelectedPOINode.js | 10 +- js/iD/controller/shape/DrawWay.js | 4 +- js/iD/controller/shape/NoSelection.js | 19 ++- js/iD/controller/shape/SelectedPOINode.js | 5 +- js/iD/controller/shape/SelectedWay.js | 5 +- js/iD/renderer/Map.js | 2 +- 17 files changed, 155 insertions(+), 262 deletions(-) delete mode 100755 js/iD/controller/ControllerState.js diff --git a/css/app.css b/css/app.css index a2faca6ea..80010a180 100644 --- a/css/app.css +++ b/css/app.css @@ -77,7 +77,7 @@ table th { } #modebuttons { - width:300px; + width:500px; position:absolute; left:0px; top:0px; diff --git a/index.html b/index.html index 00071bdb8..ed50e5dc4 100755 --- a/index.html +++ b/index.html @@ -12,6 +12,7 @@
+ @@ -86,6 +87,14 @@ require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented controller.setState(new iD.controller.shape.NoSelection()); }); + $('#undo').click(function() { + controller.undoStack.undo(); + }); + + $('#redo').click(function() { + controller.undoStack.redo(); + }); + // ---------------------------------------------------- // Map control handlers @@ -118,7 +127,11 @@ require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented + + Area + +
diff --git a/js/iD/Controller.js b/js/iD/Controller.js index f21f998d8..724ee2534 100755 --- a/js/iD/Controller.js +++ b/js/iD/Controller.js @@ -14,7 +14,7 @@ declare("iD.Controller", [Evented], { constructor:function(map) { // summary: The Controller marshalls ControllerStates and passes events to them. this.map = map; - this.undoStack = new iD.actions.UndoStack(); + this.undoStack = new iD.UndoStack(); this.editorCache = {}; }, @@ -29,17 +29,15 @@ declare("iD.Controller", [Evented], { if (this.state) { this.emit("exitState", { bubbles: true, - cancelable: true, - state: this.state.stateNameAsArray() + cancelable: true }); } - newState.setController(this); + newState.controller = this; this.state = newState; newState.enterState(); this.emit("enterState", { bubbles: true, - cancelable: true, - state: this.state.stateNameAsArray() + cancelable: true }); }, diff --git a/js/iD/actions/CreateEntityAction.js b/js/iD/actions/CreateEntityAction.js index 74ef2400a..48fa94fdd 100644 --- a/js/iD/actions/CreateEntityAction.js +++ b/js/iD/actions/CreateEntityAction.js @@ -16,7 +16,7 @@ declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], { this.setName("Create " + entity.entityType); }, - doAction: function() { + run: function() { // summary: Call out to the specified method (in the Controller) to create the entity. // See undoAction for explanation of special redo handling. if (this.deleteAction!==null) { @@ -28,7 +28,7 @@ declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], { return this.SUCCESS; }, - undoAction: function() { + undo: function() { // summary: Special handling for undoing a create. When undo is called, instead // of simply removing the entity, we work through to make a Delete[Entity]Action, // call that, and store it for later. Then, when this action is called again @@ -43,7 +43,6 @@ declare("iD.actions.CreateEntityAction", [iD.actions.UndoableEntityAction], { // summary: Set the associated delete action (see undoAction for explanation). deleteAction = action; } - }); // ---------------------------------------------------------------------- diff --git a/js/iD/actions/CreatePOIAction.js b/js/iD/actions/CreatePOIAction.js index 7e0659baf..e12afc23b 100644 --- a/js/iD/actions/CreatePOIAction.js +++ b/js/iD/actions/CreatePOIAction.js @@ -17,34 +17,31 @@ declare("iD.actions.CreatePOIAction", [iD.actions.CompositeUndoableAction], { constructor: function(connection, tags, lat, lon) { // summary: Create a new node and set it as a POI. Used by drag-and-drop. Note that the // node is remembered, so that on redo we can just reinstate it. - this.setName('Create POI'); + this.setName('Create POI: ' + iD.Util.friendlyName(tags)); this.connection = connection; this.tags = tags; this.lat = lat; this.lon = lon; }, - doAction:function() { + run: function() { if (this.newNode === null) { this.newNode = this.connection.doCreateNode(this.tags, this.lat, this.lon, _.bind(this.push, this)); } - this.inherited(arguments); this.connection.registerPOI(this.newNode); - return this.SUCCESS; + return true; }, - undoAction:function() { - this.inherited(arguments); + undo: function() { this.connection.unregisterPOI(this.newNode); - return this.SUCCESS; + return true; }, - getNode:function() { + getNode: function() { return this.newNode; } - }); // ---------------------------------------------------------------------- diff --git a/js/iD/actions/MoveNodeAction.js b/js/iD/actions/MoveNodeAction.js index 700db8f8c..5af2f4b5f 100644 --- a/js/iD/actions/MoveNodeAction.js +++ b/js/iD/actions/MoveNodeAction.js @@ -15,7 +15,7 @@ declare("iD.actions.MoveNodeAction", [iD.actions.UndoableEntityAction], { newLon: NaN, setLatLon: null, - constructor:function(node, newLat, newLon, setLatLon) { + constructor: function(node, newLat, newLon, setLatLon) { // summary: Move a node to a new position. this.entity = node; this.newLat = newLat; @@ -24,25 +24,28 @@ declare("iD.actions.MoveNodeAction", [iD.actions.UndoableEntityAction], { this.createTime = new Date().getTime(); }, - doAction:function() { + run: function() { var node = this.entity; this.oldLat = node.lat; this.oldLon = node.lon; - if (this.oldLat==this.newLat && this.oldLon==this.newLon) { return NO_CHANGE; } + if (this.oldLat === this.newLat && + this.oldLon === this.newLon) { + return NO_CHANGE; + } this.setLatLon(this.newLat, this.newLon); this.markDirty(); node.refresh(); - return this.SUCCESS; + return true; }, - undoAction:function() { + undo: function() { this.setLatLon(this.oldLat, this.oldLon); this.markClean(); this.refresh(); - return this.SUCCESS; + return true; }, - mergePrevious:function(prev) { + mergePrevious: function(prev) { if (prev.declaredClass!=this.declaredClass) { return false; } if (prev.entity == this.entity && prev.createTime+1000>this.createTime) { @@ -52,7 +55,6 @@ declare("iD.actions.MoveNodeAction", [iD.actions.UndoableEntityAction], { } return false; } - }); // ---------------------------------------------------------------------- diff --git a/js/iD/actions/UndoStack.js b/js/iD/actions/UndoStack.js index ee0f2ef17..9129dab59 100644 --- a/js/iD/actions/UndoStack.js +++ b/js/iD/actions/UndoStack.js @@ -1,124 +1,104 @@ // iD/actions/UndoStack.js // ** FIXME: a couple of AS3-isms in undoIfAction/removeLastIfAction -define(['dojo/_base/declare'], function(declare){ - // ---------------------------------------------------------------------- // UndoStack base class -declare("iD.actions.UndoStack", null, { - - undoActions: null, - redoActions: null, +if (typeof iD === 'undefined') iD = {}; - FAIL: 0, - SUCCESS: 1, - NO_CHANGE: 2, +iD.UndoStack = function() { - constructor: function() { - // summary: An undo stack. There can be any number of these, but almost all operations will - // take place on the global undo stack - implemented as a singleton-like property of - // the Controller. - this.undoActions=[]; - this.redoActions=[]; - }, - - addAction: function(_action) { - // summary: Do an action, and add it to the undo stack if it succeeded. - var result = _action.doAction(); - switch (result) { - case this.FAIL: - // do something bad - break; + var stack = {}, + undoActions = [], + redoActions = []; - case this.NO_CHANGE: - break; + var FAIL = 0, + SUCCESS = 1, + NO_CHANGE = 2; - case this.SUCCESS: - default: - if (this.undoActions.length>0) { - var previous = this.undoActions[this.undoActions.length-1]; - if (_action.mergePrevious(previous)) { - _action.wasDirty = previous.wasDirty; - _action.connectionWasDirty = previous.connectionWasDirty; - this.undoActions.pop(); - } - } - this.undoActions.push(_action); - this.redoActions=[]; - break; - } - }, + stack.add = function(action) { + // summary: Add an action to the undo stack + if (undoActions.length > 0) { + var previous = undoActions[undoActions.length - 1]; + if (action.mergePrevious(previous)) { + action.wasDirty = previous.wasDirty; + action.connectionWasDirty = previous.connectionWasDirty; + undoActions.pop(); + } + } + undoActions.push(action); + redoActions = []; + }; - breakUndo: function() { - // summary: Wipe the undo stack - typically used after saving. - this.undoActions = []; - this.redoActions = []; - }, + stack.breakUndo = function() { + // summary: Wipe the undo stack - typically used after saving. + undoActions = []; + redoActions = []; + }; - canUndo: function() { - // summary: Are there any items on the undo stack? - return this.undoActions.length > 0; - }, + stack.canUndo = function() { + // summary: Are there any items on the undo stack? + return undoActions.length > 0; + }; - canRedo: function() { - // summary: Are there any redoable actions? - return this.redoActions.length > 0; - }, + stack.canRedo = function() { + // summary: Are there any redoable actions? + return redoActions.length > 0; + }; - undo: function() { - // summary: Undo the most recent action, and add it to the top of the redo stack. - if (!this.undoActions.length) { return; } - var action = undoActions.pop(); - action.undoAction(); - redoActions.push(action); - }, - undoIfAction: function(_action) { - // summary: Undo the most recent action _only_ if it was of a certain type. - // Fixme: isInstanceOf needs to be made into JavaScript. - if (!this.undoActions.length) { return; } - if (this.undoActions[this.undoActions.length-1].isInstanceOf(_action)) { - this.undo(); - return true; - } - return false; - }, - removeLastIfAction: function(_action) { - // summary: Remove the most recent action from the stack _only_ if it was of a certain type. - // Fixme: isInstanceOf needs to be made into JavaScript. - if (this.undoActions.length && this.undoActions[this.undoActions.length-1].isInstanceOf(_action)) { - this.undoActions.pop(); - } - }, + stack.undo = function() { + // summary: Undo the most recent action, and add it to the top of the redo stack. + if (!stack.canUndo()) { return; } + var action = undoActions.pop(); + action.undo(); + redoActions.push(action); + }; - getUndoDescription: function() { - // summary: Get the name of the topmost item on the undo stack. - if (!this.undoActions.length) return null; - if (this.undoActions[this.undoActions.length-1].name) { - return this.undoActions[this.undoActions.length-1].name; - } - return null; - }, + stack.undoIfAction = function(_action) { + // summary: Undo the most recent action _only_ if it was of a certain type. + // Fixme: isInstanceOf needs to be made into JavaScript. + if (!undoActions.length) { return; } + if (undoActions[undoActions.length-1].isInstanceOf(_action)) { + undo(); + return true; + } + return false; + }; - getRedoDescription: function() { - // summary: Get the name of the topmost item on the redo stack. - if (!this.redoActions.length) return null; - if (this.redoActions[this.redoActions.length-1].name) { - return this.redoActions[this.redoActions.length-1].name; - } - return null; - }, + stack.removeLastIfAction = function(_action) { + // summary: Remove the most recent action from the stack _only_ if it was of a certain type. + // Fixme: isInstanceOf needs to be made into JavaScript. + if (undoActions.length && + undoActions[undoActions.length - 1].isInstanceOf(_action)) { + undoActions.pop(); + } + }; - redo: function() { - // summary: Takes the action most recently undone, does it, and adds it to the undo stack. - if (!this.redoActions.length) { return; } - var action = this.redoActions.pop(); - action.doAction(); - this.undoActions.push(action); - } + stack.getUndoDescription = function() { + // summary: Get the name of the topmost item on the undo stack. + if (!undoActions.length) return null; + if (undoActions[undoActions.length - 1].name) { + return undoActions[undoActions.length - 1].name; + } + return null; + }; -}); + stack.getRedoDescription = function() { + // summary: Get the name of the topmost item on the redo stack. + if (!redoActions.length) return null; + if (redoActions[redoActions.length - 1].name) { + return redoActions[redoActions.length - 1].name; + } + return null; + }; -// ---------------------------------------------------------------------- -// End of module -}); + stack.redo = function() { + // summary: Takes the action most recently undone, does it, and adds it to the undo stack. + if (!stack.canRedo()) { return; } + var action = redoActions.pop(); + action.run(); + undoActions.push(action); + }; + + return stack; +}; diff --git a/js/iD/actions/UndoableAction.js b/js/iD/actions/UndoableAction.js index 003c7557a..0971af7e8 100644 --- a/js/iD/actions/UndoableAction.js +++ b/js/iD/actions/UndoableAction.js @@ -7,42 +7,14 @@ define(['dojo/_base/declare'], function(declare){ // UndoableAction base class declare("iD.actions.UndoableAction", null, { - - FAIL: 0, - SUCCESS: 1, - NO_CHANGE: 2, - - name: "", - - constructor:function() { - // summary: An UndoableAction is a user-induced change to the map data (held within the Connection) - // which can be undone. The UndoableAction doesn't actually change the data: it provides - // the logic around the change, but the change itself is generally made by the model. - // Therefore the UndoableAction constructor needs to be passed a reference - // to a method that actually makes the change - for example, MoveNodeAction will be - // passed a reference to Node._setLatLonImmediate. - }, - - doAction:function() { - // summary: Do the action. - return FAIL; - }, - - undoAction:function() { - // summary: Undo the action. - return FAIL; - }, - mergePrevious:function() { // summary: If two successive actions can be merged (in particular, two successive node moves), do that. return false; }, - setName:function(_name) { // summary: Set the name of an action. For UI and debugging purposes. this.name=_name; } - }); // ---------------------------------------------------------------------- @@ -63,14 +35,14 @@ declare("iD.actions.UndoableEntityAction", [iD.actions.UndoableAction], { }, markDirty:function() { - // summary: Mark a change to the entity ('dirtying' it). + // summary: Mark a change to the entity ('dirtying' it). if (!this.initialised) this.init(); if (!this.wasDirty) this.entity._markDirty(); if (!this.connectionWasDirty) this.entity.connection.modified = true; }, markClean:function() { - // summary: If the entity was clean before, revert the dirty flag to that state. + // summary: If the entity was clean before, revert the dirty flag to that state. if (!this.initialised) this.init(); if (!this.wasDirty) this.entity._markClean(); if (!this.connectionWasDirty) this.entity.connection.modified = false; @@ -102,7 +74,7 @@ declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], { // summary: A CompositeUndoableAction is a group of user-induced changes to the map data // (held within the Connection) which are executed, and therefore undone, // in a batch. For example, creating a new node and a new way containing it. - this.actions=[]; + this.actions = []; }, push:function(action) { @@ -112,14 +84,14 @@ declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], { clearActions:function() { // summary: Clear the list of actions. - this.actions=[]; + this.actions = []; }, doAction:function() { // summary: Execute all the actions one-by-one as a transaction (i.e. roll them all back if one fails). if (this.actionsDone) { return this.FAIL; } - var somethingDone=false; - for (var i=0; i Date: Tue, 23 Oct 2012 15:12:14 -0400 Subject: [PATCH 2/3] Less segregation of ways and nodes in renderer code --- index.html | 2 + js/iD/Connection.js | 42 ++++------- js/iD/Way.js | 2 +- js/iD/controller/shape/NoSelection.js | 9 ++- js/iD/renderer/Map.js | 101 +++++++------------------- js/iD/renderer/NodeUI.js | 4 +- js/iD/renderer/WayUI.js | 4 +- 7 files changed, 55 insertions(+), 109 deletions(-) diff --git a/index.html b/index.html index ed50e5dc4..16729d1ee 100755 --- a/index.html +++ b/index.html @@ -89,10 +89,12 @@ require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented $('#undo').click(function() { controller.undoStack.undo(); + map.updateUIs(true, true); }); $('#redo').click(function() { controller.undoStack.redo(); + map.updateUIs(true, true); }); // ---------------------------------------------------- diff --git a/js/iD/Connection.js b/js/iD/Connection.js index 47814ebfd..bfd2b95c6 100755 --- a/js/iD/Connection.js +++ b/js/iD/Connection.js @@ -12,8 +12,7 @@ iD.Connection = function(apiURL) { var nextNode = -1, // next negative ids nextWay = -1, // | nextRelation = -1, // | - nodes = {}, - ways = {}, + entities = {}, relations = {}, pois = {}, modified = false, @@ -24,20 +23,20 @@ iD.Connection = function(apiURL) { function assign(obj) { // summary: Save an entity to the data store. switch (obj.entityType) { - case "node": nodes[obj.id]=obj; break; - case "way": ways[obj.id]=obj; break; - case "relation": relations[obj.id]=obj; break; + case "node": entities[obj.id] = obj; break; + case "way": entities[obj.id] = obj; break; + case "relation": relations[obj.id] = obj; break; } } function getOrCreate(id, type) { // summary: Return an entity if it exists: if not, create an empty one with the given id, and return that. if (type === 'node') { - if (!nodes[id]) assign(new iD.Node(connection, id, NaN, NaN, {}, false)); - return nodes[id]; + if (!entities[id]) assign(new iD.Node(connection, id, NaN, NaN, {}, false)); + return entities[id]; } else if (type === 'way') { - if (!ways[id]) assign(new iD.Way(connection, id, [], {}, false)); - return ways[id]; + if (!entities[id]) assign(new iD.Way(connection, id, [], {}, false)); + return entities[id]; } else if (type === 'relation') { if (!relations[id]) assign(new iD.Relation(connection, id, [], {}, false)); return relations[id]; @@ -66,23 +65,15 @@ iD.Connection = function(apiURL) { } function getObjectsByBbox(left,right,top,bottom) { - // summary: Find all drawable entities that are within a given bounding box. - // returns: Object An object with four properties: .poisInside, .poisOutside, .waysInside, .waysOutside. + // summary: Find all drawable entities that are within a given bounding box. // Each one is an array of entities. var o = { - poisInside: [], - poisOutside: [], - waysInside: [], - waysOutside: [] + inside: [], + outside: [] }; - for (var id in ways) { - var way = ways[id]; - if (way.within(left,right,top,bottom)) { o.waysInside.push(way); } - else { o.waysOutside.push(way); } - } - _.each(pois, function(node) { - if (node.within(left,right,top,bottom)) { o.poisInside.push(node); } - else { o.poisOutside.push(node); } + _.each(this.entities, function(e, id) { + if (e.within(left, right, top, bottom)) { o.inside.push(e); } + else { o.outside.push(e); } }); return o; } @@ -183,7 +174,7 @@ iD.Connection = function(apiURL) { return _(obj.childNodes).chain() .filter(filterNodeName('nd')) .map(function(item) { - return nodes[getAttribute(item,'ref')]; + return entities[getAttribute(item,'ref')]; }).value(); } @@ -202,8 +193,7 @@ iD.Connection = function(apiURL) { }; } - connection.nodes = nodes; - connection.ways = ways; + connection.entities = entities; connection.relations = relations; connection.loadFromAPI = loadFromAPI; connection.loadFromURL = loadFromURL; diff --git a/js/iD/Way.js b/js/iD/Way.js index 3a66a1a46..fecf79f19 100644 --- a/js/iD/Way.js +++ b/js/iD/Way.js @@ -48,7 +48,7 @@ iD.Way.prototype = { // --------------------- // Bounding-box handling - within:function(left,right,top,bottom) { + within: function(left,right,top,bottom) { // TODO invert and just return if (!this.extent.west || (this.extent.west < left && this.extent.east < left ) || diff --git a/js/iD/controller/shape/NoSelection.js b/js/iD/controller/shape/NoSelection.js index 70009a347..7d6cc6765 100644 --- a/js/iD/controller/shape/NoSelection.js +++ b/js/iD/controller/shape/NoSelection.js @@ -47,6 +47,7 @@ declare("iD.controller.shape.NoSelection", null, { var entity = entityUI ? entityUI.entity : null; var entityType = entity ? entity.entityType : null; var map = this.controller.map; + var connection = map.connection; if (event.type === 'click') { if (entityType === 'node') { @@ -65,18 +66,18 @@ declare("iD.controller.shape.NoSelection", null, { if (this.intent === 'way') { // Click to start a new way var undo = new iD.actions.CompositeUndoableAction(); - var startNode = this.getConnection().doCreateNode({}, + var startNode = connection.doCreateNode({}, map.coord2lat(map.mouseY(event)), map.coord2lon(map.mouseX(event)), _.bind(undo.push, undo) ); - var way = this.getConnection().doCreateWay({}, [startNode], _.bind(undo.push, undo) ); + var way = connection.doCreateWay({}, [startNode], _.bind(undo.push, undo) ); this.controller.undoStack.addAction(undo); this.controller.map.createUI(way); return new iD.controller.shape.DrawWay(way); } else if (this.intent === 'node') { - var action = new iD.actions.CreatePOIAction(this.getConnection(), {}, + var action = new iD.actions.CreatePOIAction(connection, {}, map.coord2lat(map.mouseY(event)), map.coord2lon(map.mouseX(event))); - if (action.doAction()) { + if (action.run()) { this.controller.undoStack.add(action); var node = action.getNode(); this.controller.map.createUI(node); diff --git a/js/iD/renderer/Map.js b/js/iD/renderer/Map.js index 114148b5b..0e7ccf250 100755 --- a/js/iD/renderer/Map.js +++ b/js/iD/renderer/Map.js @@ -27,8 +27,7 @@ declare("iD.renderer.Map", null, { backdrop: null, // coloured backdrop (MapCSS canvas element) connection: null, // data store controller: null, // UI controller - nodeuis: {}, // graphic representations of data - wayuis: {}, // | + uis: {}, tilegroup: null, // group within container for adding bitmap tiles tiles: {}, // index of tile objects @@ -68,8 +67,7 @@ declare("iD.renderer.Map", null, { this.mapheight = obj.height ? obj.height : 400; // Initialise variables - this.nodeuis = {}, - this.wayuis = {}, + this.uis = {}; this.div=document.getElementById(obj.div); this.surface=Gfx.createSurface(obj.div, this.mapwidth, this.mapheight); this.backdrop=this.surface.createRect({ @@ -171,57 +169,40 @@ declare("iD.renderer.Map", null, { return sub; } } - sub=collection.createGroup().moveToFront(); + sub = collection.createGroup().moveToFront(); sub.sublayer=sublayer; return sub; // dojox.gfx.Group }, - createUI: function(entity, stateClasses) { + createUI: function(e, stateClasses) { // summary: Create a UI (sprite) for an entity, assigning any specified state classes // (temporary attributes such as ':hover' or ':selected') - var id = entity.id; - if (entity.entityType === 'node') { - if (!this.nodeuis[id]) { - this.nodeuis[id] = new iD.renderer.NodeUI(entity,this,stateClasses); - } else { - this.nodeuis[id].setStateClasses(stateClasses).redraw(); + if (!this.uis[e.id]) { + if (e.entityType === 'node') { + this.uis[e.id] = new iD.renderer.NodeUI(e, this, stateClasses); + } else if (e.entityType === 'way') { + this.uis[e.id] = new iD.renderer.WayUI(e, this, stateClasses); } - return this.nodeuis[id]; // iD.renderer.EntityUI - } else if (entity.entityType === 'way') { - if (!this.wayuis[id]) { - this.wayuis[id] = new iD.renderer.WayUI(entity,this,stateClasses); - } else { - this.wayuis[id].setStateClasses(stateClasses).redraw(); - } - return this.wayuis[id]; // iD.renderer.EntityUI + } else { + this.uis[e.id].setStateClasses(stateClasses).redraw(); } }, - getUI: function(entity) { + getUI: function(e) { // summary: Return the UI for an entity, if it exists. - if (entity.entityType === 'node') { - return this.nodeuis[entity.id]; // iD.renderer.EntityUI - } else if (entity.entityType === 'way') { - return this.wayuis[entity.id]; // iD.renderer.EntityUI - } - return null; + return this.uis[e.id]; // iD.renderer.EntityUI }, - refreshUI: function(entity) { + refreshUI: function(e) { // summary: Redraw the UI for an entity. - if (entity.entityType === 'node') { - if (this.nodeuis[entity.id]) { this.nodeuis[entity.id].redraw(); } - } else if (entity.entityType === 'way') { - if (this.wayuis[entity.id] ) { this.wayuis[entity.id].redraw(); } - } + if (this.uis[e.id]) { this.uis[e.id].redraw(); } }, - deleteUI: function(entity) { + deleteUI: function(e) { // summary: Delete the UI for an entity. - var uis = { node: 'nodeuis', way: 'wayuis' }[entity.entityType]; - if (uis && this[uis][entity.id]) { - this[uis][entity.id].removeSprites(); - delete this[uis][entity.id]; + if (this.uis[e.id]) { + this.uis[e.id].removeSprites(); + delete this.uis[e.id]; } }, @@ -231,49 +212,21 @@ declare("iD.renderer.Map", null, { this.connection.loadFromAPI(this.extent, _.bind(this.updateUIs, this)); }, - updateUIs: function(redraw, remove) { + updateUIs: function() { // summary: Draw/refresh all EntityUIs within the bbox, and remove any others. // redraw: Boolean Should we redraw any UIs that are already present? // remove: Boolean Should we delete any UIs that are no longer in the bbox? $('#progress').hide().removeClass('spinner'); - - var m = this; - var way, poi; var o = this.connection.getObjectsByBbox(this.extent); - - _(o.waysInside).chain() + _(o.inside).chain() .filter(function(w) { return w.loaded; }) - .each(function(way) { - if (!m.wayuis[way.id]) { m.createUI(way); } - else if (redraw) { m.wayuis[way.id].recalculate(); m.wayuis[way.id].redraw(); } - }); - - if (remove !== false) { - _.each(o.waysOutside, function(way) { - if (m.wayuis[way.id]) { // && !m.wayuis[way.id].purgable - if (redraw) { - m.wayuis[way.id].recalculate(); - m.wayuis[way.id].redraw(); - } - } else { - m.deleteUI(way); + .each(_.bind(function(e) { + if (!this.uis[e.id]) { + this.createUI(e); + } else { + this.uis[e.id].redraw(); } - }); - } - - _.each(o.poisInside, function(poi) { - if (!poi.loaded) return; - if (!m.nodeuis[poi.id]) { m.createUI(poi); } - else if (redraw) { m.nodeuis[poi.id].redraw(); } - }); - - if (remove !== false) { - _.each(o.poisOutside, function(poi) { - if (m.nodeuis[poi.id]) { // && !m.nodeuis[poi.id].purgable - if (redraw) { m.nodeuis[poi.id].redraw(); } - } else { m.deleteUI(poi); } - }); - } + }, this)); }, // ------------- diff --git a/js/iD/renderer/NodeUI.js b/js/iD/renderer/NodeUI.js index 470627fa7..163fcacbb 100755 --- a/js/iD/renderer/NodeUI.js +++ b/js/iD/renderer/NodeUI.js @@ -1,8 +1,8 @@ // iD/renderer/NodeUI.js // NodeUI classes for iD -define(['dojo/_base/declare','dojo/_base/array','dojox/gfx/_base','iD/renderer/EntityUI'], - function(declare,array,g){ +define(['dojo/_base/declare','dojox/gfx/_base','iD/renderer/EntityUI'], + function(declare, g) { // ---------------------------------------------------------------------- // NodeUI class diff --git a/js/iD/renderer/WayUI.js b/js/iD/renderer/WayUI.js index 7de8cbe81..3ae0def54 100755 --- a/js/iD/renderer/WayUI.js +++ b/js/iD/renderer/WayUI.js @@ -18,7 +18,7 @@ declare("iD.renderer.WayUI", [iD.renderer.EntityUI], { this.redraw(); }, getEnhancedTags: function() { - var tags=this.inherited(arguments); + var tags = this.inherited(arguments); if (this.entity.isClosed()) { tags[':area']='yes'; } return tags; }, @@ -29,7 +29,7 @@ declare("iD.renderer.WayUI", [iD.renderer.EntityUI], { redraw: function() { // summary: Draw the object and add hitzone sprites. var way = this.entity, - maxwidth=4, + maxwidth = 4, i; this.removeSprites(); From c3b6ee472cb236ed01698947e4a804523eec7489 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 23 Oct 2012 15:26:55 -0400 Subject: [PATCH 3/3] Reformat map, starting to work on removing uis --- js/iD/renderer/EntityUI.js | 233 +++++----- js/iD/renderer/Map.js | 873 +++++++++++++++++++------------------ 2 files changed, 556 insertions(+), 550 deletions(-) diff --git a/js/iD/renderer/EntityUI.js b/js/iD/renderer/EntityUI.js index c37f86bf3..b6541bddc 100755 --- a/js/iD/renderer/EntityUI.js +++ b/js/iD/renderer/EntityUI.js @@ -8,126 +8,127 @@ // opacity define(['dojo/_base/declare','iD/Entity','iD/renderer/Map'], - function(declare) { +function(declare) { -// ---------------------------------------------------------------------- -// EntityUI base class + // ---------------------------------------------------------------------- + // EntityUI base class -declare("iD.renderer.EntityUI", null, { + declare("iD.renderer.EntityUI", null, { - entity:null, // Entity this represents - map:null, // the Map object containing this - layer:0, // OSM layer - sprites:null, // array of sprites created for this EntityUI - styleList:null, // current StyleList - stateClasses:null, // list of stateClass tags to apply + entity:null, // Entity this represents + map:null, // the Map object containing this + layer:0, // OSM layer + sprites:null, // array of sprites created for this EntityUI + styleList:null, // current StyleList + stateClasses:null, // list of stateClass tags to apply - constructor:function(entity,map,stateClasses) { - // summary: Base class for a UI representing an entity. - this.entity=entity; - this.map=map; - this.stateClasses=stateClasses ? stateClasses.slice() : []; - this.sprites=[]; - }, - getConnection:function() { - // summary: Get the Connection from where the map draws its data. - return this.map.connection; // iD.Connection - }, - targetGroup:function(groupType,sublayer) { - // summary: Find a gfx.Group to render on. - return this.map.sublayer(this.layer,groupType,sublayer); // dojox.gfx.Group - }, - recordSprite:function(sprite) { - // summary: Record that an individual sprite (one stroke, icon or text item) has been added. - if (!_.include(this.sprites, sprite)) { - this.sprites.push(sprite); + constructor: function(entity,map,stateClasses) { + // summary: Base class for a UI representing an entity. + this.entity=entity; + this.map=map; + this.stateClasses=stateClasses ? stateClasses.slice() : []; + this.sprites=[]; + }, + getConnection: function() { + // summary: Get the Connection from where the map draws its data. + return this.map.connection; // iD.Connection + }, + targetGroup: function(groupType,sublayer) { + // summary: Find a gfx.Group to render on. + return this.map.sublayer(this.layer,groupType,sublayer); // dojox.gfx.Group + }, + recordSprite: function(sprite) { + // summary: Record that an individual sprite (one stroke, icon or text item) has been added. + if (!_.include(this.sprites, sprite)) { + this.sprites.push(sprite); + } + return sprite; + }, + removeSprites: function() { + // summary: Clear all sprites currently used. + for (var i=0; i-1) { + this.stateClasses.splice(this.stateClasses.indexOf(sc),1); + this.invalidateStyleList(); + } + return this; + }, + + hasStateClass:function(sc) { + // summary: Is a particular state class set for this UI? + return this.stateClasses.indexOf(sc) > -1; + }, + + invalidateStyleList:function() { + // summary: Invalidate the StyleList so it's recalculated on next redraw. + this.styleList = null; + }, + + // -------------------- + // Mouse event handling + + entityMouseEvent:function(event) { + // summary: Receive a mouse event (e.g. clicking on the UI), and forward it to the Controller. + this.map.controller.entityMouseEvent(event, event.gfxTarget.source); + event.stopPropagation(); } - return sprite; - }, - removeSprites:function() { - // summary: Clear all sprites currently used. - for (var i=0; i-1) { - this.stateClasses.splice(this.stateClasses.indexOf(sc),1); - this.invalidateStyleList(); - } - return this; - }, - - hasStateClass:function(sc) { - // summary: Is a particular state class set for this UI? - return this.stateClasses.indexOf(sc)>-1; - }, - invalidateStyleList:function() { - // summary: Invalidate the StyleList so it's recalculated on next redraw. - this.styleList=null; - }, - - // -------------------- - // Mouse event handling - - entityMouseEvent:function(event) { - // summary: Receive a mouse event (e.g. clicking on the UI), and forward it to the Controller. - this.map.controller.entityMouseEvent(event, event.gfxTarget.source); - event.stopPropagation(); - } -}); - -// ---------------------------------------------------------------------- -// End of module + // ---------------------------------------------------------------------- + // End of module }); diff --git a/js/iD/renderer/Map.js b/js/iD/renderer/Map.js index 0e7ccf250..875c6f8b3 100755 --- a/js/iD/renderer/Map.js +++ b/js/iD/renderer/Map.js @@ -2,295 +2,300 @@ // at present this combines P2's Map and MapPaint functionality define(['dojo/_base/declare','dojo/_base/event', - 'dojo/dom-geometry', - 'dojox/gfx','dojox/gfx/matrix', - 'iD/Connection','iD/Entity','iD/renderer/EntityUI','iD/renderer/WayUI','iD/renderer/NodeUI'], - function(declare, Event, domGeom, Gfx, Matrix){ + 'dojo/dom-geometry', + 'dojox/gfx','dojox/gfx/matrix', + 'iD/Connection','iD/Entity','iD/renderer/EntityUI','iD/renderer/WayUI','iD/renderer/NodeUI'], +function(declare, Event, domGeom, Gfx, Matrix){ -// ---------------------------------------------------------------------- -// Connection base class + // ---------------------------------------------------------------------- + // Connection base class -declare("iD.renderer.Map", null, { + declare("iD.renderer.Map", null, { - MASTERSCALE: 5825.4222222222, - MINSCALE: 14, - MAXSCALE: 23, - zoom: NaN, - zoomfactor: NaN, - baselon: NaN, // original longitude at top left of viewport - baselat: NaN, // original latitude at top left of viewport - baselatp: NaN, // original projected latitude at top left of viewport + MASTERSCALE: 5825.4222222222, + MINSCALE: 14, + MAXSCALE: 23, + zoom: NaN, + zoomfactor: NaN, + baselon: NaN, // original longitude at top left of viewport + baselat: NaN, // original latitude at top left of viewport + baselatp: NaN, // original projected latitude at top left of viewport - div: '', //
of this map - surface: null, //
.surface containing the rendering - container: null, // root-level group within the surface - backdrop: null, // coloured backdrop (MapCSS canvas element) - connection: null, // data store - controller: null, // UI controller - uis: {}, + div: '', //
of this map + surface: null, //
.surface containing the rendering + container: null, // root-level group within the surface + backdrop: null, // coloured backdrop (MapCSS canvas element) + connection: null, // data store + controller: null, // UI controller + uis: {}, - tilegroup: null, // group within container for adding bitmap tiles - tiles: {}, // index of tile objects - tilebaseURL: 'http://ecn.t0.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&mkt=en-gb&n=z', // Bing imagery URL + tilegroup: null, // group within container for adding bitmap tiles + tiles: {}, // index of tile objects + tilebaseURL: 'http://ecn.t0.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&mkt=en-gb&n=z', // Bing imagery URL - dragging: false, // current drag state - dragged: false, // was most recent click a drag? - dragx: NaN, // click co-ordinates at previously recorded drag event - dragy: NaN, // | - startdragx: NaN, // click co-ordinates at start of drag - startdragy: NaN, // | - dragtime: NaN, // timestamp of mouseup (compared to stop resulting click from firing) - dragconnect: null, // event listener for endDrag + dragging: false, // current drag state + dragged: false, // was most recent click a drag? + dragx: NaN, // click co-ordinates at previously recorded drag event + dragy: NaN, // | + startdragx: NaN, // click co-ordinates at start of drag + startdragy: NaN, // | + dragtime: NaN, // timestamp of mouseup (compared to stop resulting click from firing) + dragconnect: null, // event listener for endDrag - containerx: 0, // screen co-ordinates of container - containery: 0, // | - centrelat: NaN, // lat/long and bounding box of map - centrelon: NaN, // | - extent: {}, // | - mapheight: NaN, // size of map object in pixels - mapwidth: NaN, // | + containerx: 0, // screen co-ordinates of container + containery: 0, // | + centrelat: NaN, // lat/long and bounding box of map + centrelon: NaN, // | + extent: {}, // | + mapheight: NaN, // size of map object in pixels + mapwidth: NaN, // | - layers: null, // array-like object of Groups, one for each OSM layer - minlayer: -5, // minimum OSM layer supported - maxlayer: 5, // maximum OSM layer supported + layers: null, // array-like object of Groups, one for each OSM layer + minlayer: -5, // minimum OSM layer supported + maxlayer: 5, // maximum OSM layer supported - elastic: null, // Group for drawing elastic band + elastic: null, // Group for drawing elastic band - ruleset: null, // map style + ruleset: null, // map style - constructor: function(obj) { - // summary: The main map display, containing the individual sprites (UIs) for each entity. - // obj: Object An object containing .lat, .lon, .scale, .div (the name of the
to be used), - // .connection, .width (px) and .height (px) properties. + constructor: function(obj) { + // summary: The main map display, containing the individual sprites (UIs) for each entity. + // obj: Object An object containing .lat, .lon, .scale, .div (the name of the
to be used), + // .connection, .width (px) and .height (px) properties. - this.mapwidth = obj.width ? obj.width : 800; - this.mapheight = obj.height ? obj.height : 400; + this.mapwidth = obj.width ? obj.width : 800; + this.mapheight = obj.height ? obj.height : 400; - // Initialise variables - this.uis = {}; - this.div=document.getElementById(obj.div); - this.surface=Gfx.createSurface(obj.div, this.mapwidth, this.mapheight); - this.backdrop=this.surface.createRect({ - x: 0, - y: 0, - width: this.mapwidth, - height: this.mapheight - }).setFill(new dojo.Color([255,255,245,1])); - this.tilegroup = this.surface.createGroup(); - this.container = this.surface.createGroup(); - this.connection = obj.connection; - this.zoom = obj.zoom ? obj.zoom : 17; - this.baselon = obj.lon; - this.baselat = obj.lat; - this.baselatp = this.lat2latp(obj.lat); - this._setScaleFactor(); - this.updateCoordsFromViewportPosition(); + // Initialise variables + this.uis = {}; + this.div=document.getElementById(obj.div); + this.surface=Gfx.createSurface(obj.div, this.mapwidth, this.mapheight); + this.backdrop=this.surface.createRect({ + x: 0, + y: 0, + width: this.mapwidth, + height: this.mapheight + }).setFill(new dojo.Color([255,255,245,1])); + this.tilegroup = this.surface.createGroup(); + this.container = this.surface.createGroup(); + this.connection = obj.connection; + this.zoom = obj.zoom ? obj.zoom : 17; + this.baselon = obj.lon; + this.baselat = obj.lat; + this.baselatp = this.lat2latp(obj.lat); + this._setScaleFactor(); + this.updateCoordsFromViewportPosition(); - // Cache the margin box, since this is expensive. - this.marginBox = domGeom.getMarginBox(this.div); + // Cache the margin box, since this is expensive. + this.marginBox = domGeom.getMarginBox(this.div); - // Initialise layers - this.layers={}; - for (var l=this.minlayer; l<=this.maxlayer; l++) { - var r=this.container.createGroup(); - this.layers[l]={ - root: r, - fill: r.createGroup(), - casing: r.createGroup(), - stroke: r.createGroup(), - text: r.createGroup(), - hit: r.createGroup() - }; - } - - // Create group for elastic band - this.elastic = this.container.createGroup(); - - // Make draggable - this.backdrop.connect("onmousedown", _.bind(this.startDrag, this)); - this.tilegroup.connect("onmousedown", _.bind(this.startDrag, this)); - this.surface.connect("onclick", _.bind(this.clickSurface, this)); - this.surface.connect("onmousemove", _.bind(this.processMove, this)); - this.surface.connect("onmousedown", _.bind(this._mouseEvent, this)); - this.surface.connect("onmouseup", _.bind(this._mouseEvent, this)); - }, - - setController:function(controller) { - // summary: Set the controller that will handle events on the map (e.g. mouse clicks). - this.controller = controller; - }, - - _moveToPosition:function(group, position) { - // summary: Supplementary method for dojox.gfx. - // This should ideally be core Dojo stuff: see http://bugs.dojotoolkit.org/ticket/15296 - var parent=group.getParent(); - if (!parent) { return; } - this._moveChildToPosition(parent,group,position); - if (position === group.rawNode.parentNode.childNodes.length) { - group.rawNode.parentNode.appendChild(group.rawNode); - } else { - group.rawNode.parentNode.insertBefore(group.rawNode, group.rawNode.parentNode.childNodes[position]); - } - }, - - _moveChildToPosition: function(parent, child, position) { - for (var i = 0; i < parent.children.length; ++i){ - if (parent.children[i] === child){ - parent.children.splice(i, 1); - parent.children.splice(position, 0, child); - break; - } - } - }, - - // ---------------------------- - // Sprite and EntityUI handling - - sublayer:function(layer,groupType,sublayer) { - // summary: Find the gfx.Group for a given OSM layer and rendering sublayer, creating it - // if necessary. Note that sublayers are only implemented for stroke and fill. - // groupType: String 'casing','text','hit','stroke', or 'fill' - var collection = this.layers[layer][groupType], sub; - switch (groupType) { - case 'casing': - case 'text': - case 'hit': - return collection; - } - // Find correct sublayer, inserting if necessary - var insertAt=collection.children.length; - for (var i = 0; i < collection.children.length; i++) { - sub=collection.children[i]; - if (sub.sublayer==sublayer) { return sub; } - else if (sub.sublayer>sublayer) { - sub = collection.createGroup(); - this._moveToPosition(sub,i); - sub.sublayer=sublayer; - return sub; - } - } - sub = collection.createGroup().moveToFront(); - sub.sublayer=sublayer; - return sub; // dojox.gfx.Group - }, - - createUI: function(e, stateClasses) { - // summary: Create a UI (sprite) for an entity, assigning any specified state classes - // (temporary attributes such as ':hover' or ':selected') - if (!this.uis[e.id]) { - if (e.entityType === 'node') { - this.uis[e.id] = new iD.renderer.NodeUI(e, this, stateClasses); - } else if (e.entityType === 'way') { - this.uis[e.id] = new iD.renderer.WayUI(e, this, stateClasses); + // Initialise layers + this.layers={}; + for (var l=this.minlayer; l<=this.maxlayer; l++) { + var r=this.container.createGroup(); + this.layers[l]={ + root: r, + fill: r.createGroup(), + casing: r.createGroup(), + stroke: r.createGroup(), + text: r.createGroup(), + hit: r.createGroup() + }; } - } else { - this.uis[e.id].setStateClasses(stateClasses).redraw(); - } - }, - getUI: function(e) { - // summary: Return the UI for an entity, if it exists. - return this.uis[e.id]; // iD.renderer.EntityUI - }, + // Create group for elastic band + this.elastic = this.container.createGroup(); - refreshUI: function(e) { - // summary: Redraw the UI for an entity. - if (this.uis[e.id]) { this.uis[e.id].redraw(); } - }, + // Make draggable + this.backdrop.connect("onmousedown", _.bind(this.startDrag, this)); + this.tilegroup.connect("onmousedown", _.bind(this.startDrag, this)); + this.surface.connect("onclick", _.bind(this.clickSurface, this)); + this.surface.connect("onmousemove", _.bind(this.processMove, this)); + this.surface.connect("onmousedown", _.bind(this._mouseEvent, this)); + this.surface.connect("onmouseup", _.bind(this._mouseEvent, this)); + }, - deleteUI: function(e) { - // summary: Delete the UI for an entity. - if (this.uis[e.id]) { - this.uis[e.id].removeSprites(); - delete this.uis[e.id]; - } - }, + setController:function(controller) { + // summary: Set the controller that will handle events on the map (e.g. mouse clicks). + this.controller = controller; + }, - download: function() { - // summary: Ask the connection to download data for the current viewport. - $('#progress').show().addClass('spinner'); - this.connection.loadFromAPI(this.extent, _.bind(this.updateUIs, this)); - }, + _moveToPosition:function(group, position) { + // summary: Supplementary method for dojox.gfx. + // This should ideally be core Dojo stuff: see http://bugs.dojotoolkit.org/ticket/15296 + var parent=group.getParent(); + if (!parent) { return; } + this._moveChildToPosition(parent,group,position); + if (position === group.rawNode.parentNode.childNodes.length) { + group.rawNode.parentNode.appendChild(group.rawNode); + } else { + group.rawNode.parentNode.insertBefore(group.rawNode, group.rawNode.parentNode.childNodes[position]); + } + }, - updateUIs: function() { - // summary: Draw/refresh all EntityUIs within the bbox, and remove any others. - // redraw: Boolean Should we redraw any UIs that are already present? - // remove: Boolean Should we delete any UIs that are no longer in the bbox? - $('#progress').hide().removeClass('spinner'); - var o = this.connection.getObjectsByBbox(this.extent); - _(o.inside).chain() - .filter(function(w) { return w.loaded; }) - .each(_.bind(function(e) { - if (!this.uis[e.id]) { - this.createUI(e); - } else { - this.uis[e.id].redraw(); + _moveChildToPosition: function(parent, child, position) { + for (var i = 0; i < parent.children.length; ++i){ + if (parent.children[i] === child){ + parent.children.splice(i, 1); + parent.children.splice(position, 0, child); + break; } + } + }, + + // ---------------------------- + // Sprite and EntityUI handling + + sublayer:function(layer,groupType,sublayer) { + // summary: Find the gfx.Group for a given OSM layer and rendering sublayer, creating it + // if necessary. Note that sublayers are only implemented for stroke and fill. + // groupType: String 'casing','text','hit','stroke', or 'fill' + var collection = this.layers[layer][groupType], sub; + switch (groupType) { + case 'casing': + case 'text': + case 'hit': + return collection; + } + // Find correct sublayer, inserting if necessary + var insertAt=collection.children.length; + for (var i = 0; i < collection.children.length; i++) { + sub=collection.children[i]; + if (sub.sublayer==sublayer) { return sub; } + else if (sub.sublayer>sublayer) { + sub = collection.createGroup(); + this._moveToPosition(sub,i); + sub.sublayer=sublayer; + return sub; + } + } + sub = collection.createGroup().moveToFront(); + sub.sublayer=sublayer; + return sub; // dojox.gfx.Group + }, + + createUI: function(e, stateClasses) { + // summary: Create a UI (sprite) for an entity, assigning any specified state classes + // (temporary attributes such as ':hover' or ':selected') + if (!this.uis[e.id]) { + if (e.entityType === 'node') { + this.uis[e.id] = new iD.renderer.NodeUI(e, this, stateClasses); + } else if (e.entityType === 'way') { + this.uis[e.id] = new iD.renderer.WayUI(e, this, stateClasses); + } + } else { + this.uis[e.id].setStateClasses(stateClasses).redraw(); + } + }, + + getUI: function(e) { + // summary: Return the UI for an entity, if it exists. + return this.uis[e.id]; // iD.renderer.EntityUI + }, + + refreshUI: function(e) { + // summary: Redraw the UI for an entity. + if (this.uis[e.id]) { this.uis[e.id].redraw(); } + }, + + deleteUI: function(e) { + // summary: Delete the UI for an entity. + if (this.uis[e.id]) { + this.uis[e.id].removeSprites(); + delete this.uis[e.id]; + } + }, + + download: function() { + // summary: Ask the connection to download data for the current viewport. + $('#progress').show().addClass('spinner'); + this.connection.loadFromAPI(this.extent, _.bind(this.updateUIs, this)); + }, + + updateUIs: function() { + // summary: Draw/refresh all EntityUIs within the bbox, and remove any others. + // redraw: Boolean Should we redraw any UIs that are already present? + // remove: Boolean Should we delete any UIs that are no longer in the bbox? + $('#progress').hide().removeClass('spinner'); + var o = this.connection.getObjectsByBbox(this.extent); + var touch = _(o.inside).chain() + .filter(function(w) { return w.loaded; }) + .map(_.bind(function(e) { + if (!this.uis[e.id]) { + this.createUI(e); + } else { + this.uis[e.id].redraw(); + } + return '' + e.id; + }, this)).value(); + _.each(_.difference(_.keys(this.uis), touch), _.bind(function(k) { + console.log(k); + this.deleteUI(k); }, this)); - }, + }, - // ------------- - // Zoom handling + // ------------- + // Zoom handling - zoomIn: function() { - // summary: Zoom in by one level (unless maximum reached). - return this.setZoom(this.zoom + 1); - }, + zoomIn: function() { + // summary: Zoom in by one level (unless maximum reached). + return this.setZoom(this.zoom + 1); + }, - zoomOut: function() { - // summary: Zoom out by one level (unless minimum reached). - this.setZoom(this.zoom - 1); - this.download(); - return this; - }, + zoomOut: function() { + // summary: Zoom out by one level (unless minimum reached). + this.setZoom(this.zoom - 1); + this.download(); + return this; + }, - setZoom: function(zoom) { - if (zoom < this.MINSCALE || zoom > this.MAXSCALE) return this; - // summary: Redraw the map at a new zoom level. - this.zoom = zoom; - this._setScaleFactor(); - this._blankTiles(); - this.setCentre({ - lat: this.centrelat, - lon: this.centrelon - }); - this.updateUIs(true, true); - return this; - }, + setZoom: function(zoom) { + if (zoom < this.MINSCALE || zoom > this.MAXSCALE) return this; + // summary: Redraw the map at a new zoom level. + this.zoom = zoom; + this._setScaleFactor(); + this._blankTiles(); + this.setCentre({ + lat: this.centrelat, + lon: this.centrelon + }); + this.updateUIs(true, true); + return this; + }, - _setScaleFactor: function() { - // summary: Calculate the scaling factor for this zoom level. - this.zoomfactor = this.MASTERSCALE/Math.pow(2, 13 - this.zoom); - }, + _setScaleFactor: function() { + // summary: Calculate the scaling factor for this zoom level. + this.zoomfactor = this.MASTERSCALE/Math.pow(2, 13 - this.zoom); + }, - // ---------------------- - // Elastic band redrawing + // ---------------------- + // Elastic band redrawing - clearElastic: function() { - // summary: Remove the elastic band used to draw new ways. - this.elastic.clear(); - }, + clearElastic: function() { + // summary: Remove the elastic band used to draw new ways. + this.elastic.clear(); + }, - drawElastic: function(x1,y1,x2,y2) { - // summary: Draw the elastic band (for new ways) between two points. - this.elastic.clear(); - // **** Next line is SVG-specific - this.elastic.rawNode.setAttribute("pointer-events","none"); - this.elastic.createPolyline( [{ x:x1, y:y1 }, { x:x2, y:y2 }] ).setStroke( { - color: [0, 0, 0, 1], - style: 'Solid', - width: 1 - }); - }, + drawElastic: function(x1,y1,x2,y2) { + // summary: Draw the elastic band (for new ways) between two points. + this.elastic.clear(); + // **** Next line is SVG-specific + this.elastic.rawNode.setAttribute("pointer-events","none"); + this.elastic.createPolyline( [{ x:x1, y:y1 }, { x:x2, y:y2 }] ).setStroke( { + color: [0, 0, 0, 1], + style: 'Solid', + width: 1 + }); + }, - // ------------- - // Tile handling - // ** FIXME: see docs - loadTiles: function() { - // summary: Load all tiles for the current viewport. This is a bare-bones function - // at present: it needs configurable URLs (not just Bing), attribution/logo - // support, and to be 'nudgable' (i.e. adjust the offset). - var tl = this.locationCoord({ + // ------------- + // Tile handling + // ** FIXME: see docs + loadTiles: function() { + // summary: Load all tiles for the current viewport. This is a bare-bones function + // at present: it needs configurable URLs (not just Bing), attribution/logo + // support, and to be 'nudgable' (i.e. adjust the offset). + var tl = this.locationCoord({ lat: this.extent.north, lon: this.extent.west }, this.zoom), @@ -302,208 +307,208 @@ declare("iD.renderer.Map", null, { seen = [], coord = { z: this.zoom }; - for (coord.x = tl.x; coord.x <= br.x; coord.x++) { - for (coord.y = tl.y; coord.y <= br.y; coord.y++) { - if (!this._getTile(coord)) { - this._fetchTile(coord); + for (coord.x = tl.x; coord.x <= br.x; coord.x++) { + for (coord.y = tl.y; coord.y <= br.y; coord.y++) { + if (!this._getTile(coord)) { + this._fetchTile(coord); + } + seen.push(iD.Util.tileKey(coord)); } - seen.push(iD.Util.tileKey(coord)); } - } - _.each(_.without(tileKeys, seen), _.bind(function(key) { - delete this.tiles[key]; - }, this)); - }, + _.each(_.without(tileKeys, seen), _.bind(function(key) { + delete this.tiles[key]; + }, this)); + }, - _fetchTile: function(coord) { - // summary: Load a tile image at the given tile co-ordinates. - var t = this.tilegroup.createImage({ - x: Math.floor(this.lon2coord(this.tile2lon(coord.x))), - y: Math.floor(this.lat2coord(this.tile2lat(coord.y))), - width: 256, - height: 256, - src: this._tileURL(coord) - }); - this._assignTile(coord, t); - }, + _fetchTile: function(coord) { + // summary: Load a tile image at the given tile co-ordinates. + var t = this.tilegroup.createImage({ + x: Math.floor(this.lon2coord(this.tile2lon(coord.x))), + y: Math.floor(this.lat2coord(this.tile2lat(coord.y))), + width: 256, + height: 256, + src: this._tileURL(coord) + }); + this._assignTile(coord, t); + }, - _getTile: function(coord) { - // summary: See if this tile is already loaded. - return this.tiles[iD.Util.tileKey(coord)]; - }, + _getTile: function(coord) { + // summary: See if this tile is already loaded. + return this.tiles[iD.Util.tileKey(coord)]; + }, - _assignTile: function(coord, t) { - // summary: Store a reference to the tile so we know it's loaded. - this.tiles[iD.Util.tileKey(coord)] = t; - }, + _assignTile: function(coord, t) { + // summary: Store a reference to the tile so we know it's loaded. + this.tiles[iD.Util.tileKey(coord)] = t; + }, - _tileURL: function(coord) { - // summary: Calculate the URL for a tile at the given co-ordinates. - var u = ''; - for (var zoom = coord.z; zoom > 0; zoom--) { - var byte = 0; - var mask = 1 << (zoom - 1); - if ((coord.x & mask) !== 0) byte++; - if ((coord.y & mask) !== 0) byte += 2; - u += byte.toString(); - } - return this.tilebaseURL - .replace('$z', coord.z) - .replace('$x', coord.x) - .replace('$y', coord.y) - .replace('$quadkey', u); - }, + _tileURL: function(coord) { + // summary: Calculate the URL for a tile at the given co-ordinates. + var u = ''; + for (var zoom = coord.z; zoom > 0; zoom--) { + var byte = 0; + var mask = 1 << (zoom - 1); + if ((coord.x & mask) !== 0) byte++; + if ((coord.y & mask) !== 0) byte += 2; + u += byte.toString(); + } + return this.tilebaseURL + .replace('$z', coord.z) + .replace('$x', coord.x) + .replace('$y', coord.y) + .replace('$quadkey', u); + }, - _blankTiles: function() { - // summary: Unload all tiles and remove from the display. - this.tilegroup.clear(); - this.tiles = {}; - }, + _blankTiles: function() { + // summary: Unload all tiles and remove from the display. + this.tilegroup.clear(); + this.tiles = {}; + }, - // ------------------------------------------- - // Co-ordinate management, dragging and redraw + // ------------------------------------------- + // Co-ordinate management, dragging and redraw - startDrag: function(e) { - // summary: Start dragging the map in response to a mouse-down. - // e: MouseEvent The mouse-down event that triggered it. - var srcElement = (e.gfxTarget === this.backdrop) ? - e.gfxTarget : e.gfxTarget.parent; - Event.stop(e); - this.dragging = true; - this.dragged = false; - this.dragx = this.dragy=NaN; - this.startdragx = e.clientX; - this.startdragy = e.clientY; - this.dragconnect = srcElement.connect("onmouseup", _.bind(this.endDrag, this)); - }, + startDrag: function(e) { + // summary: Start dragging the map in response to a mouse-down. + // e: MouseEvent The mouse-down event that triggered it. + var srcElement = (e.gfxTarget === this.backdrop) ? + e.gfxTarget : e.gfxTarget.parent; + Event.stop(e); + this.dragging = true; + this.dragged = false; + this.dragx = this.dragy=NaN; + this.startdragx = e.clientX; + this.startdragy = e.clientY; + this.dragconnect = srcElement.connect("onmouseup", _.bind(this.endDrag, this)); + }, - endDrag: function(e) { - // summary: Stop dragging the map in response to a mouse-up. - // e: MouseEvent The mouse-up event that triggered it. - Event.stop(e); - dojo.disconnect(this.dragconnect); - this.dragging=false; - this.dragtime=e.timeStamp; - this.updateCoordsFromViewportPosition(); - if (Math.abs(e.clientX - this.startdragx) < 3 && - Math.abs(e.clientY - this.startdragy) < 3) { - return; - } - this.download(); - }, + endDrag: function(e) { + // summary: Stop dragging the map in response to a mouse-up. + // e: MouseEvent The mouse-up event that triggered it. + Event.stop(e); + dojo.disconnect(this.dragconnect); + this.dragging=false; + this.dragtime=e.timeStamp; + this.updateCoordsFromViewportPosition(); + if (Math.abs(e.clientX - this.startdragx) < 3 && + Math.abs(e.clientY - this.startdragy) < 3) { + return; + } + this.download(); + }, - processMove: function(e) { - // summary: Drag the map to a new origin. - // e: MouseEvent The mouse-move event that triggered it. - var x = e.clientX; - var y = e.clientY; - if (this.dragging) { - if (this.dragx) { - this.containerx += (x - this.dragx); - this.containery += (y - this.dragy); - this.updateOrigin(); - this.dragged=true; - } - this.dragx = x; - this.dragy = y; - } else { - this.controller.entityMouseEvent(e,null); - } - }, + processMove: function(e) { + // summary: Drag the map to a new origin. + // e: MouseEvent The mouse-move event that triggered it. + var x = e.clientX; + var y = e.clientY; + if (this.dragging) { + if (this.dragx) { + this.containerx += (x - this.dragx); + this.containery += (y - this.dragy); + this.updateOrigin(); + this.dragged=true; + } + this.dragx = x; + this.dragy = y; + } else { + this.controller.entityMouseEvent(e,null); + } + }, - updateOrigin: function() { - // summary: Tell Dojo to update the viewport origin. - this.container.setTransform([Matrix.translate(this.containerx, this.containery)]); - this.tilegroup.setTransform([Matrix.translate(this.containerx, this.containery)]); - }, + updateOrigin: function() { + // summary: Tell Dojo to update the viewport origin. + this.container.setTransform([Matrix.translate(this.containerx, this.containery)]); + this.tilegroup.setTransform([Matrix.translate(this.containerx, this.containery)]); + }, - _mouseEvent: function(e) { - // summary: Catch mouse events on the surface but not the tiles - in other words, - // on drawn items that don't have their own hitzones, like the fill of a shape. - if (e.type=='mousedown') { this.startDrag(e); } - // ** FIXME: we may want to reinstate this at some point... - // this.controller.entityMouseEvent(e,null); - }, + _mouseEvent: function(e) { + // summary: Catch mouse events on the surface but not the tiles - in other words, + // on drawn items that don't have their own hitzones, like the fill of a shape. + if (e.type=='mousedown') { this.startDrag(e); } + // ** FIXME: we may want to reinstate this at some point... + // this.controller.entityMouseEvent(e,null); + }, - updateCoordsFromViewportPosition: function(e) { - // summary: Update centre and bbox from the current viewport origin. - this._updateCoords(this.containerx, this.containery); - }, + updateCoordsFromViewportPosition: function(e) { + // summary: Update centre and bbox from the current viewport origin. + this._updateCoords(this.containerx, this.containery); + }, - setCentre: function(loc) { - // summary: Update centre and bbox to a specified lat/lon. - var coord = this.locationCoord(loc, this.zoom); - this._updateCoords( - -coord.x - this.mapwidth / 2, - -coord.y - this.mapheight / 2); - return this; - }, + setCentre: function(loc) { + // summary: Update centre and bbox to a specified lat/lon. + var coord = this.locationCoord(loc, this.zoom); + this._updateCoords( + -coord.x - this.mapwidth / 2, + -coord.y - this.mapheight / 2); + return this; + }, - setCenter: function(loc) { this.setCentre(loc); }, + setCenter: function(loc) { this.setCentre(loc); }, - _updateCoords:function(x, y) { - // summary: Set centre and bbox. - this.containerx = x; - this.containery = y; - this.updateOrigin(); - this.centrelon = this.coord2lon(-x + this.mapwidth/2); - this.centrelat = this.coord2lat(-y + this.mapheight/2); + _updateCoords:function(x, y) { + // summary: Set centre and bbox. + this.containerx = x; + this.containery = y; + this.updateOrigin(); + this.centrelon = this.coord2lon(-x + this.mapwidth/2); + this.centrelat = this.coord2lat(-y + this.mapheight/2); - this.extent = { - north: this.coord2lat(-y), - south: this.coord2lat(-y + this.mapheight), - west: this.coord2lon(-x), - east: this.coord2lon(-x + this.mapwidth) - }; + this.extent = { + north: this.coord2lat(-y), + south: this.coord2lat(-y + this.mapheight), + west: this.coord2lon(-x), + east: this.coord2lon(-x + this.mapwidth) + }; - this.loadTiles(); - }, + this.loadTiles(); + }, - clickSurface:function(e) { - // summary: Handle a click on an empty area of the map. - if (this.dragged && e.timeStamp==this.dragtime) { return; } - this.controller.entityMouseEvent(e,null); - }, + clickSurface:function(e) { + // summary: Handle a click on an empty area of the map. + if (this.dragged && e.timeStamp==this.dragtime) { return; } + this.controller.entityMouseEvent(e,null); + }, - // ----------------------- - // Co-ordinate conversions + // ----------------------- + // Co-ordinate conversions - latp2coord:function(a) { return -(a-this.baselatp)*this.zoomfactor; }, - coord2latp:function(a) { return a/-this.zoomfactor+this.baselatp; }, - lon2coord:function(a) { return (a-this.baselon)*this.zoomfactor; }, - coord2lon:function(a) { return a/this.zoomfactor+this.baselon; }, - lon2screen:function(a) { return this.lon2coord(a) + this.marginBox.l + this.containerx; }, + latp2coord:function(a) { return -(a-this.baselatp)*this.zoomfactor; }, + coord2latp:function(a) { return a/-this.zoomfactor+this.baselatp; }, + lon2coord:function(a) { return (a-this.baselon)*this.zoomfactor; }, + coord2lon:function(a) { return a/this.zoomfactor+this.baselon; }, + lon2screen:function(a) { return this.lon2coord(a) + this.marginBox.l + this.containerx; }, - lat2latp:function(a) { return 180/Math.PI * Math.log(Math.tan(Math.PI/4+a*(Math.PI/180)/2)); }, - latp2lat:function(a) { return 180/Math.PI * (2 * Math.atan(Math.exp(a*Math.PI/180)) - Math.PI/2); }, - lat2coord:function(a) { return -(this.lat2latp(a)-this.baselatp)*this.zoomfactor; }, - coord2lat:function(a) { return this.latp2lat(a/-this.zoomfactor+this.baselatp); }, - lat2screen:function(a) { return this.lat2coord(a) + this.marginBox.t + this.containery; }, + lat2latp:function(a) { return 180/Math.PI * Math.log(Math.tan(Math.PI/4+a*(Math.PI/180)/2)); }, + latp2lat:function(a) { return 180/Math.PI * (2 * Math.atan(Math.exp(a*Math.PI/180)) - Math.PI/2); }, + lat2coord:function(a) { return -(this.lat2latp(a)-this.baselatp)*this.zoomfactor; }, + coord2lat:function(a) { return this.latp2lat(a/-this.zoomfactor+this.baselatp); }, + lat2screen:function(a) { return this.lat2coord(a) + this.marginBox.t + this.containery; }, - locationCoord: function(ll, z) { - var z2 = Math.pow(2, z), d2r = Math.PI / 180; - return { - z: z, - x: Math.floor((ll.lon + 180) / 360 * z2), - y: Math.floor((1 - Math.log(Math.tan(ll.lat * d2r) + - 1 / Math.cos(ll.lat * d2r)) / Math.PI) / 2 * z2) - }; - }, - lon2tile:function(a) { return (Math.floor((a+180)/360*Math.pow(2,this.zoom))); }, - lat2tile:function(a) { return (Math.floor((1-Math.log(Math.tan(a*Math.PI/180) + 1/Math.cos(a*Math.PI/180))/Math.PI)/2 *Math.pow(2,this.zoom))); }, - tile2lon:function(a) { return (a/Math.pow(2,this.zoom)*360-180); }, - tile2lat:function(a) { - var n=Math.PI-2*Math.PI*a/Math.pow(2,this.zoom); - return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)))); - }, + locationCoord: function(ll, z) { + var z2 = Math.pow(2, z), d2r = Math.PI / 180; + return { + z: z, + x: Math.floor((ll.lon + 180) / 360 * z2), + y: Math.floor((1 - Math.log(Math.tan(ll.lat * d2r) + + 1 / Math.cos(ll.lat * d2r)) / Math.PI) / 2 * z2) + }; + }, + lon2tile:function(a) { return (Math.floor((a+180)/360*Math.pow(2,this.zoom))); }, + lat2tile:function(a) { return (Math.floor((1-Math.log(Math.tan(a*Math.PI/180) + 1/Math.cos(a*Math.PI/180))/Math.PI)/2 *Math.pow(2,this.zoom))); }, + tile2lon:function(a) { return (a/Math.pow(2,this.zoom)*360-180); }, + tile2lat:function(a) { + var n=Math.PI-2*Math.PI*a/Math.pow(2,this.zoom); + return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)))); + }, - // Turn event co-ordinates into map co-ordinates + // Turn event co-ordinates into map co-ordinates - mouseX: function(e) { return e.clientX - this.marginBox.l - this.containerx; }, - mouseY: function(e) { return e.clientY - this.marginBox.t - this.containery; } -}); - -// ---------------------------------------------------------------------- -// End of module + mouseX: function(e) { return e.clientX - this.marginBox.l - this.containerx; }, + mouseY: function(e) { return e.clientY - this.marginBox.t - this.containery; } + }); + + // ---------------------------------------------------------------------- + // End of module });