mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-30 08:50:32 +02:00
Bring in changes from blog post
This commit is contained in:
133
ARCHITECTURE.md
133
ARCHITECTURE.md
@@ -19,35 +19,41 @@ 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, an undo/redo history for changes made during
|
||||
editing, and a couple of important auxiliary classes. It aims to be generic
|
||||
enough to be used by other JavaScript-based tools for OpenStreetMap.
|
||||
The iD *core* implements OSM data types, a graph of OSM objects'
|
||||
relationships to one another, an undo/redo history for changes made during
|
||||
editing, and a couple of important auxiliary classes. It eventually aims
|
||||
to be a reusable, modular library to kickstart 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
|
||||
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.
|
||||
* 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
|
||||
`iD.Relation`. These three classes inherit from a common base, `iD.Entity`.
|
||||
This is the only use of classical inheritance in iD, but it's justified
|
||||
by the common functionality of the types. 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
|
||||
Every entity has an _ID_ either assigned by the OSM database or
|
||||
a negative, local identifier assigned by iD for newly-created objects.
|
||||
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.
|
||||
begin with a minus sign (and thus will not conflict with proxy IDs). The three
|
||||
types of entities have separate ID spaces: a
|
||||
node can have the same numeric ID as a way or a relation. Instead of segregating
|
||||
ways, nodes, and other entities into different datastructures,
|
||||
iD internally uses fully-unique IDs generated 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,
|
||||
@@ -56,8 +62,9 @@ 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
|
||||
structure](http://en.wikipedia.org/wiki/Persistent_data_structure).
|
||||
|
||||
Since iD is an editor, it must allow for new versions of entities. 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
|
||||
@@ -65,46 +72,81 @@ 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
|
||||
many members. 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.
|
||||
Because entities are immutable, the original and new graphs can minimize
|
||||
memory use by sharing references to entities that have not changed instead of
|
||||
copying the entire graph.
|
||||
This persistent data structure approach is similar to the internals of
|
||||
the [git](http://git-scm.com/) revision control system.
|
||||
|
||||
The last major component of the core is `iD.History`, which tracks the changes
|
||||
made in an editing session and provide undo/redo capabilities. Here, the
|
||||
The final major component of the core is `iD.History`, which tracks the changes
|
||||
made in an editing session and provides 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.
|
||||
stack, and the map is re-rendered based on the new top of the stack.
|
||||
|
||||
Finally, we have the auxiliary classes `iD.Difference` and `iD.Tree`. The first
|
||||
encapsulates the difference between two graphs, and knows how to calculate the
|
||||
This approach constitutes one of the main differences between iD's approach
|
||||
to data and that of [JOSM](http://josm.openstreetmap.de/) and
|
||||
[Potlatch 2](http://wiki.openstreetmap.org/wiki/Potlatch_2).
|
||||
Instead of changing a single copy of local data and having to implement
|
||||
an 'undo' for each specific action, actions in iD do not need to be aware
|
||||
of history and the undo system.
|
||||
|
||||
Finally, we have the auxiliary classes `iD.Difference` and `iD.Tree`.
|
||||
|
||||
`iD.Difference` encapsulates the difference between two graphs, and knows how to calculate the
|
||||
set of entities that were created, modified, or deleted, and need to be redrawn.
|
||||
The second provides a means of calculating which entities, out of all that have
|
||||
been downloaded, are currently visible. In order to do this very quickly as you
|
||||
pan or zoom the map, it uses an [R-tree](http://en.wikipedia.org/wiki/R-tree).
|
||||
|
||||
```js
|
||||
var a = iD.Graph(), b = iD.Graph();
|
||||
// (fill a & b with data)
|
||||
var difference = iD.Difference(a, b);
|
||||
|
||||
// returns entities created between and b
|
||||
difference.created();
|
||||
```
|
||||
|
||||
`iD.Tree` calculates the set of downloaded entities that are visible in the
|
||||
current map view. To calculate this quickly during map
|
||||
interaction, it uses an [R-tree](http://en.wikipedia.org/wiki/R-tree).
|
||||
|
||||
```js
|
||||
var graph = iD.Graph();
|
||||
// (load OSM data into graph)
|
||||
|
||||
// this tree indexes the contents of the graph
|
||||
var tree = iD.Tree(graph);
|
||||
|
||||
// quickly pull all features that intersect with an extent
|
||||
var features = tree.intersects(
|
||||
iD.geo.Extent([0, 0], [2, 2]), tree.graph());
|
||||
```
|
||||
|
||||
## 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
|
||||
new, 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
|
||||
```js
|
||||
// construct the action: this returns a function that remembers the
|
||||
// value `n123456` in a closure so that when it's called, it runs
|
||||
// the specified action on the graph
|
||||
var action = iD.actions.DeleteNode('n123456');
|
||||
|
||||
// apply the action, yielding a new graph. oldGraph is untouched.
|
||||
newGraph = action(oldGraph);
|
||||
```
|
||||
|
||||
iD provides actions for all the typical things an editor needs to do: add a
|
||||
@@ -120,9 +162,8 @@ 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.
|
||||
based tools for OpenStreetMap can reuse the iD's core implementation,
|
||||
significantly reducing the work necessary to create a robust tool.
|
||||
|
||||
## Modes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user