diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 000000000..c0ff6b217 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,203 @@ +## d3 + +[d3](http://d3js.org/) is the primary library used by iD. It is used for +rendering the map data as well as many sorts of general DOM manipulation tasks +for which jQuery would often be used. + +Notable features of d3 that are used by iD include +[d3.xhr](https://github.com/mbostock/d3/wiki/Requests#wiki-d3_xhr), which is +used to make the API requests to download data from openstreetmap.org and save +changes; +[d3.dispatch](https://github.com/mbostock/d3/wiki/Internals#wiki-d3_dispatch), +which provides a callback-based [Observer +pattern](http://en.wikipedia.org/wiki/Observer_pattern) between different +parts of iD; +[d3.geo.path](https://github.com/mbostock/d3/wiki/Geo-Paths#wiki-path), which +generates SVG paths for lines and areas; and +[d3.behavior.zoom](https://github.com/mbostock/d3/wiki/Zoom-Behavior#wiki-zoom), +which implements map panning and zooming. + +## Core + +The iD *core* implements the OSM data types, a graph of OSM object's +relationships to each other, and an undo/redo history for changes made during +editing. It aims to be generic enough to be used by other JavaScript-based +tools for OpenStreetMap. + +Briefly, the OSM data model includes three basic data types: nodes, ways, and +relations. A _node_ is a point type, having a single geographic coordinate. A +_way_ is an ordered list of nodes. And a _relation_ groups together nodes, +ways, and other relations to provide free-form higher-level structures. Each +of these three types has _tags_: an associative array of key-value pairs which +describe the object. + +In iD, these three types are implemented by `iD.Node`, `iD.Way` and +`iD.Relation`. These three classes inherit from a common base, `iD.Entity` +(the only use of classical inheritance in iD). Generically, we refer to a +node, way or relation as an _entity_. + +Every entity has an _ID_ either assigned by the OSM database, or, for an +entity that is newly created, constructed as a proxy consisting of a negative +numeral. IDs from the OSM database as treated as opaque strings; no +[assumptions](http://lists.openstreetmap.org/pipermail/dev/2013-February/026495.html) +are made of them other than that they can be compared for identity and do not +begin with a minus sign (and thus will not conflict with proxy IDs). In fact, +in the OSM database the three types of entities have separate ID spaces; a +node can have the same ID as a way, for instance. Because it is useful to +store heterogeneous entities in the same datastructure, iD ensures that every +entity has a fully-unique ID by prefixing each OSM ID with the first letter of +the entity type. For example, a way with OSM ID 123456 is represented as +'w123456' within iD. + +iD entities are *immutable*: once constructed, an `Entity` object cannot +change. Tags cannot be updated; nodes cannot be added or removed from ways, +and so on. Immutability makes it easier to reason about the behavior of an +entity: if your code has a reference to one, it is safe to store it and use it +later, knowing that it cannot have been changed outside of your control. It +also makes it possible to implement the entity graph (described below) as an +efficient [persistent data +structure](http://en.wikipedia.org/wiki/Persistent_data_structure). But +obviously, iD is an editor, and must allow entities to change somehow. The +solution is that all edits produce new copies of anything that changes. At the +entity level, this takes the form of methods such as `iD.Node#move`, which +returns a new node object that has the same ID and tags as the original, but a +different coordinate. More generically, `iD.Entity#update` returns a new +entity of the same type and ID as the original but with specified properties +such as `nodes`, `tags`, or `members` replaced. + +Entities are related to one another: ways have many nodes and relations have +many members. In order to render a map of a certain area, iD needs a +datastructure to hold all the entities in that area and traverse these +relationships. `iD.Graph` provides this functionality. The core of a graph is +a map between IDs and the associated entities; given an ID, the graph can give +you the entity. Like entities, a graph is immutable: adding, replacing, or +removing an entity produces a new graph, and the original is unchanged. +Because entities are immutable, the original and new graphs can share +references to entities that have not changed, keeping memory use to a minimum. +If you are familiar with how git works internally, this persistent data +structure approach is very similar. + +The final component of the core is comprised of `iD.History` and +`iD.Difference`, which track the changes made in an editing session and +provide undo/redo capabilities. Here, the immutable nature of the core types +really pays off: the history is a simple stack of graphs, each representing +the state of the data at a particular point in editing. The graph at the top +of the stack is the current state, off which all rendering is based. To undo +the last change, this graph is popped off the stack, and the map is +re-rendered based on the new top of the stack. Contrast this to a mutable +graph as used in JOSM and Potlatch: every command that changes the graph must +implement an equal and opposite undo command that restores the graph to the +previous state. + +## Actions + +In iD, an _action_ is a function that accepts a graph as input and returns a +modified graph as output. Actions typically need other inputs as well; for +example, `iD.actions.DeleteNode` also requires the ID of a node to delete. The +additional input is passed to the action's constructor: + +``` var action = iD.actions.DeleteNode('n123456'); // construct the action var +newGraph = action(oldGraph); // apply the action ``` + +iD provides actions for all the typical things an editor needs to do: add a +new entity, split a way in two, connect the vertices of two ways together, and +so on. In addition to performing the basic work needed to accomplish these +things, an action typically contains a significant amount of logic for keeping +the relationships between entities logical and consistent. For example, an +action as apparently simple as `DeleteNode`, in addition to removing the node +from the graph, needs to do two other things: remove the node from any ways in +which it is a member (which in turn requires deleting parent ways that are +left with just a single node), and removing it from any relations of which it +is a member. + +As you can imagine, implementing all these details requires an expert +knowledge of the OpenStreetMap data model. It is our hope that JavaScript +based tools for OpenStreetMap can reuse the implementations provided by iD in +other contexts, significantly reducing the work necessary to create a robust +tool. + +## Modes + +With _modes_, we shift gears from abstract data types and algorithms to the +parts of the architecture that implement the user interface for iD. Modes are +manifested in the interface by the four buttons at the top left: + +![Mode buttons](img/modes.png) + +The modality of existing OSM editors runs the gamut from Potlatch 2, which is +almost entirely modeless, to JOSM, which sports half a dozen modes out of the +box and has many more provided by plugins. iD seeks a middle ground: too few +modes can leave new users unsure where to start, while too many can be +overwhelming. + +iD's user-facing modes consist of a base "Browse" mode, in which you can move +around the map and select and edit entities, and three geometrically-oriented +drawing modes: Point, Line, and Area. In the code, these are broken down a +little bit more. There are separate modes for when an entity is selected +(`iD.modes.Select`) versus when nothing is selected (`iD.modes.Browse`), and +each of the geometric modes is split into one mode for starting to draw an +object and one mode for continuing an existing object (with the exception of +`iD.modes.AddPoint`, which is a single-step operation for obvious reasons). + +The code interface for each mode consists of a pair of methods: `enter` and +`exit`. In the `enter` method, a mode sets up all the behavior that should be +present when that mode is active. This typically means binding callbacks to +DOM events that will be triggered on map elements, installing keybindings, and +showing certain parts of the interface like the inspector in `Select` mode. +The `exit` mode does the opposite, removing the behavior installed by the +`enter` method. Together the two methods ensure that modes are self-contained +and exclusive: each mode knows exactly the behavior that is specific to that +mode, and exactly one mode's behavior is active at any time. + +## Behavior + +Certain behaviors are common to more than one mode. For example, iD indicates +interactive map elements by drawing a halo around them when you hover over +them, and this behavior is common to both the browse and draw modes. Instead +of duplicating the code to implement this behavior in all these modes, we +extract it to `iD.behavior.Hover`. + +_Behaviors_ take their inspiration from [d3's +behaviors](https://github.com/mbostock/d3/wiki/Behaviors). Like d3's `zoom` +and `drag`, each iD behavior is a function that takes as input a d3 selection +(assumed to consist of a single element) and installs the DOM event bindings +necessary to implement the behavior. The `Hover` behavior, for example, +installs bindings for the `mouseover` and `mouseout` events that add and +remove a `hover` class from map elements. + +Because certain behaviors are appropriate to some but not all modes, we need +the ability to remove a behavior when entering a mode where it is not +appropriate. (This is functionality [not yet +provided](https://github.com/mbostock/d3/issues/894) by d3's own behaviors.) +Each behavior implements an `off` function that "uninstalls" the behavior. +This is very similar to the `exit` method of a mode, and in fact many modes do +little else but uninstall behaviors in their `exit` methods. + +## Operations + +_Operations_ wrap actions, providing their user-interface: tooltips, key +bindings, and the logic that determines whether an action can be validly +performed given the current map state and selection. Each operation is +constructed with the list of IDs which are currently selected and a `context` +object which provides access to the history and other important parts of iD's +internal state. After being constructed, an operation can be queried as to +whether or not it should be made available (i.e., show up in the context menu) +and if so, if it should be enabled. + +![Operations menu](img/operations.png) + +We make a distinction between availability and enabled state for the sake of +learnability: most operations are available so long as an entity of the +appropriate type is selected. Even if it remains disabled for other reasons +(e.g. because you can't split a way on its start or end vertex), a new user +can still learn that "this is something I can do to this type of thing", and a +tooltip can provide an explanation of what that operation does and the +conditions under which it is enabled. + +To execute an operation, call it as a function, with no arguments. The typical +operation will perform the appropriate action, creating a new undo state in +the history, and then enter the appropriate mode. For example, +`iD.operations.Split` performs `iD.actions.Split`, then enters +`iD.modes.Select` with the resulting ways selected. + +## Rendering and other UI diff --git a/img/modes.png b/img/modes.png new file mode 100644 index 000000000..6b5ec8603 Binary files /dev/null and b/img/modes.png differ diff --git a/img/operations.png b/img/operations.png new file mode 100644 index 000000000..c1e516b29 Binary files /dev/null and b/img/operations.png differ