mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-22 16:19:48 +02:00
Merge pull request #32 from systemed/simple-actions
Refactoring actions and undoing
This commit is contained in:
+1
-1
@@ -77,7 +77,7 @@ table th {
|
||||
}
|
||||
|
||||
#modebuttons {
|
||||
width:300px;
|
||||
width:500px;
|
||||
position:absolute;
|
||||
left:0px;
|
||||
top:0px;
|
||||
|
||||
+16
-1
@@ -12,6 +12,7 @@
|
||||
<div id="appLayout" class="demoLayout">
|
||||
<script type="text/javascript" src="js/lib/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="js/lib/jquery-1.8.2.min.js"></script>
|
||||
<script type="text/javascript" src="js/iD/actions/UndoStack.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Util.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Taginfo.js"></script>
|
||||
<script type="text/javascript" src="js/iD/Node.js"></script>
|
||||
@@ -86,6 +87,16 @@ 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();
|
||||
map.updateUIs(true, true);
|
||||
});
|
||||
|
||||
$('#redo').click(function() {
|
||||
controller.undoStack.redo();
|
||||
map.updateUIs(true, true);
|
||||
});
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Map control handlers
|
||||
|
||||
@@ -118,7 +129,11 @@ require(["dojo/dom-geometry","dojo/dom-class","dojo/on","dojo/dom","dojo/Evented
|
||||
<button id='add-place'>
|
||||
+ Place</button><button id="add-road">
|
||||
+ Road</button><button id="add-area">
|
||||
+ Area</button>
|
||||
+ Area</button><button id="undo">
|
||||
←</button><button id="redo">
|
||||
→</button>
|
||||
|
||||
|
||||
<div id='addPOI'>
|
||||
<table id='dndgrid'>
|
||||
</table>
|
||||
|
||||
+16
-26
@@ -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;
|
||||
|
||||
+4
-6
@@ -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
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
+1
-1
@@ -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 ) ||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
+85
-105
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<this.actions.length; i++) {
|
||||
var somethingDone = false;
|
||||
for (var i = 0; i < this.actions.length; i++) {
|
||||
var action = this.actions[i];
|
||||
var result = action.doAction();
|
||||
switch (result) {
|
||||
@@ -144,7 +116,7 @@ declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], {
|
||||
// summary: Roll back all the actions one-by-one.
|
||||
if (!this.actionsDone) { return this.FAIL; }
|
||||
this._undoFrom(this.actions.length);
|
||||
return this.SUCCESS;
|
||||
return true;
|
||||
},
|
||||
|
||||
_undoFrom:function(index) {
|
||||
@@ -154,7 +126,6 @@ declare("iD.actions.CompositeUndoableAction", [iD.actions.UndoableAction], {
|
||||
}
|
||||
this.actionsDone=false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// iD/controller/ControllerState.js
|
||||
|
||||
define(['dojo/_base/declare'], function(declare) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ControllerState base class
|
||||
|
||||
declare("iD.controller.ControllerState", null, {
|
||||
controller: null, // parent Controller
|
||||
|
||||
constructor:function() {
|
||||
// summary: Base class for ControllerStates.
|
||||
},
|
||||
|
||||
setController:function(_controller) {
|
||||
// summary: Set a reference to the parent Controller.
|
||||
this.controller=_controller;
|
||||
},
|
||||
|
||||
processMouseEvent:function(event,entityUI) {
|
||||
// summary: Process mouse events. Most of the UI handling goes on in here.
|
||||
// returns: iD.controller.ControllerState The ControllerState to move to as a result of the user's actions (or just 'this' for no change).
|
||||
},
|
||||
|
||||
enterState:function() {
|
||||
// summary: Do any work required for entering the ControllerState, such as highlighting the selected entity.
|
||||
},
|
||||
|
||||
exitState:function(newState) {
|
||||
// summary: Do any work required to leave the ControllerState clearly, such as unhighlighting the selected entity.
|
||||
},
|
||||
|
||||
stateName:function() {
|
||||
// summary: Return the name of this state as a string, e.g. 'edit.NoSelection'.
|
||||
// return: String
|
||||
return this.stateNameAsArray.join('.');
|
||||
},
|
||||
|
||||
stateNameAsArray:function() {
|
||||
// summary: Return the name of this state as an array, e.g. ['edit','NoSelection'].
|
||||
// return: Array
|
||||
return this.__proto__.declaredClass.split('.').slice(2);
|
||||
},
|
||||
|
||||
getConnection: function() {
|
||||
// summary: Shorthand to return the Connection associated with this Controller (via its Map object).
|
||||
// return: iD.Connection
|
||||
return this.controller.map.connection;
|
||||
},
|
||||
|
||||
undoAdder:function() {
|
||||
// summary: Shorthand for adding an action to the global undo stack, setting the scope correctly.
|
||||
// return: Function
|
||||
return _.bind(this.controller.undoStack.addAction,
|
||||
this.controller.undoStack);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// End of module
|
||||
});
|
||||
@@ -1,13 +1,13 @@
|
||||
// iD/controller/edit/EditBaseState.js
|
||||
|
||||
define(['dojo/_base/declare','dojo/_base/lang','dojo/_base/array','dojo/on',
|
||||
'dijit/registry','dijit/TooltipDialog','dijit/Dialog','dijit/popup',
|
||||
'iD/controller/ControllerState'], function(declare,lang,array,on,registry){
|
||||
'dijit/registry'
|
||||
], function(declare,lang,array,on,registry){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// EditBaseState class - provides shared UI functions to edit mode states
|
||||
|
||||
declare("iD.controller.edit.EditBaseState", [iD.controller.ControllerState], {
|
||||
declare("iD.controller.edit.EditBaseState", null, {
|
||||
|
||||
editortooltip: null,
|
||||
|
||||
@@ -16,10 +16,8 @@ declare("iD.controller.edit.EditBaseState", [iD.controller.ControllerState], {
|
||||
// object is selected and the user is making changes to it.
|
||||
},
|
||||
|
||||
openEditorTooltip: function(x, y, entity) {
|
||||
openEditorTooltip: function(entity) {
|
||||
// summary: Open the initial 'Edit tags/Edit shape' tooltip.
|
||||
// x: Number Screen co-ordinate.
|
||||
// y: Number Screen co-ordinate.
|
||||
// entity: iD.Entity The entity to be edited.
|
||||
$('.edit-pane h2').text(iD.Util.friendlyName(entity));
|
||||
$('.edit-pane').show().addClass('active');
|
||||
|
||||
@@ -13,7 +13,7 @@ define(['dojo/_base/declare',
|
||||
declare("iD.controller.edit.NoSelection", [iD.controller.edit.EditBaseState], {
|
||||
|
||||
constructor: function() {
|
||||
// summary: In 'Edit object' mode but nothing selected.
|
||||
// summary: In 'Edit object' mode but nothing selected.
|
||||
},
|
||||
|
||||
enterState: function() {
|
||||
|
||||
@@ -20,9 +20,7 @@ define(['dojo/_base/declare','iD/controller/edit/EditBaseState'], function(decla
|
||||
this.nodeUI = map.getUI(this.node);
|
||||
this.nodeUI.setStateClass('selected')
|
||||
.redraw();
|
||||
this.openEditorTooltip(
|
||||
map.lon2screen(this.node.lon),
|
||||
map.lat2screen(this.node.lat), this.node);
|
||||
this.openEditorTooltip(this.node);
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -33,10 +31,10 @@ define(['dojo/_base/declare','iD/controller/edit/EditBaseState'], function(decla
|
||||
return this;
|
||||
},
|
||||
|
||||
processMouseEvent: function(event,entityUI) {
|
||||
processMouseEvent: function(event, entityUI) {
|
||||
if (event.type !== 'click') return this;
|
||||
var entity=entityUI ? entityUI.entity : null;
|
||||
var entityType=entity ? entity.entityType : null;
|
||||
var entity = entityUI ? entityUI.entity : null;
|
||||
var entityType = entity ? entity.entityType : null;
|
||||
switch (entityType) {
|
||||
case null: return new iD.controller.edit.NoSelection();
|
||||
case 'node': return new iD.controller.edit.SelectedPOINode(entityUI.entity);
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
*/
|
||||
|
||||
|
||||
define(['dojo/_base/declare', 'dojo/_base/lang', 'dojox/gfx/shape', 'iD/controller/ControllerState'],
|
||||
define(['dojo/_base/declare', 'dojo/_base/lang', 'dojox/gfx/shape'],
|
||||
function(declare, lang, shape){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// DrawWay class
|
||||
|
||||
declare("iD.controller.shape.DrawWay", [iD.controller.ControllerState], {
|
||||
declare("iD.controller.shape.DrawWay", null, {
|
||||
|
||||
way: null,
|
||||
wayUI: null,
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/actions/UndoableAction',
|
||||
'iD/controller/ControllerState',
|
||||
'iD/controller/shape/DrawWay',
|
||||
'iD/controller/shape/SelectedWay',
|
||||
'iD/controller/shape/SelectedPOINode'
|
||||
@@ -24,7 +23,7 @@ define(['dojo/_base/declare',
|
||||
// ----------------------------------------------------------------------
|
||||
// ControllerState base class
|
||||
|
||||
declare("iD.controller.shape.NoSelection", [iD.controller.ControllerState], {
|
||||
declare("iD.controller.shape.NoSelection", null, {
|
||||
|
||||
constructor: function(intent) {
|
||||
// summary: In 'Draw shape' mode but nothing is selected.
|
||||
@@ -48,6 +47,7 @@ declare("iD.controller.shape.NoSelection", [iD.controller.ControllerState], {
|
||||
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') {
|
||||
@@ -66,24 +66,26 @@ declare("iD.controller.shape.NoSelection", [iD.controller.ControllerState], {
|
||||
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)));
|
||||
this.controller.undoStack.addAction(action);
|
||||
var node = action.getNode();
|
||||
this.controller.map.createUI(node);
|
||||
var state = new iD.controller.edit.SelectedPOINode(node);
|
||||
state.controller = this.controller;
|
||||
state.enterState();
|
||||
return state;
|
||||
if (action.run()) {
|
||||
this.controller.undoStack.add(action);
|
||||
var node = action.getNode();
|
||||
this.controller.map.createUI(node);
|
||||
var state = new iD.controller.edit.SelectedPOINode(node);
|
||||
state.controller = this.controller;
|
||||
state.enterState();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,13 @@
|
||||
*/
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/actions/UndoableAction',
|
||||
'iD/controller/ControllerState'
|
||||
'iD/actions/UndoableAction'
|
||||
], function(declare){
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedPOINode class
|
||||
|
||||
declare("iD.controller.shape.SelectedPOINode", [iD.controller.ControllerState], {
|
||||
declare("iD.controller.shape.SelectedPOINode", null, {
|
||||
|
||||
constructor:function() {
|
||||
// summary: In 'Draw shape' mode and a POI node is selected, to be converted into a way.
|
||||
|
||||
@@ -7,14 +7,13 @@
|
||||
// to.
|
||||
|
||||
define(['dojo/_base/declare',
|
||||
'iD/actions/UndoableAction',
|
||||
'iD/controller/ControllerState'
|
||||
'iD/actions/UndoableAction'
|
||||
], function(declare) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// SelectedWayNode class
|
||||
|
||||
declare("iD.controller.shape.SelectedWay", [iD.controller.ControllerState], {
|
||||
declare("iD.controller.shape.SelectedWay", null, {
|
||||
|
||||
way: null,
|
||||
wayUI: null,
|
||||
|
||||
+117
-116
@@ -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<this.sprites.length; i++) {
|
||||
this.sprites[i].removeShape();
|
||||
}
|
||||
this.sprites=[];
|
||||
},
|
||||
refreshStyleList: function(tags) {
|
||||
// summary: Calculate the list of styles that apply to this UI at this zoom level.
|
||||
if (!this.styleList || !this.styleList.isValidAt(this.map.zoom)) {
|
||||
this.styleList=this.map.ruleset.getStyles(this.entity,tags, this.map.zoom);
|
||||
}
|
||||
this.layer=this.styleList.layerOverride();
|
||||
if (isNaN(this.layer)) {
|
||||
this.layer=0;
|
||||
if (tags.layer) { this.layer = +tags.layer; }
|
||||
}
|
||||
|
||||
// Iterate through each subpart, drawing any styles on that layer
|
||||
var drawn=false;
|
||||
for (i=0; i<this.styleList.subparts.length; i++) {
|
||||
var subpart=this.styleList.subparts[i];
|
||||
}
|
||||
},
|
||||
getEnhancedTags: function() {
|
||||
// summary: Return tags for this entity augmented by the EntityUI's state classes.
|
||||
var tags = _.clone(this.entity.tags);
|
||||
// Apply stateClasses (hover, selected, hoverway, selectedway)
|
||||
for (var i in this.stateClasses) {
|
||||
tags[':'+this.stateClasses[i]] = 'yes';
|
||||
}
|
||||
// todo - Add any common 'special-case' tags, e.g. :hasTags
|
||||
return tags; // Object
|
||||
},
|
||||
|
||||
// --------------------
|
||||
// State class handling
|
||||
|
||||
setStateClasses:function(stateClasses) {
|
||||
// summary: Set all state classes at once, and prompt a redraw if they're different to previously,
|
||||
if (stateClasses && this.stateClasses.join(',')!=stateClasses.join(',')) {
|
||||
this.stateClasses=stateClasses.slice();
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setStateClass:function(sc) {
|
||||
// summary: Set a single state class, and prompt a redraw if it wasn't set previously.
|
||||
if (this.stateClasses.indexOf(sc)==-1) {
|
||||
this.stateClasses.push(sc);
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
resetStateClass:function(sc) {
|
||||
// summary: Reset a single state class, and prompt a redraw if it was set previously.
|
||||
if (this.stateClasses.indexOf(sc)>-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<this.sprites.length; i++) {
|
||||
this.sprites[i].removeShape();
|
||||
}
|
||||
this.sprites=[];
|
||||
},
|
||||
refreshStyleList:function(tags) {
|
||||
// summary: Calculate the list of styles that apply to this UI at this zoom level.
|
||||
if (!this.styleList || !this.styleList.isValidAt(this.map.zoom)) {
|
||||
this.styleList=this.map.ruleset.getStyles(this.entity,tags, this.map.zoom);
|
||||
}
|
||||
this.layer=this.styleList.layerOverride();
|
||||
if (isNaN(this.layer)) {
|
||||
this.layer=0;
|
||||
if (tags.layer) { this.layer = +tags.layer; }
|
||||
}
|
||||
});
|
||||
|
||||
// Iterate through each subpart, drawing any styles on that layer
|
||||
var drawn=false;
|
||||
for (i=0; i<this.styleList.subparts.length; i++) {
|
||||
var subpart=this.styleList.subparts[i];
|
||||
}
|
||||
},
|
||||
getEnhancedTags:function() {
|
||||
// summary: Return tags for this entity augmented by the EntityUI's state classes.
|
||||
var tags = _.clone(this.entity.tags);
|
||||
// Apply stateClasses (hover, selected, hoverway, selectedway)
|
||||
for (var i in this.stateClasses) {
|
||||
tags[':'+this.stateClasses[i]] = 'yes';
|
||||
}
|
||||
// todo - Add any common 'special-case' tags, e.g. :hasTags
|
||||
return tags; // Object
|
||||
},
|
||||
|
||||
// --------------------
|
||||
// State class handling
|
||||
|
||||
setStateClasses:function(stateClasses) {
|
||||
// summary: Set all state classes at once, and prompt a redraw if they're different to previously,
|
||||
if (stateClasses && this.stateClasses.join(',')!=stateClasses.join(',')) {
|
||||
this.stateClasses=stateClasses.slice();
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
setStateClass:function(sc) {
|
||||
// summary: Set a single state class, and prompt a redraw if it wasn't set previously.
|
||||
if (this.stateClasses.indexOf(sc)==-1) {
|
||||
this.stateClasses.push(sc);
|
||||
this.invalidateStyleList();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
resetStateClass:function(sc) {
|
||||
// summary: Reset a single state class, and prompt a redraw if it was set previously.
|
||||
if (this.stateClasses.indexOf(sc)>-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
|
||||
});
|
||||
|
||||
+440
-482
@@ -2,342 +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: '', // <div> of this map
|
||||
surface: null, // <div>.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
|
||||
nodeuis: {}, // graphic representations of data
|
||||
wayuis: {}, // |
|
||||
div: '', // <div> of this map
|
||||
surface: null, // <div>.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 <div> 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 <div> 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.nodeuis = {},
|
||||
this.wayuis = {},
|
||||
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(entity,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();
|
||||
// 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()
|
||||
};
|
||||
}
|
||||
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);
|
||||
|
||||
// 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 {
|
||||
this.wayuis[id].setStateClasses(stateClasses).redraw();
|
||||
group.rawNode.parentNode.insertBefore(group.rawNode, group.rawNode.parentNode.childNodes[position]);
|
||||
}
|
||||
return this.wayuis[id]; // iD.renderer.EntityUI
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
getUI: function(entity) {
|
||||
// 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;
|
||||
},
|
||||
|
||||
refreshUI: function(entity) {
|
||||
// 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(); }
|
||||
}
|
||||
},
|
||||
|
||||
deleteUI: function(entity) {
|
||||
// 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];
|
||||
}
|
||||
},
|
||||
|
||||
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(redraw, remove) {
|
||||
// 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()
|
||||
.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);
|
||||
_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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_.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(); }
|
||||
});
|
||||
// ----------------------------
|
||||
// Sprite and EntityUI handling
|
||||
|
||||
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); }
|
||||
});
|
||||
}
|
||||
},
|
||||
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
|
||||
},
|
||||
|
||||
// -------------
|
||||
// Zoom handling
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
zoomIn: function() {
|
||||
// summary: Zoom in by one level (unless maximum reached).
|
||||
return this.setZoom(this.zoom + 1);
|
||||
},
|
||||
getUI: function(e) {
|
||||
// summary: Return the UI for an entity, if it exists.
|
||||
return this.uis[e.id]; // iD.renderer.EntityUI
|
||||
},
|
||||
|
||||
zoomOut: function() {
|
||||
// summary: Zoom out by one level (unless minimum reached).
|
||||
this.setZoom(this.zoom - 1);
|
||||
this.download();
|
||||
return this;
|
||||
},
|
||||
refreshUI: function(e) {
|
||||
// summary: Redraw the UI for an entity.
|
||||
if (this.uis[e.id]) { this.uis[e.id].redraw(); }
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
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];
|
||||
}
|
||||
},
|
||||
|
||||
_setScaleFactor: function() {
|
||||
// summary: Calculate the scaling factor for this zoom level.
|
||||
this.zoomfactor = this.MASTERSCALE/Math.pow(2, 13 - this.zoom);
|
||||
},
|
||||
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));
|
||||
},
|
||||
|
||||
// ----------------------
|
||||
// Elastic band redrawing
|
||||
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));
|
||||
},
|
||||
|
||||
clearElastic: function() {
|
||||
// summary: Remove the elastic band used to draw new ways.
|
||||
this.elastic.clear();
|
||||
},
|
||||
// -------------
|
||||
// Zoom handling
|
||||
|
||||
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
|
||||
});
|
||||
},
|
||||
zoomIn: function() {
|
||||
// summary: Zoom in by one level (unless maximum reached).
|
||||
return this.setZoom(this.zoom + 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({
|
||||
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;
|
||||
},
|
||||
|
||||
_setScaleFactor: function() {
|
||||
// summary: Calculate the scaling factor for this zoom level.
|
||||
this.zoomfactor = this.MASTERSCALE/Math.pow(2, 13 - this.zoom);
|
||||
},
|
||||
|
||||
// ----------------------
|
||||
// Elastic band redrawing
|
||||
|
||||
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
|
||||
});
|
||||
},
|
||||
|
||||
// -------------
|
||||
// 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),
|
||||
@@ -349,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
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user