iD Architecture: Map Rendering and Other UI How iD renders the vector map and other UI components using d3
Let’s look at how iD renders entities on the map and its pattern for abstracting other UI components.
The core of iD’s map rendering system is
iD.Map, which takes care of the map projection, zooming and panning, and states like center and zoom level.
iD.Map also coordinates a system of smaller pieces that render a vector map.
For rendering entities on screen, iD adopts a geometric vocabulary that’s a higher-level representation than OSM’s simple data model:
- A point is a node that is not a member of any way.
- A vertex is a node that is a member of at least one way.
- A line is a way that is not an area.
- An area is a way that is circular and has certain tags, or a series of one or more ways grouped in a multipolygon relation.
For each of these geometric types,
iD.svg has a corresponding module:
iD.Map delegates to these modules to render entities on screen.
Each piece of data is connected to the rendering with d3’s join system. When iD loads and renders an entity for the first time, it is part of the enter selection and the SVG elements needed to represent it are created. When a user modifies an entity, it is part of the update selection and the appropriate attributes of the SVG element, like those that specify the location on screen, are updated. And when a user deletes an entity or moves it offscreen, the corresponding SVG element is in the exit selection, and will be removed.
iD.svg modules apply classes to SVG elements based on tags, via
iD.svg.TagClasses. For example, an entity tagged with
highway=residential gets two classes:
.tag-highway-residential. This allows distinct visual styles to be applied via CSS at either the key or key-value levels. Elements also receive a class corresponding to their entity type (
relation) and one corresponding to their geometry type (
iD.svg namespace has a few other modules that don’t have a one-to-one correspondence with entities:
iD.svg.Midpointsrenders small “virtual nodes” at the midpoints between two vertices.
iD.svg.Labelsrenders textual labels.
iD.svg.Surfacesets up a stack of SVG groups that ensure that map elements appear in an appropriate z-order.
iD provides many user interface elements beyond than the map, including the page footer, an interface for saving changes, and geocoding controls.
The implementations for all non-map UI components live in the
iD.ui namespace. These modules follow a pattern for reusable d3 components Mike Bostock suggested in the context of charts. The entry point to a UI element is a constructor function; for example
iD.ui.Geocoder(). The constructor function may require a set of mandatory arguments. Most UI components require exactly one argument: a
context object produced by the top-level
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:
var container = d3.select('body').append('div') .attr('class', 'map-control geocode-control'); var geocoder = iD.ui.Geocoder(context)(container);
Alternatively, and more commonly, the same result is accomplished with d3.selection#call:
d3.select('body').append('div') .attr('class', 'map-control geocode-control') .call(iD.ui.Geocoder(context));
Some components are reconfigurable and provide functionality beyond basic rendering, by providing methods off of the function:
var inspector = iD.ui.Inspector(); inspector(container); // render the inspector inspector.tags(); // retrieve the current tags inspector.on('change', callback); // get notified when a tag change is made
We hope you enjoyed the deep dive on iD’s technical internals. As iD evolves, no doubt some of these details will change, and new architectural pieces will emerge as we build out remaining features like presets. We’ve captured the text of these posts in the ARCHITECTURE.md file in the iD repository, and will keep it up to date as the project matures.
Devlogging work on the OpenStreetMap project by the MapBox team.
Much of this work is currently focused on improvements to OpenStreetMap funded by the Knight Foundation