mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-31 20:21:36 +02:00
Update ARCHITECTURE.md
Could use a bit more, but at least it is up to date (closes #3234)
This commit is contained in:
+108
-91
@@ -1,59 +1,74 @@
|
||||
## d3
|
||||
## iD Architecture
|
||||
|
||||
iD is written in a modular code style using ES6 modules. The modules are bundled
|
||||
with [rollup.js](http://rollupjs.org/). iD eventually aims to be a reusable,
|
||||
modular library to kickstart other JavaScript-based tools for OpenStreetMap.
|
||||
|
||||
### 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
|
||||
[d3.request](https://github.com/d3/d3/blob/master/API.md#requests-d3-request), 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),
|
||||
[d3.dispatch](https://github.com/d3/d3/blob/master/API.md#dispatches-d3-dispatch),
|
||||
which provides a callback-based [Observer
|
||||
pattern](http://en.wikipedia.org/wiki/Observer_pattern) between different
|
||||
parts of iD;
|
||||
[d3.geoPath](https://github.com/mbostock/d3/wiki/Geo-Paths#wiki-path), which
|
||||
[d3.geoPath](https://github.com/d3/d3/blob/master/API.md#paths), which
|
||||
generates SVG paths for lines and areas; and
|
||||
[d3.behavior.zoom](https://github.com/mbostock/d3/wiki/Zoom-Behavior#wiki-zoom),
|
||||
[d3.zoom](https://github.com/d3/d3/blob/master/API.md#zooming-d3-zoom),
|
||||
which implements map panning and zooming.
|
||||
|
||||
## Core
|
||||
|
||||
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.
|
||||
### Core Module
|
||||
|
||||
The OSM data model includes three basic data types: nodes, ways, and
|
||||
relations.
|
||||
The iD *core* module implements the basic datastructures needed to support
|
||||
browser-based editing:
|
||||
|
||||
* 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
|
||||
* `iD.coreContext` - container for all iD "global" objects and bootstrap code
|
||||
* `iD.coreGraph` - graph of objects and their relationships to one another
|
||||
* `iD.coreHistory` - undo/redo history for changes made during editing
|
||||
* `iD.coreDifference` - efficiently determine the difference between two graphs
|
||||
* `iD.coreTree` - performs fast spatial indexing of the loaded objects
|
||||
|
||||
### OSM Module
|
||||
|
||||
The iD *osm* module includes classes which represent the basic OpenStreetMap
|
||||
objects: nodes, ways, and relations.
|
||||
|
||||
* `iD.osmNode` - a _node_ is a point type, having a single geographic coordinate
|
||||
* `iD.osmWay` - a _way_ is an ordered list of nodes
|
||||
* `iD.osmRelation` - 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
|
||||
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`.
|
||||
* `iD.osmEntity` - common base class for `iD.osmNode`, `iD.osmWay`, `iD.osmRelation`
|
||||
|
||||
These three classes inherit from a common base, `iD.osmEntity`.
|
||||
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
|
||||
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
|
||||
a negative, local identifier assigned by iD for newly-created objects.
|
||||
IDs from the OSM database as treated as opaque strings; no
|
||||
Every entity has a unique numeric `id`. By convention, positive numbers are
|
||||
assigned by the OSM database for saved features, and negative numbers are
|
||||
assigned by the iD editor for local newly-created objects.
|
||||
|
||||
`id` values 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). 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.
|
||||
begin with a minus sign (and thus will not conflict with local `id` values).
|
||||
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
|
||||
`id` values 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,
|
||||
@@ -66,19 +81,30 @@ 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
|
||||
different coordinate. More generically, `iD.Entity#update` returns a new
|
||||
entity of the same type and ID as the original but with specified properties
|
||||
entity level, this takes the form of methods such as `iD.osmNode#move`, which
|
||||
returns a new node object that has the same `id` and `tags` as the original, but a
|
||||
different `loc` coordinate. More generically, `iD.osmEntity#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. To render a map of a certain area, iD needs a
|
||||
many members.
|
||||
|
||||
The osm module also includes code related to special kinds of objects in OpenStreetMap.
|
||||
|
||||
* `iD.osmIntersection` - code for working with turn restrictions
|
||||
* `iD.osmLanes` - code for working with traffic and turn lanes
|
||||
* `iD.osmMultipolygon` - code for working with multipolygon relations
|
||||
|
||||
|
||||
### Editing OSM
|
||||
|
||||
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
|
||||
relationships. `iD.coreGraph` provides this functionality. The core of a graph is
|
||||
a map between `id`s 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 minimize
|
||||
@@ -87,7 +113,7 @@ 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 final major component of the core is `iD.History`, which tracks the changes
|
||||
The final major component of the core is `iD.coreHistory`, 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
|
||||
@@ -102,41 +128,41 @@ 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`.
|
||||
Finally, we have the auxiliary classes `iD.coreDifference` and `iD.coreTree`.
|
||||
|
||||
`iD.Difference` encapsulates the difference between two graphs, and knows how to calculate the
|
||||
`iD.coreDifference` 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.
|
||||
|
||||
```js
|
||||
var a = iD.Graph(), b = iD.Graph();
|
||||
var a = iD.coreGraph(), b = iD.coreGraph();
|
||||
// (fill a & b with data)
|
||||
var difference = iD.Difference(a, b);
|
||||
var difference = iD.coreDifference(a, b);
|
||||
|
||||
// returns entities created between and b
|
||||
// returns entities created between a and b
|
||||
difference.created();
|
||||
```
|
||||
|
||||
`iD.Tree` calculates the set of downloaded entities that are visible in the
|
||||
`iD.coreTree` 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();
|
||||
var graph = iD.coreGraph();
|
||||
// (load OSM data into graph)
|
||||
|
||||
// this tree indexes the contents of the graph
|
||||
var tree = iD.Tree(graph);
|
||||
var tree = iD.coreTree(graph);
|
||||
|
||||
// quickly pull all features that intersect with an extent
|
||||
var features = tree.intersects(
|
||||
iD.geoExtent([0, 0], [2, 2]), tree.graph());
|
||||
```
|
||||
|
||||
## Actions
|
||||
### Actions Module
|
||||
|
||||
In iD, an _action_ is a function that accepts a graph as input and returns a
|
||||
new, modified graph as output. Actions typically need other inputs as well; for
|
||||
example, `iD.actionDeleteNode` also requires the ID of a node to delete. The
|
||||
example, `iD.actionDeleteNode` also requires the `id` of a node to delete. The
|
||||
additional input is passed to the action's constructor:
|
||||
|
||||
```js
|
||||
@@ -165,10 +191,10 @@ knowledge of the OpenStreetMap data model. It is our hope that JavaScript
|
||||
based tools for OpenStreetMap can reuse the iD's core implementation,
|
||||
significantly reducing the work necessary to create a robust tool.
|
||||
|
||||
## Modes
|
||||
### Modes Module
|
||||
|
||||
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
|
||||
parts of the architecture that implement the user interface for iD. Modes are
|
||||
manifested in the interface by the three buttons at the top left:
|
||||
|
||||

|
||||
@@ -199,7 +225,7 @@ The `exit` mode does the opposite, removing the behavior installed by the
|
||||
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
|
||||
### Behavior Module
|
||||
|
||||
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
|
||||
@@ -208,7 +234,7 @@ of duplicating the code to implement this behavior in all these modes, we
|
||||
extract it to `iD.behaviorHover`.
|
||||
|
||||
_Behaviors_ take their inspiration from [d3's
|
||||
behaviors](https://github.com/mbostock/d3/wiki/Behaviors). Like d3's `zoom`
|
||||
behaviors](https://github.com/d3/d3/blob/master/API.md). 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,
|
||||
@@ -223,12 +249,12 @@ 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 Module
|
||||
|
||||
_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`
|
||||
constructed with the list of `id`s 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)
|
||||
@@ -250,11 +276,11 @@ the history, and then enter the appropriate mode. For example,
|
||||
`iD.operationSplit` performs `iD.actionSplit`, then enters
|
||||
`iD.modeSelect` with the resulting ways selected.
|
||||
|
||||
## Map Rendering
|
||||
### Renderer and SVG Modules
|
||||
|
||||
Finally, we get to the parts of iD that actually draw and manipulate the map
|
||||
entities on screen. The rendering is coordinated by `iD.Map`, which takes care
|
||||
of setting up a [Spherical Mercator](http://bl.ocks.org/mbostock/3757132)
|
||||
entities on screen. The rendering is coordinated by `iD.rendererMap`, which
|
||||
takes care of setting up a [Spherical Mercator](http://bl.ocks.org/mbostock/3757132)
|
||||
projection and the [zoom
|
||||
behavior](https://github.com/mbostock/d3/wiki/Zoom-Behavior), and provides
|
||||
accessors for such things as the current zoom level and map center.
|
||||
@@ -271,7 +297,7 @@ entity types of the OSM data model:
|
||||
|
||||
For each of these geometric types, `iD.svg` has a corresponding module:
|
||||
`iD.svgPoints`, `iD.svgVertices`, `iD.svgLines`, and `iD.svgAreas`. To
|
||||
render entities on screen, `iD.Map` delegates to these modules. Internally,
|
||||
render entities on screen, `iD.rendererMap` delegates to these modules. Internally,
|
||||
they make heavy use of [d3 joins](http://bost.ocks.org/mike/join/) to
|
||||
manipulate the SVG elements that visually represent the map entities. When an
|
||||
entity is rendered for the first time, it is part of the _enter_ selection,
|
||||
@@ -289,53 +315,44 @@ via CSS at either the key or key-value levels. SVG elements also receive a
|
||||
class corresponding to their entity type (`node`, `way`, or `relation`) and
|
||||
one corresponding to their geometry type (`point`, `line`, or `area`).
|
||||
|
||||
The `iD.svg` namespace has a few other modules that don't have a one-to-one
|
||||
The `iD.svg` module has a few other submodules that don't have a one-to-one
|
||||
correspondence with entities:
|
||||
|
||||
* `iD.svgMidpoints` renders the small "virtual node" at the midpoint between
|
||||
* `iD.svgMidpoints` - draws the small "virtual node" at the midpoint between
|
||||
two vertices.
|
||||
* `iD.svgLabels` renders the textual
|
||||
[labels](http://mapbox.com/osmdev/2013/02/12/labeling-id/).
|
||||
* `iD.svgLayers` sets up a number of layers that ensure that map elements
|
||||
* `iD.svgLabels` - draws textual labels
|
||||
* `iD.svgLayers` - sets up a number of layers that ensure that map elements
|
||||
appear in an appropriate z-order.
|
||||
* `iD.svgOsm` - sets up the OSM-specific data layers
|
||||
* `iD.svgGpx` - draws gpx traces
|
||||
* `iD.svgDebug` - draws debugging information
|
||||
|
||||
## Other UI
|
||||
### Other UI
|
||||
|
||||
iD provides a lot of user interface elements other than the core map component:
|
||||
the page footer, the interface for saving changes, the splash screen you see
|
||||
the first time you use iD, the geocoding and background layer controls, and the
|
||||
tag/preset editor, for example.
|
||||
the first time you use iD, the map controls, and the tag/preset editor, for example.
|
||||
|
||||

|
||||
|
||||
The implementations for all non-map UI components live in the `iD.ui` namespace.
|
||||
Many of the modules in this namespace follow a pattern for reusable d3
|
||||
The implementations for all non-map UI components live in the `iD.ui` module.
|
||||
Many of the submodules under the `ui` module follow a pattern for reusable d3
|
||||
components [originally suggested](http://bost.ocks.org/mike/chart/) by Mike
|
||||
Bostock in the context of charts. The entry point to a UI element is a
|
||||
constructor function, e.g. `iD.uiGeocoder()`. The constructor function may
|
||||
Bostock in the context of charts. The entry point to a UI element is a
|
||||
constructor function, e.g. `iD.uiViewOnOSM()`. The constructor function may
|
||||
require a set of mandatory arguments; for most UI components exactly one
|
||||
argument is required, a `context` object produced by the top-level `iD()`
|
||||
function.
|
||||
argument is required, a `context`.
|
||||
|
||||
A component needs some way to be rendered on screen by creating new DOM
|
||||
elements or manipulating existing elements. This is done by calling the
|
||||
component as a function, and passing a d3 selection where the component should
|
||||
render itself:
|
||||
The constructor function returns a draw function which accepts a d3 selection.
|
||||
Drawing is then accomplished with
|
||||
[d3.selection#call](https://github.com/d3/d3-selection/blob/master/README.md#selection_call):
|
||||
|
||||
```
|
||||
var container = d3.select('body').append('div')
|
||||
.attr('class', 'map-control geocode-control');
|
||||
```js
|
||||
footer = footer.enter()
|
||||
.append('div')
|
||||
.attr('class', 'footer')
|
||||
.merge(footer);
|
||||
|
||||
var geocoder = iD.uiGeocoder(context)(container);
|
||||
```
|
||||
|
||||
Alternatively, and more commonly, the same result is accomplished with
|
||||
[d3.selection#call](https://github.com/mbostock/d3/wiki/Selections#wiki-call):
|
||||
|
||||
```
|
||||
d3.select('body').append('div')
|
||||
.attr('class', 'map-control geocode-control')
|
||||
.call(iD.uiGeocoder(context));
|
||||
footer
|
||||
.call(uiViewOnOSM(context).entityID(entityID));
|
||||
```
|
||||
|
||||
Some components are reconfigurable, and some provide functionality beyond
|
||||
|
||||
Reference in New Issue
Block a user