Merge branch 'master' of git://github.com/systemed/iD into rotate2

Conflicts:
	index.html
	test/index.html
This commit is contained in:
Ansis Brammanis
2013-02-27 21:41:26 -05:00
76 changed files with 1085 additions and 1995 deletions
+191 -45
View File
@@ -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, and an undo/redo history for changes made during
editing. It aims to be generic enough to be used by other JavaScript-based
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,39 +72,82 @@ 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.
![](http://farm9.staticflickr.com/8087/8508309757_ccf5b6f09b_o.png)
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 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.
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.
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.
```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
new entity, split a way in two, connect the vertices of two ways together, and
@@ -112,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
@@ -200,4 +249,101 @@ 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
## Map Rendering
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)
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.
For rendering entities on screen, we found it convenient to adopt a geometric
vocabulary that provides a slightly higher-level representation than the basic
entity types of the OSM 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 one or more ways.
* 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.svg.Points`, `iD.svg.Vertices`, `iD.svg.Lines`, and `iD.svg.Areas`. To
render entities on screen, `iD.Map` 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,
and the SVG elements needed to represent it are created. When an entity is
modified, it is part of the _update_ selection, and the appropriate attributes
of the SVG element (for example, those that specify the location on screen)
are updated. And when an entity is deleted (or simply moves offscreen), the
corresponding SVG element is in the _exit_ selection, and will be removed.
The `iD.svg` modules apply classes to the SVG elements based on the entity
tags, via `iD.svg.TagClasses`. For example, an entity tagged with
`highway=residential` gets two classes: `tag-highway` and
`tag-highway-residential`. This allows distinct visual styles to be applied
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
correspondence with entities:
* `iD.svg.Midpoints` renders the small "virtual node" at the midpoint between
two vertices.
* `iD.svg.Labels` renders the textual
[labels](http://mapbox.com/osmdev/2013/02/12/labeling-id/).
* `iD.svg.Surface` sets up a number of layers that ensure that map elements
appear in an appropriate z-order.
## 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.
![Geocoder UI](img/geocoder.png)
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
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.ui.Geocoder()`. 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.
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](https://github.com/mbostock/d3/wiki/Selections#wiki-call):
```
d3.select('body').append('div')
.attr('class', 'map-control geocode-control')
.call(iD.ui.Geocoder(context));
```
Some components are reconfigurable, and some provide functionality beyond
basic rendering. Both reconfiguration and extended functionality are exposed
via module functions:
```
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
```
+2 -2
View File
@@ -18,7 +18,6 @@ all: \
js/lib/d3.keybinding.js \
js/lib/d3.one.js \
js/lib/d3.size.js \
js/lib/d3.tail.js \
js/lib/d3.trigger.js \
js/lib/d3.typeahead.js \
js/lib/jxon.js \
@@ -51,6 +50,7 @@ all: \
js/id/svg/*.js \
js/id/ui.js \
js/id/ui/*.js \
js/id/ui/preset/*.js \
js/id/presetdata.js \
js/id/validate.js \
js/id/end.js \
@@ -63,7 +63,7 @@ iD.js: Makefile
%.min.js: %.js Makefile
@rm -f $@
$(JS_COMPILER) $< -o $@
$(JS_COMPILER) $< -c -m -o $@
clean:
rm -f iD*.js
+3 -3
View File
@@ -23,7 +23,6 @@
<script src='js/lib/d3.size.js'></script>
<script src='js/lib/d3.trigger.js'></script>
<script src='js/lib/d3.keybinding.js'></script>
<script src='js/lib/d3.tail.js'></script>
<script src='js/lib/d3-compat.js'></script>
<script src='js/lib/bootstrap-tooltip.js'></script>
<script src='js/lib/rtree.js'></script>
@@ -70,6 +69,7 @@
<script src='js/id/ui/flash.js'></script>
<script src='js/id/ui/save.js'></script>
<script src='js/id/ui/tag_reference.js'></script>
<script src='js/id/ui/tail.js'></script>
<script src='js/id/ui/key_reference.js'></script>
<script src='js/id/actions.js'></script>
@@ -81,7 +81,7 @@
<script src="js/id/actions/delete_way.js"></script>
<script src='js/id/actions/disconnect.js'></script>
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move_way.js'></script>
<script src='js/id/actions/move.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/noop.js'></script>
<script src='js/id/actions/reverse.js'></script>
@@ -103,7 +103,7 @@
<script src='js/id/modes/browse.js'></script>
<script src='js/id/modes/draw_area.js'></script>
<script src='js/id/modes/draw_line.js'></script>
<script src='js/id/modes/move_way.js'></script>
<script src='js/id/modes/move.js'></script>
<script src='js/id/modes/select.js'></script>
<script src='js/id/operations.js'></script>
+40 -7
View File
@@ -278,6 +278,7 @@ button {
transition: background 100ms;
}
button:focus,
button:hover {
background-color: #ececec;
}
@@ -641,6 +642,26 @@ div.combobox {
border-right: 5px solid transparent;
}
.rowselect .item {
cursor: pointer;
}
.rowselect .item label:hover{
background-color: #ececec;
}
.rowselect .item label {
padding: 5px;
width: 100%;
cursor: pointer;
display: block;
text-align: center;
}
.rowselect .item div {
text-align: center;
}
/* Address input */
.preset-input .addr-housename {
@@ -653,7 +674,7 @@ div.combobox {
border-right: none;
}
.preset-input .addr-streetname {
.preset-input .addr-street {
width: 80%;
}
@@ -756,17 +777,25 @@ div.combobox {
margin-bottom: 20px;
}
.preset-grid.filtered .grid-entry:first-child,
.preset-grid .grid-entry .grid-inner:hover {
.preset-grid.filtered .grid-entry:first-child {
background: #ececec;
}
.preset-grid .grid-entry .grid-inner:hover {
background: inherit;
.grid-entry .preset-help {
position: absolute;
bottom: 0px;
right: 0px;
width: 20px;
height: 20px;
background: black;
}
.grid-entry .grid-inner {
position: absolute;
.grid-entry .preset-help:hover {
background: grey;
}
.preset-inspect {
position: relative;
}
.grid-entry .preset-icon-fill.area {
@@ -1099,6 +1128,10 @@ img.tile {
-webkit-transform-origin:0 0;
-moz-transform-origin:0 0;
-o-transform-origin:0 0;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
#surface {
+11 -6
View File
@@ -229,6 +229,11 @@ path.area.fill {
fill-rule: evenodd;
}
path.line.stroke {
stroke: white;
stroke-width: 2;
}
path.stroke.tag-natural {
stroke: #b6e199;
stroke-width:1;
@@ -507,28 +512,28 @@ svg[data-zoom="16"] path.casing.tag-highway-construction {
/* railways */
path.stroke.tag-railway {
.line.stroke.tag-railway {
stroke: #eee;
stroke-width: 2;
stroke-linecap: butt;
stroke-dasharray: 12,12;
}
path.casing.tag-railway {
.line.casing.tag-railway {
stroke: #555;
stroke-width: 4;
}
path.stroke.tag-railway-abandoned {
.line.stroke.tag-railway-abandoned {
stroke: #eee;
}
path.casing.tag-railway-abandoned {
.line.casing.tag-railway-abandoned {
stroke: #999;
}
path.stroke.tag-railway-subway {
.line.stroke.tag-railway-subway {
stroke: #666;
}
path.casing.tag-railway-subway {
.line.casing.tag-railway-subway {
stroke: #222;
}
-10
View File
@@ -15,16 +15,6 @@ In order to unify the construction interface for these two styles, classical cla
[instanceof trick](http://ejohn.org/blog/simple-class-instantiation/). This allows instantiation
of both module pattern classes and classical classes to be done without using `new`.
Function names
--------------
Anything that creates and calls an Action should be prefixed with do:
doSetLatLon(lat,lon)
Anything that is called by an Action, to do the actual work, should be prefixed with an underscore:
_setLatLon(lat,lon)
and commented as such. Underscores are also used to prefix private methods.
File naming
-----------
The filename should be the name of the base class. You can add subclasses within
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+4 -3
View File
@@ -20,11 +20,11 @@
<script src='js/lib/jxon.js'></script>
<script src='js/lib/d3.typeahead.js'></script>
<script src='js/lib/d3.combobox.js'></script>
<script src='js/lib/d3.rowselect.js'></script>
<script src='js/lib/d3.geo.tile.js'></script>
<script src='js/lib/d3.size.js'></script>
<script src='js/lib/d3.trigger.js'></script>
<script src='js/lib/d3.keybinding.js'></script>
<script src='js/lib/d3.tail.js'></script>
<script src='js/lib/d3-compat.js'></script>
<script src='js/lib/bootstrap-tooltip.js'></script>
<script src='js/lib/rtree.js'></script>
@@ -91,7 +91,8 @@
<script src='js/id/ui/taglist.js'></script>
<script src='js/id/ui/presetgrid.js'></script>
<script src='js/id/ui/tageditor.js'></script>
<script src='js/id/ui/address.js'></script>
<script src='js/id/ui/tail.js'></script>
<script src='js/id/ui/preset/address.js'></script>
<script src='js/id/actions.js'></script>
<script src="js/id/actions/add_midpoint.js"></script>
@@ -107,7 +108,7 @@
<script src='js/id/actions/join.js'></script>
<script src='js/id/actions/merge.js'></script>
<script src='js/id/actions/move_node.js'></script>
<script src='js/id/actions/move_way.js'></script>
<script src='js/id/actions/move.js'></script>
<script src='js/id/actions/rotate_way.js'></script>
<script src='js/id/actions/circularize.js'></script>
<script src='js/id/actions/orthogonalize.js'></script>
+15
View File
@@ -1,5 +1,11 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as
iD.actions.DeleteRelation = function(relationId) {
function deleteEntity(entity, graph) {
return !graph.parentWays(entity).length &&
!graph.parentRelations(entity).length &&
!entity.hasInterestingTags();
}
return function(graph) {
var relation = graph.entity(relationId);
@@ -8,6 +14,15 @@ iD.actions.DeleteRelation = function(relationId) {
graph = graph.replace(parent.removeMember(relationId));
});
_.uniq(_.pluck(relation.members, 'id')).forEach(function(memberId) {
graph = graph.replace(relation.removeMember(memberId));
var entity = graph.entity(memberId);
if (deleteEntity(entity, graph)) {
graph = iD.actions.DeleteMultiple([memberId])(graph);
}
});
return graph.remove(relation);
};
};
+10 -12
View File
@@ -1,5 +1,11 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as
iD.actions.DeleteWay = function(wayId) {
function deleteNode(node, graph) {
return !graph.parentWays(node).length &&
!graph.parentRelations(node).length &&
!node.hasInterestingTags();
}
return function(graph) {
var way = graph.entity(wayId);
@@ -8,20 +14,12 @@ iD.actions.DeleteWay = function(wayId) {
graph = graph.replace(parent.removeMember(wayId));
});
way.nodes.forEach(function(nodeId) {
var node = graph.entity(nodeId);
// Circular ways include nodes more than once, so they
// can be deleted on earlier iterations of this loop.
if (!node) return;
_.uniq(way.nodes).forEach(function(nodeId) {
graph = graph.replace(way.removeNode(nodeId));
if (!graph.parentWays(node).length &&
!graph.parentRelations(node).length) {
if (!node.hasInterestingTags()) {
graph = graph.remove(node);
}
var node = graph.entity(nodeId);
if (deleteNode(node, graph)) {
graph = graph.remove(node);
}
});
+31
View File
@@ -0,0 +1,31 @@
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
iD.actions.Move = function(ids, delta, projection) {
function addNodes(ids, nodes, graph) {
ids.forEach(function(id) {
var entity = graph.entity(id);
if (entity.type === 'node') {
nodes.push(id);
} else if (entity.type === 'way') {
nodes.push.apply(nodes, entity.nodes);
} else {
addNodes(_.pluck(entity.members, 'id'), nodes, graph);
}
});
}
return function(graph) {
var nodes = [];
addNodes(ids, nodes, graph);
_.uniq(nodes).forEach(function(id) {
var node = graph.entity(id),
start = projection(node.loc),
end = projection.invert([start[0] + delta[0], start[1] + delta[1]]);
graph = graph.replace(node.move(end));
});
return graph;
};
};
-14
View File
@@ -1,14 +0,0 @@
iD.actions.MoveWay = function(wayId, delta, projection) {
return function(graph) {
return graph.update(function(graph) {
var way = graph.entity(wayId);
_.uniq(way.nodes).forEach(function(id) {
var node = graph.entity(id),
start = projection(node.loc),
end = projection.invert([start[0] + delta[0], start[1] + delta[1]]);
graph = graph.replace(node.move(end));
});
});
};
};
+2 -1
View File
@@ -98,7 +98,8 @@ iD.behavior.DragNode = function(context) {
}
}
context.replace(iD.actions.MoveNode(entity.id, loc));
context.replace(iD.actions.MoveNode(entity.id, loc),
t('operations.move.annotation.' + entity.geometry(context.graph())));
}
function end(entity) {
+1 -1
View File
@@ -240,7 +240,7 @@ iD.History = function(context) {
context.storage(getKey('nextIDs', null));
context.storage(getKey('index', null));
stack = JSON.parse(json).map(function(d, i) {
stack = JSON.parse(json).map(function(d) {
d.graph = iD.Graph(stack[0].graph).load(d.entities);
return d;
});
+4 -3
View File
@@ -1,5 +1,3 @@
iD.Tree = function(graph) {
var rtree = new RTree(),
@@ -58,7 +56,10 @@ iD.Tree = function(graph) {
});
var created = diff.created().concat(queuedCreated);
modified = d3.values(diff.addParents(modified)).concat(queuedModified);
modified = d3.values(diff.addParents(modified))
// some parents might be created, not modified
.filter(function(d) { return !!graph.entity(d.id); })
.concat(queuedModified);
queuedCreated = [];
queuedModified = [];
+3 -2
View File
@@ -4,7 +4,7 @@ window.iD = function () {
// https://github.com/systemed/iD/issues/772
// http://mathiasbynens.be/notes/localstorage-pattern#comment-9
try { storage = localStorage } catch (e) {}
try { storage = localStorage; } catch (e) {}
storage = storage || {};
context.storage = function(k, v) {
@@ -106,7 +106,8 @@ window.iD = function () {
context.background()
.source(_.find(iD.layers, function(l) {
if (l.data.sourcetag === q.layer) {
return (detected = true);
detected = true;
return true;
}
}));
}
+4 -5
View File
@@ -10,13 +10,12 @@ iD.modes.AddLine = function(context) {
var behavior = iD.behavior.AddWay(context)
.on('start', start)
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode),
defaultTags = {highway: 'residential'};
.on('startFromNode', startFromNode);
function start(loc) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
way = iD.Way();
context.perform(
iD.actions.AddEntity(node),
@@ -29,7 +28,7 @@ iD.modes.AddLine = function(context) {
function startFromWay(other, loc, index) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
way = iD.Way();
context.perform(
iD.actions.AddEntity(node),
@@ -52,7 +51,7 @@ iD.modes.AddLine = function(context) {
context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph));
} else {
var way = iD.Way({tags: defaultTags});
var way = iD.Way();
context.perform(
iD.actions.AddEntity(way),
+8 -19
View File
@@ -4,14 +4,13 @@ iD.modes.Move = function(context, entityIDs) {
button: 'browse'
};
var keybinding = d3.keybinding('move'),
entities = entityIDs.map(context.entity);
var keybinding = d3.keybinding('move');
mode.enter = function() {
var origin,
nudgeInterval,
annotation = entities.length === 1 ?
t('operations.move.annotation.' + context.geometry(entities[0].id)) :
annotation = entityIDs.length === 1 ?
t('operations.move.annotation.' + context.geometry(entityIDs[0])) :
t('operations.move.annotation.multiple');
context.perform(
@@ -57,29 +56,19 @@ iD.modes.Move = function(context, entityIDs) {
origin = context.map().mouseCoordinates();
entities.forEach(function(entity) {
if (entity.type === 'way') {
context.replace(
iD.actions.MoveWay(entity.id, delta, context.projection));
} else if (entity.type === 'node') {
var start = context.projection(context.entity(entity.id).loc),
end = [start[0] + delta[0], start[1] + delta[1]],
loc = context.projection.invert(end);
context.replace(iD.actions.MoveNode(entity.id, loc));
}
});
context.replace(iD.actions.Noop(), annotation);
context.replace(
iD.actions.Move(entityIDs, delta, context.projection),
annotation);
}
function finish() {
d3.event.stopPropagation();
context.enter(iD.modes.Select(context, entityIDs, true));
context.enter(iD.modes.Select(context, entityIDs));
}
function cancel() {
context.pop();
context.enter(iD.modes.Select(context, entityIDs, true));
context.enter(iD.modes.Select(context, entityIDs));
}
function undone() {
+1 -2
View File
@@ -4,7 +4,7 @@ iD.modes.Select = function(context, selection, initial) {
button: 'browse'
};
var inspector = iD.ui.Inspector().initial(!!initial),
var inspector = iD.ui.Inspector(context).initial(!!initial),
keybinding = d3.keybinding('select'),
timeout = null,
behaviors = [
@@ -84,7 +84,6 @@ iD.modes.Select = function(context, selection, initial) {
if (entity) {
inspector
.context(context)
.presetData(context.connection().presetData());
context.container()
+7 -13
View File
@@ -4,8 +4,6 @@ iD.OAuth = function(context) {
keys,
oauth = {};
function keyclean(x) { return x.replace(/\W/g, ''); }
function timenonce(o) {
o.oauth_timestamp = ohauth.timestamp();
o.oauth_nonce = ohauth.nonce();
@@ -73,17 +71,13 @@ iD.OAuth = function(context) {
['top', screen.height / 2 - h / 2]].map(function(x) {
return x.join('=');
}).join(','),
popup = window.open("about:blank", 'oauth_window', settings),
locationCheck = window.setInterval(function() {
if (popup.closed) return window.clearInterval(locationCheck);
if (popup.location.search) {
var search = popup.location.search,
oauth_token = ohauth.stringQs(search.slice(1));
popup.close();
get_access_token(oauth_token);
window.clearInterval(locationCheck);
}
}, 100);
popup = window.open("about:blank", 'oauth_window', settings);
window.authComplete = function(token) {
var oauth_token = ohauth.stringQs(token);
get_access_token(oauth_token);
delete window.authComplete;
};
function reqTokenDone(err, xhr) {
if (err) callback(err);
+2
View File
@@ -11,6 +11,8 @@ iD.operations.Delete = function(selection, context) {
context.perform(
iD.actions.DeleteMultiple(selection),
annotation);
context.enter(iD.modes.Browse(context));
};
operation.available = function() {
+1 -1
View File
@@ -6,7 +6,7 @@ iD.operations.Move = function(selection, context) {
operation.available = function() {
return selection.length > 1 ||
context.entity(selection[0]).type === 'way';
context.entity(selection[0]).type !== 'node';
};
operation.enabled = function() {
+15 -3
View File
@@ -1,7 +1,19 @@
iD.presetData = function() {
var other = {
name: 'other',
title: 'Other',
icon: 'marker-stroked',
match: {
tags: {},
type: ['node', 'line', 'area']
},
form: []
};
var presets = {},
data = [],
categories = {},
data = [other],
categories = [],
defaults = {
node: [],
area: [],
@@ -16,7 +28,7 @@ iD.presetData = function() {
presets.data = function(_) {
if (!arguments.length) return data;
data = _.presets;
data = _.presets.concat([other]);
categories = _.categories;
defaults = _.defaults;
return presets;
+1 -1
View File
@@ -21,7 +21,7 @@ iD.Map = function(context) {
areas = iD.svg.Areas(roundedProjection),
midpoints = iD.svg.Midpoints(roundedProjection),
labels = iD.svg.Labels(roundedProjection),
tail = d3.tail(),
tail = iD.ui.Tail(),
surface, tilegroup;
function map(selection) {
+45 -14
View File
@@ -14,6 +14,8 @@ iD.taginfo = function() {
line: 'ways'
};
var cache = this.cache = {};
function sets(parameters, n, o) {
if (parameters.geometry && o[parameters.geometry]) {
parameters[n] = o[parameters.geometry];
@@ -30,16 +32,25 @@ iD.taginfo = function() {
}
function clean(parameters) {
return _.omit(parameters, 'geometry');
return _.omit(parameters, 'geometry', 'debounce');
}
function shorten(parameters) {
if (!parameters.query) {
delete parameters.query;
} else {
parameters.query = parameters.query.slice(0, 3);
}
return parameters;
}
function popularKeys(parameters) {
var pop_field = 'count_all_fraction';
if (parameters.filter) pop_field = 'count_' + parameters.filter + '_fraction';
return function(d) { return parseFloat(d[pop_field]) > 0.01; };
var pop_field = 'count_all';
if (parameters.filter) pop_field = 'count_' + parameters.filter;
return function(d) { return parseFloat(d[pop_field]) > 10000; };
}
function popularValues(parameters) {
function popularValues() {
return function(d) { return parseFloat(d.fraction) > 0.01; };
}
@@ -52,38 +63,58 @@ iD.taginfo = function() {
};
}
var debounced = _.debounce(d3.json, 100, true);
function request(url, debounce, callback) {
if (cache[url]) {
callback(null, cache[url]);
} else if (debounce) {
debounced(url, done);
} else {
d3.json(url, done);
}
function done(err, data) {
if (!err) cache[url] = data;
callback(err, data);
}
}
taginfo.keys = function(parameters, callback) {
parameters = clean(setSort(setFilter(parameters)));
d3.json(endpoint + 'keys/all?' +
var debounce = parameters.debounce;
parameters = clean(shorten(setSort(setFilter(parameters))));
request(endpoint + 'keys/all?' +
iD.util.qsString(_.extend({
rp: 6,
rp: 10,
sortname: 'count_all',
sortorder: 'desc',
page: 1
}, parameters)), function(err, d) {
}, parameters)), debounce, function(err, d) {
if (err) return callback(err);
callback(null, d.data.filter(popularKeys(parameters)).map(valKey));
});
};
taginfo.values = function(parameters, callback) {
parameters = clean(setSort(setFilter(parameters)));
d3.json(endpoint + 'key/values?' +
var debounce = parameters.debounce;
parameters = clean(shorten(setSort(setFilter(parameters))));
request(endpoint + 'key/values?' +
iD.util.qsString(_.extend({
rp: 20,
sortname: 'count_all',
sortorder: 'desc',
page: 1
}, parameters)), function(err, d) {
}, parameters)), debounce, function(err, d) {
if (err) return callback(err);
callback(null, d.data.filter(popularValues()).map(valKeyDescription), parameters);
});
};
taginfo.docs = function(parameters, callback) {
var debounce = parameters.debounce;
parameters = clean(setSort(parameters));
d3.json(endpoint + 'tag/wiki_pages?' +
iD.util.qsString(parameters), callback);
request(endpoint + (parameters.value ? 'tag/wiki_pages?' : 'key/wiki_pages?') +
iD.util.qsString(parameters), debounce, callback);
};
taginfo.endpoint = function(_) {
+3 -3
View File
@@ -80,9 +80,9 @@ iD.svg.Labels = function(projection) {
.data(entities, iD.Entity.key)
.attr({
'startOffset': '50%',
'xlink:href': function(d, i) { return '#halo-' + d.id; }
'xlink:href': function(d) { return '#halo-' + d.id; }
})
.text(function(d, i) { return name(d); });
.text(function(d) { return name(d); });
texts.exit().remove();
@@ -97,7 +97,7 @@ iD.svg.Labels = function(projection) {
halos.enter()
.append('path')
.style('stroke-width', get(labels, 'font-size'))
.attr('id', function(d, i) { return 'halo-' + d.id; })
.attr('id', function(d) { return 'halo-' + d.id; })
.attr('class', classes);
halos.attr('d', get(labels, 'lineString'));
+1 -1
View File
@@ -127,7 +127,7 @@ iD.svg.Lines = function(projection) {
text.selectAll('.textpath')
.filter(filter)
.attr('xlink:href', function(d, i) { return '#shadow-' + d.id; })
.attr('xlink:href', function(d) { return '#shadow-' + d.id; })
.text(function(d) {
// adding longer text than necessary, since overflow is hidden
return (new Array(Math.floor(lengths[d.id] * 1.1))).join(arrowtext);
+1 -1
View File
@@ -2,7 +2,7 @@ iD.svg.MemberClasses = function(graph) {
var tagClassRe = /^member-?/;
return function memberClassesSelection(selection) {
selection.each(function memberClassesEach(d, i) {
selection.each(function memberClassesEach(d) {
var classes, value = this.className;
if (value.baseVal !== undefined) value = value.baseVal;
+1 -1
View File
@@ -6,5 +6,5 @@ iD.ui.Attribution = function(context) {
selection
.append('span')
.attr('class', 'provided-by');
}
};
};
+9 -7
View File
@@ -46,18 +46,18 @@ iD.ui.Commit = function(context) {
var userLink = d3.select(document.createElement('div'));
userLink.append('a')
.attr('class','user-info')
.text(user.display_name)
.attr('href', connection.url() + '/user/' + user.display_name)
.attr('target', '_blank');
if (user.image_url) {
userLink.append('img')
.attr('src', user.image_url)
.attr('class', 'icon icon-pre-text user-icon');
}
userLink.append('a')
.attr('class','user-info')
.text(user.display_name)
.attr('href', connection.url() + '/user/' + user.display_name)
.attr('target', '_blank');
commentSection.append('p')
.attr('class', 'commit-info')
.html(t('commit.upload_explanation', {user: userLink.html()}));
@@ -108,7 +108,9 @@ iD.ui.Commit = function(context) {
.enter()
.append('li');
warningLi.append('button')
// only show the fix icon when an entity is given
warningLi.filter(function(d) { return d.entity; })
.append('button')
.attr('class', 'minor')
.on('click', event.fix)
.append('span')
+3 -3
View File
@@ -4,9 +4,9 @@ iD.ui.Contributors = function(context) {
limit = 4,
entities = context.intersects(context.map().extent());
for (var i in entities) {
if (entities[i].user) users[entities[i].user] = true;
}
entities.forEach(function(entity) {
if (entity && entity.user) users[entity.user] = true;
});
var u = Object.keys(users),
subset = u.slice(0, u.length > limit ? limit - 1 : limit);
+3 -3
View File
@@ -59,7 +59,7 @@ iD.ui.Geocoder = function(context) {
}
function hide() { setVisible(false); }
function toggle() { setVisible(gcForm.classed('hide')); }
function toggle() { tooltip.hide(button); setVisible(gcForm.classed('hide')); }
function setVisible(show) {
if (show !== shown) {
@@ -71,13 +71,13 @@ iD.ui.Geocoder = function(context) {
shown = show;
}
}
var tooltip = bootstrap.tooltip().placement('right');
var button = selection.append('button')
.attr('tabindex', -1)
.attr('title', t('geocoder.title'))
.on('click', toggle)
.call(bootstrap.tooltip()
.placement('right'));
.call(tooltip);
button.append('span')
.attr('class', 'icon geocode');
+10 -46
View File
@@ -1,15 +1,11 @@
iD.ui.Inspector = function() {
iD.ui.Inspector = function(context) {
var event = d3.dispatch('changeTags', 'close', 'change'),
taginfo = iD.taginfo(),
presetData = iD.presetData(),
initial = false,
inspectorbody,
entity,
presetUI,
presetGrid,
tagList,
tagEditor,
context;
tagEditor;
function inspector(selection) {
@@ -21,29 +17,27 @@ iD.ui.Inspector = function() {
inspectorbody = selection.append('div')
.attr('class', 'fillL'),
selection.append('div')
.attr('class', 'inspector-actions pad1 fillD col12')
.call(drawButtons);
presetGrid = iD.ui.PresetGrid()
presetGrid = iD.ui.PresetGrid(context)
.presetData(presetData)
.entity(entity)
.context(context)
.on('message', changeMessage)
.on('choose', function(preset) {
inspectorbody.call(tagEditor, preset);
});
tagEditor = iD.ui.TagEditor()
tagEditor = iD.ui.TagEditor(context)
.presetData(presetData)
.tags(entity.tags)
.context(context)
.on('message', changeMessage)
.on('change', function() {
.on('changeTags', function() {
event.changeTags(entity, inspector.tags());
})
.on('close', function() {
event.close(entity);
})
.on('choose', function() {
inspectorbody.call(presetGrid);
inspectorbody.call(presetGrid, true);
});
function changeMessage(msg) { message.text(msg);}
@@ -58,32 +52,7 @@ iD.ui.Inspector = function() {
selection.call(iD.ui.Toggle(true));
}
function drawButtons(selection) {
var entity = selection.datum();
var inspectorButton = selection.append('button')
.attr('class', 'apply action')
.on('click', apply);
inspectorButton.append('span')
.attr('class','label')
.text(t('inspector.okay'));
var minorButtons = selection.append('div')
.attr('class','minor-buttons fl');
minorButtons.append('a')
.attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId())
.attr('target', '_blank')
.text(t('inspector.view_on_osm'));
}
function apply(entity) {
event.changeTags(entity, inspector.tags());
event.close(entity);
}
inspector.tags = function(tags) {
inspector.tags = function() {
if (!arguments.length) {
return tagEditor.tags();
} else {
@@ -102,10 +71,5 @@ iD.ui.Inspector = function() {
return inspector;
};
inspector.context = function(_) {
context = _;
return inspector;
};
return d3.rebind(inspector, event, 'on');
};
+1 -1
View File
@@ -27,7 +27,7 @@ iD.ui.keyReference = function(selection) {
.append('tr');
var cols = rows.selectAll('td')
.data(function(d, i) {
.data(function(d) {
return [d.value, d.description || "", d.count];
})
.enter()
+2 -3
View File
@@ -1,7 +1,6 @@
iD.ui.Lasso = function() {
var center, box,
group,
var box, group,
a = [0, 0],
b = [0, 0];
@@ -50,7 +49,7 @@ iD.ui.Lasso = function() {
return lasso;
};
lasso.close = function(selection) {
lasso.close = function() {
if (group) {
group.call(iD.ui.Toggle(false, function() {
d3.select(this).remove();
+4 -3
View File
@@ -18,20 +18,21 @@ iD.ui.LayerSwitcher = function(context) {
.append('div').attr('class', 'content fillD map-overlay hide'),
shown = false;
var tooltip = bootstrap.tooltip().placement('right');
var button = selection
.append('button')
.attr('tabindex', -1)
.attr('class', 'fillD')
.attr('title', t('layerswitcher.description'))
.on('click.layerswitcher-toggle', toggle)
.call(bootstrap.tooltip()
.placement('right'));
.call(tooltip);
button.append('span')
.attr('class', 'layers icon');
function hide() { setVisible(false); }
function toggle() { setVisible(content.classed('hide')); }
function toggle() { tooltip.hide(button); setVisible(content.classed('hide')); }
function setVisible(show) {
if (show !== shown) {
+33 -25
View File
@@ -1,7 +1,6 @@
iD.ui.preset = function() {
var event = d3.dispatch('change'),
iD.ui.preset = function(context) {
var event = d3.dispatch('change', 'setTags'),
taginfo = iD.taginfo(),
context,
entity,
type,
hidden,
@@ -13,7 +12,11 @@ iD.ui.preset = function() {
var tags = _.clone(preset.match.tags);
sections.selectAll('input,select')
.each(function(d) {
tags[d.key] = this.value;
if (d && d.key) {
tags[d.key] = d.type === 'combo' || d.type === 'select' ?
this.value.replace(' ', '_') :
this.value;
}
});
return tags;
}
@@ -22,8 +25,15 @@ iD.ui.preset = function() {
if (!sections) return;
sections.selectAll('input,select')
.each(function(d) {
this.value = tags[d.key] || '';
if (d && d.key) {
this.value = tags[d.key] || '';
if (d.type === 'combo' || d.type === 'select') {
this.value = this.value.replace('_', ' ');
}
}
});
event.setTags();
}
function clean(o) {
@@ -66,20 +76,25 @@ iD.ui.preset = function() {
.attr('id', 'input-' + d.key)
.attr('placeholder', 'http://example.com/');
break;
case 'check':
i = this.append('input')
.attr('type', 'checkbox')
.attr('id', 'input-' + d.key);
break;
case 'select':
wrap = this.append('span').attr('class', 'input-wrap-position'),
i = wrap.append('input').attr('type', 'text');
wrap.call(d3.combobox().data(d.options.map(function(d) {
return {
title: d,
value: d
};
})));
if (d.options.length <= 5) {
var select = d3.rowselect()
.data(d.options)
.on('change', key);
i.datum(d);
wrap.call(select);
event.on('setTags.' + d.key, select.update);
} else {
wrap.call(d3.combobox().data(d.options.map(function(d) {
var o = {};
o.title = o.value = d.replace('_', ' ');
return o;
})));
}
break;
case 'combo':
var combobox = d3.combobox();
@@ -90,7 +105,7 @@ iD.ui.preset = function() {
key: d.key
}, function(err, data) {
if (!err) combobox.data(data.map(function(d) {
d.title = d.value;
d.title = d.value = d.value.replace('_', ' ');
return d;
}));
});
@@ -130,8 +145,7 @@ iD.ui.preset = function() {
if (d.type === 'address') {
wrap.append('div')
.attr('class', 'col9 preset-input', d)
.call(iD.ui.preset.address()
.context(context)
.call(iD.ui.preset.address(context)
.on('change', key)
.entity(entity));
}
@@ -157,12 +171,6 @@ iD.ui.preset = function() {
return clean(getTags());
};
presets.context = function(_) {
if (!arguments.length) return context;
context = _;
return presets;
};
presets.entity = function(_) {
if (!arguments.length) return entity;
entity = _;
@@ -1,14 +1,13 @@
iD.ui.preset.address = function() {
iD.ui.preset.address = function(context) {
var event = d3.dispatch('change'),
context,
entity;
function getStreets() {
var l = entity.loc || context.entity(entity.nodes[0]).loc,
var extent = entity.extent(context.graph()),
l = extent.center(),
dist = iD.geo.metresToCoordinates(l, [200, 200]),
extent = entity.extent(context.graph()),
box = iD.geo.Extent(
[extent[0][0] - dist[0], extent[0][1] - dist[1]],
[extent[1][0] + dist[0], extent[1][1] + dist[1]]);
@@ -56,12 +55,12 @@ iD.ui.preset.address = function() {
var streetwrap = selection.append('span')
.attr('class', 'input-wrap-position')
.datum({ 'key': 'addr:streetname' });
.datum({ 'key': 'addr:street' });
streetwrap.append('input')
.property('type', 'text')
.attr('placeholder', 'Oak Street')
.attr('class', 'addr-streetname')
.attr('class', 'addr-street')
.on('blur', change)
.on('change', change);
@@ -74,11 +73,5 @@ iD.ui.preset.address = function() {
return address;
};
address.context = function(_) {
if (!arguments.length) return context;
context = _;
return address;
};
return d3.rebind(address, event, 'on');
};
+125 -55
View File
@@ -1,15 +1,15 @@
iD.ui.PresetGrid = function() {
iD.ui.PresetGrid = function(context) {
var event = d3.dispatch('choose', 'message'),
entity,
context,
presetData;
presetData,
taginfo = iD.taginfo();
function presetgrid(selection) {
function presetgrid(selection, preset) {
selection.html('');
var viable = presetData.match(entity);
event.message('What kind of ' + entity.geometry(context.graph()) + ' are you adding?');
event.message(t('inspector.choose'));
var searchwrap = selection.append('div')
.attr('class', 'preset-grid-search-wrap inspector-inner');
@@ -24,8 +24,7 @@ iD.ui.PresetGrid = function() {
.on('keyup', function() {
// enter
if (d3.event.keyCode === 13) {
var chosen = grid.selectAll('.grid-entry:first-child').datum();
if (chosen) event.choose(chosen);
choose(grid.selectAll('.grid-entry:first-child').datum());
} else {
var value = search.property('value'),
presets = filter(value);
@@ -36,6 +35,11 @@ iD.ui.PresetGrid = function() {
});
search.node().focus();
if (preset) {
selection.append('div')
.attr('class', 'inspector-actions pad1 fillD col12')
.call(drawButtons);
}
function filter(value) {
if (!value) return presetData.defaults(entity);
@@ -56,54 +60,128 @@ iD.ui.PresetGrid = function() {
return iD.util.editDistance(value, a.name) - iD.util.editDistance(value, b.name);
}).filter(function(d) {
return iD.util.editDistance(value, d.name) - d.name.length + value.length < 3;
return iD.util.editDistance(value, d.name) - d.name.length + value.length < 3 ||
d.name === 'other';
});
}
function choose(d) {
// Category
if (d.members) {
search.property('value', '');
viable = presetData.categories(d.name);
drawGrid(selection, viable);
// Preset
} else {
event.choose(d);
}
}
function name(d) { return d.name; }
function drawGrid(selection, presets) {
var entries = selection
.selectAll('button.grid-entry')
.data(presets.slice(0, 12), name);
var entered = entries.enter()
.append('button')
.attr('class', 'grid-entry col3')
.on('click', choose);
entered.append('div')
.attr('class', function(d) {
var s = 'preset-icon-fill ' + entity.geometry(context.graph());
if (d.members) {
s += 'category';
} else {
for (var i in d.match.tags) {
s += ' tag-' + i + ' tag-' + i + '-' + d.match.tags[i];
}
}
return s;
});
entered.append('div')
.attr('class', function(d) { return 'preset-' + d.icon + ' icon'; });
var presetinspect;
entered.append('span').attr('class','label').text(name);
entered.append('div')
.attr('tabindex', -1)
.attr('class', 'preset-help')
.on('click', function(d) {
// Display description box inline
d3.event.stopPropagation();
var entry = this.parentNode,
index,
entries = selection.selectAll('button.grid-entry');
if (presetinspect && presetinspect.remove().datum() === d) {
presetinspect = null;
return;
}
entries.each(function(d, i) {
if (this === entry) index = i;
});
var selector = '.grid-entry:nth-child(' + (Math.floor(index/4) * 4 + 5 ) + ')';
presetinspect = selection.insert('div', selector)
.attr('class', 'preset-inspect col12')
.datum(d);
presetinspect.append('h2').text(d.title || d.name);
var description = presetinspect.append('p');
var link = presetinspect.append('a');
var params = {},
locale = iD.detect().locale.split('-')[0] || 'en';
params.key = Object.keys(d.match.tags)[0];
if (d.match.tags[params.key] !== '*') {
params.value = d.match.tags[params.key];
}
taginfo.docs(params, function(err, data) {
var doc = _.find(data, function(d) { return d.lang === locale; }) ||
_.find(data, function(d) { return d.lang === 'en'; });
description.text(doc.description);
link.attr('href', 'http://wiki.openstreetmap.org/wiki/' + encodeURIComponent(doc.title));
link.text(doc.title);
});
})
.append('span')
.attr('class', 'icon inspect');
entries.exit().remove();
entries.order();
}
}
function name(d) { return d.name; }
function cancel() {
event.choose();
}
function drawGrid(selection, presets) {
function drawButtons(selection) {
var entries = selection
.selectAll('button.grid-entry')
.data(presets.slice(0, 12), name);
var inspectorButton = selection.append('button')
.attr('class', 'apply action')
.on('click', cancel);
var entered = entries.enter()
.append('button')
.attr('class', 'grid-entry col3')
.on('click', function(d) {
// Category
if (d.members) {
drawGrid(selection, presetData.categories(d.name));
// Preset
} else {
event.choose(d);
}
});
entered.append('div')
.attr('class', function(d) {
var s = 'preset-icon-fill ' + entity.geometry(context.graph());
if (d.members) {
s += 'category';
} else {
for (var i in d.match.tags) {
s += ' tag-' + i + ' tag-' + i + '-' + d.match.tags[i];
}
}
return s;
});
entered.append('div')
.attr('class', function(d) { return 'preset-' + d.icon + ' icon'; });
entered.append('span').attr('class','label').text(name);
entries.exit().remove();
entries.order();
inspectorButton.append('span')
.attr('class','label')
.text(t('commit.cancel'));
}
presetgrid.presetData = function(_) {
@@ -112,19 +190,11 @@ iD.ui.PresetGrid = function() {
return presetgrid;
};
presetgrid.context = function(_) {
if (!arguments.length) return context;
context = _;
return presetgrid;
};
presetgrid.entity = function(_) {
if (!arguments.length) return entity;
entity = _;
return presetgrid;
};
return d3.rebind(presetgrid, event, 'on');
};
+2
View File
@@ -6,6 +6,8 @@ iD.ui.RadialMenu = function(operations) {
if (!operations.length)
return;
selection.node().focus();
function click(operation) {
d3.event.stopPropagation();
operation();
+1 -1
View File
@@ -28,5 +28,5 @@ iD.ui.Splash = function(context) {
website: '<a href="http://ideditor.com/">ideditor.com</a>',
github: '<a href="https://github.com/systemed/iD">github.com</a>'
}));
}
};
};
+2 -2
View File
@@ -19,7 +19,7 @@ iD.ui.Success = function(connection) {
connection.changesetUrl(changeset.id);
header.append('a')
.attr('href', function(d) {
.attr('href', function() {
return connection.changesetUrl(changeset.id);
})
.attr('target', '_blank')
@@ -28,7 +28,7 @@ iD.ui.Success = function(connection) {
header.append('a')
.attr('target', '_blank')
.attr('href', function(d) {
.attr('href', function() {
return 'https://twitter.com/intent/tweet?source=webclient&text=' +
encodeURIComponent(message);
})
+59 -40
View File
@@ -1,20 +1,18 @@
iD.ui.TagEditor = function() {
var event = d3.dispatch('changeTags', 'choose', 'close', 'change', 'message'),
taginfo = iD.taginfo(),
iD.ui.TagEditor = function(context) {
var event = d3.dispatch('changeTags', 'choose', 'close', 'message'),
presetData = iD.presetData(),
inspectorbody,
entity,
tags,
name,
presetMatch,
selection_,
presetUI,
presetGrid,
tagList,
context;
tagList;
function tageditor(selection, preset) {
entity = selection.datum();
selection_ = selection;
var type = entity.type === 'node' ? entity.type : entity.geometry();
// preset was explicitly chosen
@@ -60,44 +58,44 @@ iD.ui.TagEditor = function() {
typewrap.append('h4').text('Type');
var typelabel = typewrap.append('button')
var typebutton = typewrap.append('button')
.attr('class','col12')
.on('click', function() {
event.choose();
});
typelabel.append('div')
typebutton.append('div')
.attr('class', 'icon icon-pre-text' + (presetMatch ? ' preset-' + presetMatch.icon : ''));
typebutton.node().focus();
var namewrap = headerwrap.append('div')
.attr('class', 'name col9 inspector-inner');
.attr('class', 'name col9 inspector-inner');
typelabel.append('span')
.attr('class','label')
.text(presetMatch ? presetMatch.name : 'Other');
typebutton.append('span')
.attr('class','label')
.text(presetMatch.name);
namewrap.append('h4').text('Name');
namewrap.append('h4').text(t('inspector.name'));
name = namewrap.append('input')
.attr('placeholder', 'unknown')
.attr('class', 'major')
.attr('type', 'text')
.property('value', entity.tags.name || 'this')
.property('value', entity.tags.name || '')
.on('blur', function() {
event.change();
event.changeTags();
});
presetUI = iD.ui.preset()
.context(context)
presetUI = iD.ui.preset(context)
.entity(entity)
.on('change', function(tags) {
event.change(tags);
.on('change', function() {
event.changeTags();
});
tagList = iD.ui.Taglist()
.context(context)
.on('change', function(tags) {
event.change(tags);
tagList = iD.ui.Taglist(context)
.on('change', function() {
event.changeTags();
});
var tageditorpreset = editorwrap.append('div')
@@ -105,26 +103,45 @@ iD.ui.TagEditor = function() {
if (presetMatch) {
tageditorpreset.call(presetUI
.preset(presetMatch));
.preset(presetMatch));
}
event.message('Edit ' + (presetMatch && presetMatch.name || ''));
event.message(t('inspector.editing', { type: presetMatch.name }));
var taglistwrap = editorwrap.append('div')
.attr('class','inspector-inner col12 fillL2').call(tagList);
editorwrap.append('div')
.attr('class','inspector-inner col12 fillL2').call(tagList, presetMatch.name === 'other');
selection.append('div')
.attr('class', 'inspector-actions pad1 fillD col12')
.call(drawButtons);
tageditor.tags(tags);
event.change(tags);
event.changeTags();
}
function drawHead(selection) {
var h2 = selection.append('h2');
function apply() {
event.changeTags();
event.close();
}
h2.append('span')
.attr('class', 'icon big icon-pre-text big-' + entity.geometry(context.graph()));
function drawButtons(selection) {
h2.append('span')
.text(entity.friendlyName());
var inspectorButton = selection.append('button')
.attr('class', 'apply action')
.on('click', apply);
inspectorButton.append('span')
.attr('class','label')
.text(t('inspector.okay'));
var minorButtons = selection.append('div')
.attr('class','minor-buttons fl');
minorButtons.append('a')
.attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId())
.attr('target', '_blank')
.text(t('inspector.view_on_osm'));
}
tageditor.tags = function(newtags) {
@@ -135,6 +152,13 @@ iD.ui.TagEditor = function() {
} else {
tags = _.clone(newtags);
if (presetUI && tagList) {
// change preset if necessary (undos/redos)
var newmatch = presetData.matchTags(entity.update({ tags: tags }));
if (newmatch !== presetMatch) {
return tageditor(selection_, newmatch);
}
name.property('value', tags.name || '');
presetUI.change(tags);
tagList.tags(_.omit(tags, _.keys(presetUI.tags() || {}).concat(['name'])));
@@ -148,10 +172,5 @@ iD.ui.TagEditor = function() {
return tageditor;
};
tageditor.context = function(_) {
context = _;
return tageditor;
};
return d3.rebind(tageditor, event, 'on');
};
+17 -17
View File
@@ -1,24 +1,25 @@
iD.ui.Taglist = function() {
iD.ui.Taglist = function(context) {
var event = d3.dispatch('change'),
taginfo = iD.taginfo(),
initial = false,
list,
context;
collapsebutton,
list;
function taglist(selection) {
function taglist(selection, expanded) {
var collapsebutton = selection.append('a')
collapsebutton = selection.append('a')
.attr('href','#')
.attr('class','hide-toggle')
.text('Additional tags')
.text(t('inspector.additional'))
.on('click', function() {
collapsebutton.classed('expanded', wrap.classed('hide'));
wrap.call(iD.ui.Toggle(wrap.classed('hide')));
selection.node().parentNode.scrollTop += 200;
});
})
.classed('expanded', expanded);
var wrap = selection.append('div')
.attr('class', 'hide');
.classed('hide', !expanded);
list = wrap.append('ul')
.attr('class', 'tag-list');
@@ -42,6 +43,8 @@ iD.ui.Taglist = function() {
function drawTags(tags) {
var entity = list.datum();
collapsebutton.text(t('inspector.additional') + ' (' + Object.keys(tags).length + ')');
tags = d3.entries(tags);
if (!tags.length) {
@@ -212,26 +215,28 @@ iD.ui.Taglist = function() {
var keyinput = key.select('input');
key.call(d3.combobox()
.fetcher(_.debounce(function(_, __, callback) {
.fetcher(function(_, __, callback) {
taginfo.keys({
debounce: true,
geometry: geometry,
query: keyinput.property('value')
}, function(err, data) {
if (!err) callback(sort(keyinput.property('value'), data));
});
}, 500)));
}));
var valueinput = value.select('input');
value.call(d3.combobox()
.fetcher(_.debounce(function(_, __, callback) {
.fetcher(function(_, __, callback) {
taginfo.values({
debounce: true,
key: keyinput.property('value'),
geometry: geometry,
query: valueinput.property('value')
}, function(err, data) {
if (!err) callback(sort(valueinput.property('value'), data));
});
}, 500)));
}));
}
function focusNewKey() {
@@ -265,10 +270,5 @@ iD.ui.Taglist = function() {
}
};
taglist.context = function(_) {
context = _;
return taglist;
};
return d3.rebind(taglist, event, 'on');
};
+16 -11
View File
@@ -1,21 +1,24 @@
d3.tail = function() {
iD.ui.Tail = function() {
var text = false,
container,
inner,
xmargin = 25,
tooltip_size = [0, 0],
selection_size = [0, 0],
transformProp = iD.util.prefixCSSProperty('Transform');
var tail = function(selection) {
function tail(selection) {
d3.select(window).on('resize.tail-size', function() {
selection_size = selection.size();
});
function setup() {
container = d3.select(document.body)
.append('div').attr('class', 'tail tooltip-inner');
.append('div')
.style('display', 'none')
.attr('class', 'tail tooltip-inner');
inner = container.append('div');
selection
.on('mousemove.tail', mousemove)
@@ -26,11 +29,16 @@ d3.tail = function() {
.on('mousemove.tail', mousemove);
selection_size = selection.size();
}
function show() {
container.style('display', 'block');
tooltip_size = container.size();
}
function mousemove() {
if (text === false) return;
if (container.style('display') === 'none') show();
var xoffset = ((d3.event.clientX + tooltip_size[0] + xmargin) > selection_size[0]) ?
-tooltip_size[0] - xmargin : xmargin;
container.classed('left', xoffset > 0);
@@ -46,12 +54,11 @@ d3.tail = function() {
function mouseover() {
if (d3.event.relatedTarget !== container.node() &&
text !== false) container.style('display', 'block');
text !== false) show();
}
if (!container) setup();
};
}
tail.text = function(_) {
if (!arguments.length) return text;
@@ -59,11 +66,9 @@ d3.tail = function() {
text = _;
container.style('display', 'none');
return tail;
} else if (container.style('display') == 'none') {
container.style('display', 'block');
}
text = _;
container.text(text);
inner.text(text);
tooltip_size = container.size();
return tail;
};
+1 -1
View File
@@ -36,5 +36,5 @@ iD.ui.Zoom = function(context) {
d3.select(document)
.call(keybinding);
}
};
};
+6
View File
@@ -15,6 +15,12 @@ iD.validate = function(changes, graph) {
if (tags.building && tags.building === 'yes') return 'building=yes';
}
if (changes.deleted.length > 100) {
warnings.push({
message: t('validations.many_deletions', { n: changes.deleted.length })
});
}
for (var i = 0; i < changes.created.length; i++) {
change = changes.created[i];
+20 -15
View File
@@ -15,15 +15,6 @@ d3.combobox = function() {
var idx = -1;
input = selection.select('input');
container = d3.select(document.body)
.insert('div', ':first-child')
.attr('class', 'combobox')
.style({
position: 'absolute',
display: 'none',
left: '0px'
});
selection.append('a', selection.select('input'))
.attr('class', 'combobox-carat')
.on('mousedown', stop)
@@ -64,14 +55,26 @@ d3.combobox = function() {
}
function show() {
container.style('display', 'block');
shown = true;
if (!shown) {
container = d3.select(document.body)
.insert('div', ':first-child')
.attr('class', 'combobox')
.style({
position: 'absolute',
display: 'block',
left: '0px'
});
shown = true;
}
}
function hide() {
idx = -1;
container.style('display', 'none');
shown = false;
if (shown) {
idx = -1;
container.remove();
shown = false;
}
}
function slowHide() {
@@ -189,7 +192,7 @@ d3.combobox = function() {
if (data.length &&
document.activeElement === input.node()) show();
else hide();
else return hide();
autocomplete(e, data);
@@ -230,6 +233,8 @@ d3.combobox = function() {
input.node().focus();
update('');
if (!container) return;
var entries = container.selectAll('a'),
height = container.node().scrollHeight / entries[0].length,
w = d3.select(window);
+9 -2
View File
@@ -2,7 +2,8 @@ d3.geo.tile = function() {
var size = [960, 500],
scale = 256,
scaleExtent = [0, 20],
translate = [size[0] / 2, size[1] / 2];
translate = [size[0] / 2, size[1] / 2],
zoomDelta = 0;
function bound(_) {
return Math.min(scaleExtent[1], Math.max(scaleExtent[0], _));
@@ -10,7 +11,7 @@ d3.geo.tile = function() {
function tile() {
var z = Math.max(Math.log(scale) / Math.LN2 - 8, 0),
z0 = bound(z | 0),
z0 = bound(Math.round(z + zoomDelta)),
k = Math.pow(2, z - z0 + 8),
origin = [(translate[0] - scale / 2) / k, (translate[1] - scale / 2) / k],
tiles = [],
@@ -53,5 +54,11 @@ d3.geo.tile = function() {
return tile;
};
tile.zoomDelta = function(_) {
if (!arguments.length) return zoomDelta;
zoomDelta = +_;
return tile;
};
return tile;
};
+63
View File
@@ -0,0 +1,63 @@
d3.rowselect = function() {
var input, data, wrap,
event = d3.dispatch('change');
var select = function(selection) {
input = selection.select('input')
.style('display', 'none');
wrap = selection.append('div')
.attr('class', 'rowselect');
var labels = wrap.selectAll('div')
.data(data)
.enter()
.append('div')
.style('display', 'inline-block')
.style('width', ~~(100 / data.length) + '%')
.attr('class', 'item')
.append('label')
.on('click', function() {
var checkbox = d3.select(this).select('input'),
val = !!checkbox.property('checked');
wrap.selectAll('input').property('checked', false);
checkbox.property('checked', val);
input.property('value', val ? checkbox.datum() : '');
event.change();
d3.event.stopPropagation();
});
var value = input.property('value');
labels.append('div')
.append('input')
.attr('type', 'checkbox');
labels.append('span').text(function(d) { return d; });
input.on('change.select', update);
};
function update() {
var value = input.property('value');
wrap.selectAll('input')
.property('checked', function(d) {
return d === value;
});
}
select.data = function(_) {
if (!arguments.length) return data;
data = _;
return select;
};
select.update = update;
return d3.rebind(select, event, 'on');
};
+76 -1484
View File
File diff suppressed because it is too large Load Diff
+3 -17
View File
@@ -1,23 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>iD</title>
<style>
body {
font:normal 20px/30px 'Helvetica Neue';
text-align:center;
}
</style>
</head>
<html><head></head>
<body>
<h1 id='land'></h1>
<script>
var i = 2;
window.setInterval(function() {
document.getElementById('land').innerHTML = (new Array(i++)).join('.');
i = (i > 8) ? 2 : i;
}, 200);
opener.authComplete(window.location.search.slice(1));
window.close();
</script>
</body>
</html>
+6 -1
View File
@@ -179,7 +179,11 @@ locale.da = {
new_tag: "Nyt tag",
edit_tags: "Ret tags",
okay: "Ok",
view_on_osm: "Vis på OSM"
view_on_osm: "Vis på OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
restore: {
@@ -221,6 +225,7 @@ locale.da = {
untagged_point: "Mangler et tag på punkt som ikke er del af en linje eller område",
untagged_line: "Mangler tag på linje",
untagged_area: "Mangler tag på område",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Dette tag {tag} mener denne linje skule være et område, men dette er ikke et område",
deprecated_tags: "Uønskede tags: {tags}"
}
+6 -1
View File
@@ -171,7 +171,11 @@ locale.de = {
new_tag: "Neues Attribut",
edit_tags: "Attribute bearbeiten",
okay: "OK",
view_on_osm: "auf OpenStreetMap ansehen"
view_on_osm: "auf OpenStreetMap ansehen",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.de = {
untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist",
untagged_line: "Linie ohne Attribute",
untagged_area: "Fläche ohne Attribute",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche",
deprecated_tags: "Veraltete Attribute: {tags}"
},
+7 -2
View File
@@ -112,7 +112,7 @@ locale.en = {
vertex: "Moved a node in a way.",
line: "Moved a line.",
area: "Moved an area.",
multiple: "Moved multiple objects"
multiple: "Moved multiple objects."
}
},
rotate: {
@@ -181,7 +181,11 @@ locale.en = {
new_tag: "New Tag",
edit_tags: "Edit tags",
okay: "Okay",
view_on_osm: "View on OSM"
view_on_osm: "View on OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -226,6 +230,7 @@ locale.en = {
untagged_point: "Untagged point which is not part of a line or area",
untagged_line: "Untagged line",
untagged_area: "Untagged area",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area",
deprecated_tags: "Deprecated tags: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.es = {
new_tag: "Nueva etiqueta", //"New Tag"
edit_tags: "Editar etiquetas", //"Edit tags",
okay: "OK",
view_on_osm: "Ver en OSM" //"View on OSM"
view_on_osm: "Ver en OSM", //"View on OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.es = {
untagged_point: "Punto sin etiquetar que no es parte de una línea ni de un área.", //"Untagged point which is not part of a line or area",
untagged_line: "Línea sin etiquetar", //"Untagged line",
untagged_area: "Área sin etiquetar", //"Untagged area",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "La etiqueta {tag} sugiere que esta línea debería ser una área, pero no lo es.", //"The tag {tag} suggests line should be area, but it is not an area",
deprecated_tags: "Etiquetas obsoletas: {tags}" //"Deprecated tags: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.fr = {
new_tag: "Nouveau tag",
edit_tags: "Editer les tags",
okay: "Okay",
view_on_osm: "Visualiser sur OSM"
view_on_osm: "Visualiser sur OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.fr = {
untagged_point: "Point sans aucun tag ne faisant partie ni d'une ligne, ni d'un polygone",
untagged_line: "Ligne sans aucun tag",
untagged_area: "Polygone sans aucun tag",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Ce tag {tag} suppose que cette ligne devrait être un polygone, or ce n'est pas le cas",
deprecated_tags: "Tags obsolètes : {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.it = {
new_tag: "Nuovo Tag",
edit_tags: "Modifica i tag",
okay: "Ok",
view_on_osm: "Mostra su OSM"
view_on_osm: "Mostra su OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.it = {
untagged_point: "Punto senza tag che non è parte di una linea o di un'area",
untagged_line: "Linea senza tag",
untagged_area: "Area senza tag",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Il tag {tag} fa pensare che la linea sia un'area, ma non rappresenta un'area",
deprecated_tags: "Tag deprecati: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.ja = {
new_tag: "新規タグ",
edit_tags: "タグ編集",
okay: "OK",
view_on_osm: "詳細情報確認"
view_on_osm: "詳細情報確認",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.ja = {
untagged_point: "ポイントにタグが付与されておらず、ラインやエリアの一部でもありません",
untagged_line: "ラインにタグが付与されていません",
untagged_area: "エリアにタグが付与されていません",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "ラインに {tag} タグが付与されています。エリアで描かれるべきです",
deprecated_tags: "タグの重複: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.lv = {
new_tag: "Jauns apzīmējums",
edit_tags: "Labot apzīmējumus",
okay: "Labi",
view_on_osm: "Apskatīt OSM"
view_on_osm: "Apskatīt OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.lv = {
untagged_point: "Neapzīmēts punkts",
untagged_line: "Neapzīmēta līnija",
untagged_area: "Neapzīmēts apgabals",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Apzīmējums {tag} parasti tiek lietots apgabaliem, bet objekts nav apgabals",
deprecated_tags: "Novecojuši apzīmējumi: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.nl = {
new_tag: "Nieuwe tag",
edit_tags: "Tags aanpassen",
okay: "OK",
view_on_osm: "Bekijk op OSM"
view_on_osm: "Bekijk op OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.nl = {
untagged_point: "Punt zonder tags, dat geen onderdeel is van een lijn of vlak",
untagged_line: "Lijn zonder tags",
untagged_area: "Vlak zonder tags",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "De tag {tag} suggereert dat de lijn een vlak is, maar het is geen vlak",
deprecated_tags: "Afgeschafte tags: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.pl = {
new_tag: "Nowy tag",
edit_tags: "Edytuj tagi",
okay: "Okej",
view_on_osm: "Zobacz w OSM"
view_on_osm: "Zobacz w OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.pl = {
untagged_point: "Nieopisany punkt, który nie jest częścią linii lub obszaru.",
untagged_line: "Nieopisana linia.",
untagged_area: "Nieopisany obszar.",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Tag {tag} sugeruje, że linia powinna być obszarem, ale nim nie jest.",
deprecated_tags: "Przestarzałe tagi: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.ru = {
new_tag: "Новый тег",
edit_tags: "Править теги",
okay: "Готово",
view_on_osm: "Посмотреть в OSM"
view_on_osm: "Посмотреть в OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.ru = {
untagged_point: "Точка без тегов и не в составе линии или контура",
untagged_line: "Линия без тегов",
untagged_area: "Контур без тегов",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Тег {tag} обычно ставится на замкнутые контуры, но это не контур",
deprecated_tags: "Теги устарели: {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.tr = {
new_tag: "Yeni Etiket",
edit_tags: "Etiketleri güncelle",
okay: "Tamam",
view_on_osm: "View on OSM"
view_on_osm: "View on OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.tr = {
untagged_point: "Herhangi bir çizgi ya da alana bağlantısı olmayan ve etiketlenmemiş bir nokta.",
untagged_line: "Etiketlenmemiş çizgi",
untagged_area: "Etiketlenmemiş alan",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "{tag} etiketi buranın alan olmasını tavsiye ediyor ama alan değil.",
deprecated_tags: "Kullanımdan kaldırılmış etiket : {tags}"
},
+6 -1
View File
@@ -171,7 +171,11 @@ locale.uk = {
new_tag: "Новий теґ",
edit_tags: "Редагувати теґи",
okay: "Готово",
view_on_osm: "Подивтись в ОСМ"
view_on_osm: "Подивтись в ОСМ",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -216,6 +220,7 @@ locale.uk = {
untagged_point: "Точка без теґів, що не є частиною лінію чи полігону",
untagged_line: "Лінія без теґів",
untagged_area: "Полігон без теґів",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Теґ {tag} зазвичай ставться на полігони, але об’єкт ним не є",
deprecated_tags: "Застарілі теґи: {tags}"
},
+7 -2
View File
@@ -112,7 +112,7 @@ locale.vi = {
vertex: "Di chuyển nốt trong lối.",
line: "Di chuyển đường kẻ.",
area: "Di chuyển vùng.",
multiple: "Di chuyển hơn một đối tượng"
multiple: "Di chuyển hơn một đối tượng."
}
},
reverse: {
@@ -172,7 +172,11 @@ locale.vi = {
new_tag: "Thẻ Mới",
edit_tags: "Sửa đổi các thẻ",
okay: "OK",
view_on_osm: "Xem tại OSM"
view_on_osm: "Xem tại OSM",
name: "Name",
editing: "Editing {type}",
additional: "Additional tags",
choose: "What are you adding?"
},
layerswitcher: {
@@ -217,6 +221,7 @@ locale.vi = {
untagged_point: "Địa điểm không có thẻ mà không trực thuộc đường kẻ hoặc vùng",
untagged_line: "Đường kẻ không có thẻ",
untagged_area: "Vùng không có thẻ",
many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
tag_suggests_area: "Thẻ {tag} có lẽ dành cho vùng nhưng được gắn vào đường kẻ",
deprecated_tags: "Thẻ bị phản đối: {tags}"
},
+2 -21
View File
@@ -13,7 +13,8 @@
"form": [
{
"key": "cuisine",
"type": "combo"
"type": "combo",
"indexed": true
},
{
"key": "internet_access",
@@ -645,26 +646,6 @@
"title": "Address"
}
]
},
{
"title": "Other",
"name": "other",
"match": {
"type": ["line", "area"],
"tags": {}
},
"icon": "square-stroked",
"form": []
},
{
"title": "Other",
"name": "other",
"match": {
"type": ["node"],
"tags": {}
},
"icon": "marker-stroked",
"form": []
}
],
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

+3 -3
View File
@@ -26,7 +26,6 @@
<script src='../js/lib/d3.trigger.js'></script>
<script src='../js/lib/d3.typeahead.js'></script>
<script src='../js/lib/d3.one.js'></script>
<script src='../js/lib/d3.tail.js'></script>
<script src='../js/lib/ohauth.js'></script>
<script src='../js/lib/jxon.js'></script>
<script src="../js/lib/rtree.js"></script>
@@ -78,6 +77,7 @@
<script src='../js/id/ui/source_switch.js'></script>
<script src='../js/id/ui/tageditor.js'></script>
<script src='../js/id/ui/taglist.js'></script>
<script src='../js/id/ui/tail.js'></script>
<script src='../js/id/ui/toggle.js'></script>
<script src='../js/id/actions.js'></script>
@@ -96,7 +96,7 @@
<script src='../js/id/actions/join.js'></script>
<script src='../js/id/actions/merge.js'></script>
<script src='../js/id/actions/move_node.js'></script>
<script src='../js/id/actions/move_way.js'></script>
<script src='../js/id/actions/move.js'></script>
<script src='../js/id/actions/rotate_way.js'></script>
<script src='../js/id/actions/noop.js'></script>
<script src='../js/id/actions/reverse.js'></script>
@@ -180,7 +180,7 @@
<script src="spec/actions/join.js"></script>
<script src='spec/actions/merge.js'></script>
<script src="spec/actions/move_node.js"></script>
<script src="spec/actions/move_way.js"></script>
<script src="spec/actions/move.js"></script>
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/reverse.js"></script>
<script src="spec/actions/split.js"></script>
+1 -1
View File
@@ -38,7 +38,7 @@
<script src="spec/actions/join.js"></script>
<script src='spec/actions/merge.js'></script>
<script src="spec/actions/move_node.js"></script>
<script src="spec/actions/move_way.js"></script>
<script src="spec/actions/move.js"></script>
<script src="spec/actions/noop.js"></script>
<script src="spec/actions/reverse.js"></script>
<script src="spec/actions/split.js"></script>
+59
View File
@@ -14,4 +14,63 @@ describe("iD.actions.DeleteRelation", function () {
graph = action(iD.Graph([a, b, parent]));
expect(graph.entity(parent.id).members).to.eql([{ id: b.id }]);
});
it("deletes member nodes not referenced by another parent", function() {
var node = iD.Node(),
relation = iD.Relation({members: [{id: node.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, relation]));
expect(graph.entity(node.id)).to.be.undefined;
});
it("does not delete member nodes referenced by another parent", function() {
var node = iD.Node(),
way = iD.Way({nodes: [node.id]}),
relation = iD.Relation({members: [{id: node.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, way, relation]));
expect(graph.entity(node.id)).not.to.be.undefined;
});
it("does not delete member nodes with interesting tags", function() {
var node = iD.Node({tags: {highway: 'traffic_signals'}}),
relation = iD.Relation({members: [{id: node.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, relation]));
expect(graph.entity(node.id)).not.to.be.undefined;
});
it("deletes member ways not referenced by another parent", function() {
var way = iD.Way(),
relation = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([way, relation]));
expect(graph.entity(way.id)).to.be.undefined;
});
it("does not delete member ways referenced by another parent", function() {
var way = iD.Way(),
relation1 = iD.Relation({members: [{id: way.id}]}),
relation2 = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation1.id),
graph = action(iD.Graph([way, relation1, relation2]));
expect(graph.entity(way.id)).not.to.be.undefined;
});
it("does not delete member ways with interesting tags", function() {
var way = iD.Node({tags: {highway: 'residential'}}),
relation = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([way, relation]));
expect(graph.entity(way.id)).not.to.be.undefined;
});
it("deletes nodes of deleted member ways", function() {
var node = iD.Node(),
way = iD.Way({nodes: [node.id]}),
relation = iD.Relation({members: [{id: way.id}]}),
action = iD.actions.DeleteRelation(relation.id),
graph = action(iD.Graph([node, way, relation]));
expect(graph.entity(node.id)).to.be.undefined;
});
});
+1 -1
View File
@@ -47,7 +47,7 @@ describe("iD.actions.DeleteWay", function() {
c = iD.Node(),
way = iD.Way({nodes: [a.id, b.id, c.id, a.id]}),
action = iD.actions.DeleteWay(way.id),
graph = iD.Graph([a, b, way]).update(action);
graph = iD.Graph([a, b, c, way]).update(action);
expect(graph.entity(a.id)).to.be.undefined;
expect(graph.entity(b.id)).to.be.undefined;
expect(graph.entity(c.id)).to.be.undefined;
+51
View File
@@ -0,0 +1,51 @@
describe("iD.actions.Move", function() {
it("moves all nodes in a way by the given amount", function() {
var node1 = iD.Node({loc: [0, 0]}),
node2 = iD.Node({loc: [5, 10]}),
way = iD.Way({nodes: [node1.id, node2.id]}),
delta = [2, 3],
projection = d3.geo.mercator(),
graph = iD.actions.Move([way.id], delta, projection)(iD.Graph([node1, node2, way])),
loc1 = graph.entity(node1.id).loc,
loc2 = graph.entity(node2.id).loc;
expect(loc1[0]).to.be.closeTo( 1.440, 0.001);
expect(loc1[1]).to.be.closeTo(-2.159, 0.001);
expect(loc2[0]).to.be.closeTo( 6.440, 0.001);
expect(loc2[1]).to.be.closeTo( 7.866, 0.001);
});
it("moves repeated nodes only once", function() {
var node = iD.Node({loc: [0, 0]}),
way = iD.Way({nodes: [node.id, node.id]}),
delta = [2, 3],
projection = d3.geo.mercator(),
graph = iD.actions.Move([way.id], delta, projection)(iD.Graph([node, way])),
loc = graph.entity(node.id).loc;
expect(loc[0]).to.be.closeTo( 1.440, 0.001);
expect(loc[1]).to.be.closeTo(-2.159, 0.001);
});
it("moves multiple ways", function() {
var node = iD.Node({loc: [0, 0]}),
way1 = iD.Way({nodes: [node.id]}),
way2 = iD.Way({nodes: [node.id]}),
delta = [2, 3],
projection = d3.geo.mercator(),
graph = iD.actions.Move([way1.id, way2.id], delta, projection)(iD.Graph([node, way1, way2])),
loc = graph.entity(node.id).loc;
expect(loc[0]).to.be.closeTo( 1.440, 0.001);
expect(loc[1]).to.be.closeTo(-2.159, 0.001);
});
it("moves leaf nodes of a relation", function() {
var node = iD.Node({loc: [0, 0]}),
way = iD.Way({nodes: [node.id]}),
relation = iD.Relation({members: [{id: way.id}]}),
delta = [2, 3],
projection = d3.geo.mercator(),
graph = iD.actions.Move([relation.id], delta, projection)(iD.Graph([node, way, relation])),
loc = graph.entity(node.id).loc;
expect(loc[0]).to.be.closeTo( 1.440, 0.001);
expect(loc[1]).to.be.closeTo(-2.159, 0.001);
});
});
-27
View File
@@ -1,27 +0,0 @@
describe("iD.actions.MoveWay", function() {
it("moves all nodes in a way by the given amount", function() {
var node1 = iD.Node({loc: [0, 0]}),
node2 = iD.Node({loc: [5, 10]}),
way = iD.Way({nodes: [node1.id, node2.id]}),
delta = [2, 3],
projection = d3.geo.mercator(),
graph = iD.actions.MoveWay(way.id, delta, projection)(iD.Graph([node1, node2, way])),
loc1 = graph.entity(node1.id).loc,
loc2 = graph.entity(node2.id).loc;
expect(loc1[0]).to.be.closeTo( 1.440, 0.001);
expect(loc1[1]).to.be.closeTo(-2.159, 0.001);
expect(loc2[0]).to.be.closeTo( 6.440, 0.001);
expect(loc2[1]).to.be.closeTo( 7.866, 0.001);
});
it("moves repeated nodes only once", function() {
var node = iD.Node({loc: [0, 0]}),
way = iD.Way({nodes: [node.id, node.id]}),
delta = [2, 3],
projection = d3.geo.mercator(),
graph = iD.actions.MoveWay(way.id, delta, projection)(iD.Graph([node, way])),
loc = graph.entity(node.id).loc;
expect(loc[0]).to.be.closeTo( 1.440, 0.001);
expect(loc[1]).to.be.closeTo(-2.159, 0.001);
});
});
+4 -4
View File
@@ -25,7 +25,7 @@ describe("iD.taginfo", function() {
server.respond();
expect(query(server.requests[0].url)).to.eql(
{query: "amen", page: "1", rp: "6", sortname: "count_all", sortorder: "desc"});
{query: "ame", page: "1", rp: "10", sortname: "count_all", sortorder: "desc"});
expect(callback).to.have.been.calledWith(null, [{"value":"amenity"}]);
});
@@ -49,11 +49,11 @@ describe("iD.taginfo", function() {
server.respondWith("GET", new RegExp("http://taginfo.openstreetmap.org/api/4/keys/all"),
[200, { "Content-Type": "application/json" },
'{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},\
{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes_fraction":1.0}]}']);
'{"data":[{"count_all":5190337,"count_nodes":500000,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},\
{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes":100}]}']);
server.respond();
expect(callback).to.have.been.calledWith(null, [{"value":"amenity"},{"value":"amenityother"}]);
expect(callback).to.have.been.calledWith(null, [{"value":"amenity"}]);
});
});
+1 -1
View File
@@ -4,7 +4,7 @@ describe("iD.ui.Inspector", function () {
entity, graph, context;
function render() {
inspector = iD.ui.Inspector().context(context);
inspector = iD.ui.Inspector(context);
element = d3.select('body')
.append('div')
.attr('id', 'inspector-wrap')