Merge remote-tracking branch 'systemed/master' into presets

Conflicts:
	css/app.css
	index.html
	js/id/connection.js
	js/id/modes/select.js
	js/id/ui/inspector.js
	js/id/util.js
	test/spec/modes/add_point.js
This commit is contained in:
Ansis Brammanis
2013-02-14 11:38:18 -05:00
180 changed files with 10222 additions and 4680 deletions
+203
View File
@@ -0,0 +1,203 @@
## d3
[d3](http://d3js.org/) is the primary library used by iD. It is used for
rendering the map data as well as many sorts of general DOM manipulation tasks
for which jQuery would often be used.
Notable features of d3 that are used by iD include
[d3.xhr](https://github.com/mbostock/d3/wiki/Requests#wiki-d3_xhr), which is
used to make the API requests to download data from openstreetmap.org and save
changes;
[d3.dispatch](https://github.com/mbostock/d3/wiki/Internals#wiki-d3_dispatch),
which provides a callback-based [Observer
pattern](http://en.wikipedia.org/wiki/Observer_pattern) between different
parts of iD;
[d3.geo.path](https://github.com/mbostock/d3/wiki/Geo-Paths#wiki-path), which
generates SVG paths for lines and areas; and
[d3.behavior.zoom](https://github.com/mbostock/d3/wiki/Zoom-Behavior#wiki-zoom),
which implements map panning and zooming.
## Core
The iD *core* implements the OSM data types, a graph of OSM object's
relationships to each other, and an undo/redo history for changes made during
editing. It aims to be generic enough to be used by other JavaScript-based
tools for OpenStreetMap.
Briefly, the OSM data model includes three basic data types: nodes, ways, and
relations. A _node_ is a point type, having a single geographic coordinate. A
_way_ is an ordered list of nodes. And a _relation_ groups together nodes,
ways, and other relations to provide free-form higher-level structures. Each
of these three types has _tags_: an associative array of key-value pairs which
describe the object.
In iD, these three types are implemented by `iD.Node`, `iD.Way` and
`iD.Relation`. These three classes inherit from a common base, `iD.Entity`
(the only use of classical inheritance in iD). Generically, we refer to a
node, way or relation as an _entity_.
Every entity has an _ID_ either assigned by the OSM database, or, for an
entity that is newly created, constructed as a proxy consisting of a negative
numeral. IDs from the OSM database as treated as opaque strings; no
[assumptions](http://lists.openstreetmap.org/pipermail/dev/2013-February/026495.html)
are made of them other than that they can be compared for identity and do not
begin with a minus sign (and thus will not conflict with proxy IDs). In fact,
in the OSM database the three types of entities have separate ID spaces; a
node can have the same ID as a way, for instance. Because it is useful to
store heterogeneous entities in the same datastructure, iD ensures that every
entity has a fully-unique ID by prefixing each OSM ID with the first letter of
the entity type. For example, a way with OSM ID 123456 is represented as
'w123456' within iD.
iD entities are *immutable*: once constructed, an `Entity` object cannot
change. Tags cannot be updated; nodes cannot be added or removed from ways,
and so on. Immutability makes it easier to reason about the behavior of an
entity: if your code has a reference to one, it is safe to store it and use it
later, knowing that it cannot have been changed outside of your control. It
also makes it possible to implement the entity graph (described below) as an
efficient [persistent data
structure](http://en.wikipedia.org/wiki/Persistent_data_structure). But
obviously, iD is an editor, and must allow entities to change somehow. The
solution is that all edits produce new copies of anything that changes. At the
entity level, this takes the form of methods such as `iD.Node#move`, which
returns a new node object that has the same ID and tags as the original, but a
different coordinate. More generically, `iD.Entity#update` returns a new
entity of the same type and ID as the original but with specified properties
such as `nodes`, `tags`, or `members` replaced.
Entities are related to one another: ways have many nodes and relations have
many members. In order to render a map of a certain area, iD needs a
datastructure to hold all the entities in that area and traverse these
relationships. `iD.Graph` provides this functionality. The core of a graph is
a map between IDs and the associated entities; given an ID, the graph can give
you the entity. Like entities, a graph is immutable: adding, replacing, or
removing an entity produces a new graph, and the original is unchanged.
Because entities are immutable, the original and new graphs can share
references to entities that have not changed, keeping memory use to a minimum.
If you are familiar with how git works internally, this persistent data
structure approach is very similar.
The final component of the core is comprised of `iD.History` and
`iD.Difference`, which track the changes made in an editing session and
provide undo/redo capabilities. Here, the immutable nature of the core types
really pays off: the history is a simple stack of graphs, each representing
the state of the data at a particular point in editing. The graph at the top
of the stack is the current state, off which all rendering is based. To undo
the last change, this graph is popped off the stack, and the map is
re-rendered based on the new top of the stack. Contrast this to a mutable
graph as used in JOSM and Potlatch: every command that changes the graph must
implement an equal and opposite undo command that restores the graph to the
previous state.
## Actions
In iD, an _action_ is a function that accepts a graph as input and returns a
modified graph as output. Actions typically need other inputs as well; for
example, `iD.actions.DeleteNode` also requires the ID of a node to delete. The
additional input is passed to the action's constructor:
``` var action = iD.actions.DeleteNode('n123456'); // construct the action var
newGraph = action(oldGraph); // apply the action ```
iD provides actions for all the typical things an editor needs to do: add a
new entity, split a way in two, connect the vertices of two ways together, and
so on. In addition to performing the basic work needed to accomplish these
things, an action typically contains a significant amount of logic for keeping
the relationships between entities logical and consistent. For example, an
action as apparently simple as `DeleteNode`, in addition to removing the node
from the graph, needs to do two other things: remove the node from any ways in
which it is a member (which in turn requires deleting parent ways that are
left with just a single node), and removing it from any relations of which it
is a member.
As you can imagine, implementing all these details requires an expert
knowledge of the OpenStreetMap data model. It is our hope that JavaScript
based tools for OpenStreetMap can reuse the implementations provided by iD in
other contexts, significantly reducing the work necessary to create a robust
tool.
## Modes
With _modes_, we shift gears from abstract data types and algorithms to the
parts of the architecture that implement the user interface for iD. Modes are
manifested in the interface by the four buttons at the top left:
![Mode buttons](img/modes.png)
The modality of existing OSM editors runs the gamut from Potlatch 2, which is
almost entirely modeless, to JOSM, which sports half a dozen modes out of the
box and has many more provided by plugins. iD seeks a middle ground: too few
modes can leave new users unsure where to start, while too many can be
overwhelming.
iD's user-facing modes consist of a base "Browse" mode, in which you can move
around the map and select and edit entities, and three geometrically-oriented
drawing modes: Point, Line, and Area. In the code, these are broken down a
little bit more. There are separate modes for when an entity is selected
(`iD.modes.Select`) versus when nothing is selected (`iD.modes.Browse`), and
each of the geometric modes is split into one mode for starting to draw an
object and one mode for continuing an existing object (with the exception of
`iD.modes.AddPoint`, which is a single-step operation for obvious reasons).
The code interface for each mode consists of a pair of methods: `enter` and
`exit`. In the `enter` method, a mode sets up all the behavior that should be
present when that mode is active. This typically means binding callbacks to
DOM events that will be triggered on map elements, installing keybindings, and
showing certain parts of the interface like the inspector in `Select` mode.
The `exit` mode does the opposite, removing the behavior installed by the
`enter` method. Together the two methods ensure that modes are self-contained
and exclusive: each mode knows exactly the behavior that is specific to that
mode, and exactly one mode's behavior is active at any time.
## Behavior
Certain behaviors are common to more than one mode. For example, iD indicates
interactive map elements by drawing a halo around them when you hover over
them, and this behavior is common to both the browse and draw modes. Instead
of duplicating the code to implement this behavior in all these modes, we
extract it to `iD.behavior.Hover`.
_Behaviors_ take their inspiration from [d3's
behaviors](https://github.com/mbostock/d3/wiki/Behaviors). Like d3's `zoom`
and `drag`, each iD behavior is a function that takes as input a d3 selection
(assumed to consist of a single element) and installs the DOM event bindings
necessary to implement the behavior. The `Hover` behavior, for example,
installs bindings for the `mouseover` and `mouseout` events that add and
remove a `hover` class from map elements.
Because certain behaviors are appropriate to some but not all modes, we need
the ability to remove a behavior when entering a mode where it is not
appropriate. (This is functionality [not yet
provided](https://github.com/mbostock/d3/issues/894) by d3's own behaviors.)
Each behavior implements an `off` function that "uninstalls" the behavior.
This is very similar to the `exit` method of a mode, and in fact many modes do
little else but uninstall behaviors in their `exit` methods.
## Operations
_Operations_ wrap actions, providing their user-interface: tooltips, key
bindings, and the logic that determines whether an action can be validly
performed given the current map state and selection. Each operation is
constructed with the list of IDs which are currently selected and a `context`
object which provides access to the history and other important parts of iD's
internal state. After being constructed, an operation can be queried as to
whether or not it should be made available (i.e., show up in the context menu)
and if so, if it should be enabled.
![Operations menu](img/operations.png)
We make a distinction between availability and enabled state for the sake of
learnability: most operations are available so long as an entity of the
appropriate type is selected. Even if it remains disabled for other reasons
(e.g. because you can't split a way on its start or end vertex), a new user
can still learn that "this is something I can do to this type of thing", and a
tooltip can provide an explanation of what that operation does and the
conditions under which it is enabled.
To execute an operation, call it as a function, with no arguments. The typical
operation will perform the appropriate action, creating a new undo state in
the history, and then enter the appropriate mode. For example,
`iD.operations.Split` performs `iD.actions.Split`, then enters
`iD.modes.Select` with the resulting ways selected.
## Rendering and other UI
+101
View File
@@ -0,0 +1,101 @@
# Contributing to iD
Thinking of contributing to iD? High five! Here are some basics for our habits
so that you can write code that fits in perfectly.
## Reporting Issues
We'd love to hear what you think about iD, about any specific problems or
concerns you have. Here's a quick list of things to consider:
Please [search for your issue before filing it: many bugs and improvements have already been reported](https://github.com/systemed/iD/issues/search?q=)
To report a bug:
* Write specifically what browser (type and version, like Firefox 22), OS, and browser extensions you have installed
* Write steps to replicate the error: when did it happen? What did you expect to happen? What happened instead?
* Please keep bug reports professional and straightforward: trust us, we share your dismay at software breaking.
* If you can, [enable web developer extensions](http://macwright.org/enable-web-developer-extensions/) and report the
Javascript error message.
When in doubt, be over-descriptive of the bug and how you discovered it.
To request a feature:
* If the feature is available in some other software (like Potlatch), link to that software and the implementation.
We care about prior art.
* Understand that iD is meant to be a simple editor and doesn't aim to be
as complete or complicated as JOSM or similar.
## Javascript
We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with
only one difference:
**4 space soft tabs always for Javascript, not 2.**
No aligned `=`, no aligned arguments, spaces are either indents or the 1
space between expressions. No hard tabs, ever.
Javascript code should pass through [JSHint](http://www.jshint.com/) with no
warnings.
## HTML
There isn't much HTML in iD, but what there is is similar to JS: 4 spaces
always, indented by the level of the tree:
```html
<div>
<div></div>
</div>
```
## CSS
Just like HTML and Javascript, 4 space soft tabs always.
```css
.radial-menu-tooltip {
background: rgba(255, 255, 255, 0.8);
}
```
We write vanilla CSS with no preprocessing step. Since iD targets modern browsers,
feel free to use newer features wisely.
## Tests
Test your code and make sure it passes. Our testing harness requires [node.js](http://nodejs.org/)
and a few modules:
1. [Install node.js](http://nodejs.org/) - 'Install' will download a package for your OS
2. Go to the directory where you have checked out `iD`
3. Run `npm install`
4. Run `npm test` to see whether your tests pass or fail.
## Licensing
iD is under the [WTFPL](http://www.wtfpl.net/). Some of the libraries it uses
are under different licenses. If you're contributing to iD, you're contributing
WTFPL code.
## Submitting Changes
Let's say that you've thought of a great improvement to iD - a change that
turns everything red (please do not do this, we like colors other than red).
In your local copy, make a branch for this change:
git checkout -b make-red
Make your changes to source files. By source files we mean the files in `js/`.
the `iD.js` and `iD.min.js` files in this project are autogenerated - don't edit
them.
So let's say you've changed `js/ui/confirm.js`.
1. Run `jshint js/id` to make sure your code is clean
2. Run tests with `npm test`
3. Commit your changes with an informative commit message
4. [Submit a pull request](https://help.github.com/articles/using-pull-requests) to the `systemed/iD` project.
+6 -4
View File
@@ -30,6 +30,9 @@ all: \
js/id/connection.js \
js/id/oauth.js \
js/id/services/*.js \
data/data.js \
data/imagery.js \
data/deprecated.js \
js/id/util.js \
js/id/geo.js \
js/id/geo/*.js \
@@ -41,8 +44,7 @@ all: \
js/id/modes/*.js \
js/id/operations.js \
js/id/operations/*.js \
js/id/controller.js \
js/id/graph/*.js \
js/id/core/*.js \
js/id/renderer/*.js \
js/id/svg.js \
js/id/svg/*.js \
@@ -50,8 +52,8 @@ all: \
js/id/ui/*.js \
js/id/validate.js \
js/id/end.js \
locale/locale.js \
locale/en.js
js/lib/locale.js \
locale/*.js
iD.js: Makefile
@rm -f $@
+10 -11
View File
@@ -2,31 +2,30 @@
[![Build Status](https://secure.travis-ci.org/systemed/iD.png)](https://travis-ci.org/systemed/iD)
[![](http://ideditor.com/img/editor.png)](http://geowiki.com/iD/)
[Try the online demo of the most recent code.](http://geowiki.com/iD/) and
[open issues for bugs and ideas!](https://github.com/systemed/iD/issues)
[![](http://ideditor.com/img/editor.png)](http://ideditor.com/)
## Basics
* iD is a JavaScript [OpenStreetMap](http://www.openstreetmap.org/) editor.
* It's intentionally simple. It lets you do the most basic tasks while
not breaking other people's data.
* We support modern browsers. Data is rendered with [d3](http://d3js.org/).
* It supports modern browsers. Data is rendered with [d3](http://d3js.org/).
## Participate!
* [Read NOTES.md, our ongoing dev journal](https://github.com/systemed/iD/blob/master/NOTES.md)
* Fork this project. We eagerly accept pull requests.
* See [open issues in the issue tracker if you're looking for something to do](https://github.com/systemed/iD/issues?state=open)
* [Try out the latest stable release](http://geowiki.com/iD/)
* [Read up on Contributing and the code style of iD](CONTRIBUTING.md)
* See [open issues in the issue tracker](https://github.com/systemed/iD/issues?state=open) if you're looking for something to do
To run the code locally, just fork this project and run it from a local webserver.
With a Mac, you can enable Web Sharing and drop this in your website directory.
## Installation
If you have Python handy, just `cd` into `iD` and run
To run the current development version, fork this project and serve it locally.
If you have Python handy, just `cd` into the project root directory and run
python -m SimpleHTTPServer
Or, with a Mac, you can enable Web Sharing and clone iD into your website directory.
Come on in, the water's lovely. More help? Ping RichardF, tmcw, or jfire on IRC
(`irc.oftc.net`, in `#osm-dev` or `#osm`), on the OSM mailing lists or at
richard@systemeD.net.
+10 -11
View File
@@ -61,7 +61,7 @@
<script src='js/id/ui/commit.js'></script>
<script src='js/id/ui/success.js'></script>
<script src='js/id/ui/loading.js'></script>
<script src='js/id/ui/userpanel.js'></script>
<script src='js/id/ui/account.js'></script>
<script src='js/id/ui/layerswitcher.js'></script>
<script src='js/id/ui/contributors.js'></script>
<script src='js/id/ui/geocoder.js'></script>
@@ -79,18 +79,17 @@
<script src='js/id/actions/change_tags.js'></script>
<script src='js/id/actions/delete_node.js'></script>
<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/circularize.js'></script>
<script src='js/id/actions/noop.js'></script>
<script src='js/id/actions/reverse_way.js'></script>
<script src='js/id/actions/split_way.js'></script>
<script src='js/id/actions/unjoin_node.js'></script>
<script src='js/id/actions/reverse.js'></script>
<script src='js/id/actions/split.js'></script>
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/add_way.js'></script>
<script src='js/id/behavior/drag.js'></script>
<script src='js/id/behavior/drag_midpoint.js'></script>
<script src='js/id/behavior/drag_node.js'></script>
<script src='js/id/behavior/draw.js'></script>
<script src='js/id/behavior/draw_way.js'></script>
@@ -115,12 +114,12 @@
<script src='js/id/operations/split.js'></script>
<script src='js/id/operations/unjoin.js'></script>
<script src='js/id/graph/entity.js'></script>
<script src='js/id/graph/graph.js'></script>
<script src='js/id/graph/history.js'></script>
<script src='js/id/graph/node.js'></script>
<script src='js/id/graph/relation.js'></script>
<script src='js/id/graph/way.js'></script>
<script src='js/id/core/entity.js'></script>
<script src='js/id/core/graph.js'></script>
<script src='js/id/core/history.js'></script>
<script src='js/id/core/node.js'></script>
<script src='js/id/core/relation.js'></script>
<script src='js/id/core/way.js'></script>
<script src='js/id/controller.js'></script>
<script src='js/id/validate.js'></script>
+67 -47
View File
@@ -19,10 +19,11 @@ body {
}
.limiter {
position: relative;
max-width: 1200px;
}
div, textarea, input, span, ul, li, ol, a, button {
div, textarea, input, form, span, ul, li, ol, a, button {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
@@ -93,7 +94,6 @@ a:hover {
color:#597be7;
}
textarea,
input[type=text],
input[type=url],
@@ -124,7 +124,7 @@ input[type=text]:focus {
}
input[type=text] {
padding:4px 10px;
padding:5px 10px;
height:30px;
resize: none;
}
@@ -185,10 +185,12 @@ ul li { list-style: none;}
ul.toggle-list li a {
font-weight: bold;
color: #333;
padding: 10px;
border-top: 1px solid white;
padding: 5px 10px;
display:block;
border-top: 1px solid rgba(0, 0, 0, .5);
border-top: 1px solid #ccc;
white-space:nowrap;
text-overflow:ellipsis;
overflow:hidden;
}
ul.toggle-list li a:hover { background-color: #ececec;}
@@ -225,7 +227,7 @@ ul.link-list li:last-child {
.fillD {
background:rgba(0,0,0,.8);
color: #a9a9a9;
color: #6C6C6C;
}
@@ -268,16 +270,17 @@ button:hover {
background-color: #ececec;
}
button.col3:hover {
background: #bde5aa;
}
button.active {
cursor:url(../img/cursor-pointing.png) 6 1, auto;
}
button.active:not([disabled]) {
background: #6bc641;
button.disabled {
background: #6c6c6c;
cursor: auto;
}
button.active:not([disabled]):not(.disabled) {
background: #7092ff;
}
button.minor {
@@ -322,7 +325,7 @@ button.centered {
border-radius:0 4px 4px 0;
}
button.Browse .label { display: none;}
button.browse .label { display: none;}
button.action {
background: #7092ff;
@@ -349,8 +352,7 @@ button.save .count {
button.save.has-count .count {
display: block;
position: absolute;
top: 0;
bottom: 0;
top: 5px;
background: rgba(255, 255, 255, .5);
color: #333;
padding: 10px;
@@ -453,8 +455,8 @@ button[disabled] .icon.browse { background-position: 0px -40px;}
button[disabled] .icon.add-point { background-position: -20px -40px;}
button[disabled] .icon.add-line { background-position: -40px -40px;}
button[disabled] .icon.add-area { background-position: -60px -40px;}
button[disabled] .icon.undo { background-position: -80px -40px;}
button[disabled] .icon.redo { background-position: -100px -40px;}
button.disabled .icon.undo { background-position: -80px -40px;}
button.disabled .icon.redo { background-position: -100px -40px;}
button[disabled] .apply.icon { background-position: -120px -40px;}
button[disabled] .save.icon { background-position: -140px -40px;}
button[disabled] .close.icon { background-position: -160px -40px;}
@@ -479,7 +481,7 @@ button[disabled] .icon.nearby { background-position: -340px -40px;}
.icon-operation-circularize { background-position: -20px -140px;}
.icon-operation-straighten { background-position: -40px -140px;}
.icon-operation-split { background-position: -60px -140px;}
.icon-operation-unjoin { background-position: -80px -140px;}
.icon-operation-disconnect { background-position: -80px -140px;}
.icon-operation-reverse { background-position: -100px -140px;}
.icon-operation-move { background-position: -120px -140px;}
.icon-operation-merge { background-position: -140px -140px;}
@@ -643,14 +645,15 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;}
}
.map-control > button.active:hover {
background: #6bc641;
background: #7092ff;
}
.map-overlay {
width: 150px;
position:absolute;
left:40px;
top:0;
right: 75%;
max-width: 260px;
min-width: 210px;
position: fixed;
left: 40px;
display: block;
border-radius: 4px;
}
@@ -685,10 +688,8 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;}
font-size:10px;
padding:0 5px 3px 5px;
background: white;
border:0;
text-transform: uppercase;
font-weight: bold;
}
.layerswitcher-control .adjustments button:hover {
@@ -725,9 +726,9 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;}
.layerswitcher-control .nudge {
text-indent: -9999px;
overflow: hidden;
width:20px;
width:16.6666%;
border-radius: 0;
margin-right:1px;
border-right: 1px solid rgba(0, 0, 0, .5);
position: relative;
}
@@ -770,7 +771,7 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;}
}
.layerswitcher-control .reset {
width: 45px;
width: 33.3333%;
border-radius: 0 4px 4px 0;
}
@@ -804,8 +805,8 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;}
.layerswitcher-control li:hover .select-box,
.layerswitcher-control li.selected .select-box {
border: 2px solid #6bc641;
background: rgba(107, 198, 65, .5);
border: 2px solid #7092ff;
background: rgba(89, 123, 231, .5);
opacity: .5;
}
.layerswitcher-control li.selected:hover .select-box,
@@ -822,14 +823,29 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;}
/* Geocoder */
.geocode-control {
.geocode-control, .geocode-control form {
top:150px;
}
.geocode-control form {
padding: 4px;
}
.geocode-control input {
width: 140px;
border: 1px solid #ccc;
margin: 4px;
width: 100%;
}
.geocode-control div.content {
z-index: 100;
top: 190px;
max-height: 300px;
overflow-y: auto;
}
.geocode-control div.content span {
display: inline-block;
border-bottom: 1px solid #333;
padding: 5px 10px;
}
/* Geolocator */
@@ -876,6 +892,10 @@ img.tile {
-o-transform-origin:0 0;
}
#surface {
position: static;
}
#tile-g {
opacity: 0.5;
}
@@ -904,17 +924,17 @@ img.tile {
color:#fff;
}
#user-list a:not(:last-child):after {
.user-list a:not(:last-child):after {
content: ', ';
}
/* Account Information */
.user-container {
.account {
float: left;
}
.user-container .logout {
.account .logout {
margin-left:10px;
border-left: 1px solid white;
padding-left: 10px;
@@ -1054,7 +1074,6 @@ div.typeahead a:first-child {
.modal-splash {
width: 33.3333%;
left: 33.3333%;
}
.logo {
@@ -1120,11 +1139,6 @@ div.typeahead a:first-child {
padding: 20px;
}
.modal-section .buttons {
padding-top: 10px;
width: 100%;
}
.modal-section img.wiki-image {
max-width: 100%;
max-height: 300px;
@@ -1188,7 +1202,7 @@ a.success-action {
}
.notice .zoom-to:hover {
background: #bde5aa;
background: #d8e1ff;
}
.notice .zoom-to .icon {
@@ -1307,7 +1321,6 @@ a.success-action {
color: #222;
font-size: 10px;
padding: 0px 7px;
text-transform: uppercase;
font-weight: bold;
display: inline-block;
border-radius: 2px;
@@ -1383,6 +1396,13 @@ a.success-action {
border-radius: 4px;
}
.lasso-box {
fill-opacity:0.2;
fill: #bde5aa;
stroke: #000;
stroke-width: 1;
}
/* Media Queries
------------------------------------------------------- */
@@ -1428,7 +1448,7 @@ a.success-action {
width: 100%;
}
.combobox {
div.combobox {
width:155px;
z-index: 9999;
display: none;
+153 -117
View File
@@ -1,3 +1,26 @@
/* tiles */
img.tile {
position:absolute;
transform-origin:0 0;
-ms-transform-origin:0 0;
-webkit-transform-origin:0 0;
-moz-transform-origin:0 0;
-o-transform-origin:0 0;
-webkit-user-select: none;
-webkit-user-drag: none;
-moz-user-drag: none;
opacity: 0;
-webkit-transition: opacity 200ms linear;
transition: opacity 200ms linear;
-moz-transition: opacity 200ms linear;
}
img.tile-loaded {
opacity: 1;
}
/* base styles */
path {
fill: none;
@@ -9,6 +32,10 @@ g.point circle {
fill:#fff;
}
g.point image {
pointer-events: none;
}
g.point .shadow {
fill: none;
pointer-events: all;
@@ -17,21 +44,27 @@ g.point .shadow {
-moz-transition: fill 100ms linear;
}
.behavior-hover g.point.hover:not(.selected) .shadow {
fill: #E96666;
fill-opacity: 0.3;
fill: #f6634f;
fill-opacity: 0.5;
}
g.point.selected .shadow {
fill: #E96666;
fill: #f6634f;
fill-opacity: 0.7;
}
g.point.active, g.point.active * {
pointer-events: none;
}
/* vertices */
g.vertex .fill {
fill:white;
}
g.vertex .stroke {
stroke:#333;
stroke:black;
stroke-opacity: .5;
stroke-width:2;
fill:white;
}
@@ -101,28 +134,40 @@ g.vertex.shared .fill {
g.vertex .shadow {
fill: none;
pointer-events: all;
stroke-width: 10;
stroke-width: 20;
-webkit-transition: -webkit-transform 100ms linear;
transition: transform 100ms linear;
-moz-transition: fill 100ms linear;
}
.behavior-hover g.vertex.hover:not(.selected) .shadow {
fill: #E96666;
fill: #f6634f;
fill-opacity: 0.3;
}
}
g.vertex.selected .shadow {
fill: #E96666;
fill-opacity: 0.7;
fill: #f6634f;
fill-opacity: 0.5;
}
/* midpoints */
.mode-draw-area g.midpoint,
.mode-draw-line g.midpoint,
.mode-add-area g.midpoint,
.mode-add-line g.midpoint,
.mode-add-point g.midpoint,
.behavior-drag-node g.midpoint {
display: none;
}
g.midpoint .fill {
fill:#aaa;
fill:#ddd;
stroke:black;
stroke-opacity: .5;
opacity: .5;
}
.behavior-hover g.midpoint .fill.hover:not(.selected) {
fill:#fff;
stroke:#000;
fill:white;
opacity: .75;
}
g.midpoint .shadow {
@@ -134,7 +179,7 @@ g.midpoint .shadow {
-moz-transition: fill 100ms linear;
}
.behavior-hover g.midpoint .shadow.hover:not(.selected) {
fill:#E96666;
fill:#f6634f;
fill-opacity: 0.3;
}
@@ -146,13 +191,8 @@ path.line {
}
path.stroke {
stroke: #222;
stroke-width: 2;
}
path.stroke,
path.casing {
shape-rendering: optimizeSpeed;
stroke: black;
stroke-width: 4;
}
path.shadow {
@@ -162,105 +202,82 @@ path.shadow {
}
.behavior-hover path.shadow.hover:not(.selected) {
stroke: #E96666;
stroke: #f6634f;
stroke-opacity: 0.3;
}
path.shadow.selected {
stroke: #E96666;
stroke: #f6634f;
stroke-opacity: 0.7;
}
path.area.stroke,
path.multipolygon {
path.line.member-type-multipolygon.stroke {
stroke-width:2;
stroke:#fff;
}
path.area.fill,
path.multipolygon {
fill:#fff;
fill-opacity:0.3;
}
path.multipolygon {
fill-rule: evenodd;
}
path.area.fill.member-type-multipolygon {
fill: none;
}
path.area.stroke.selected {
path.area.stroke.selected,
path.line.member-type-multipolygon.stroke.selected {
stroke-width:4 !important;
}
path.area.stroke.tag-natural,
path.multipolygon.tag-natural {
stroke: #ADD6A5;
path.area.stroke {
stroke:#fff;
}
path.area.fill {
fill:#fff;
fill-opacity:0.3;
fill-rule: evenodd;
}
path.stroke.tag-natural {
stroke: #b6e199;
stroke-width:1;
}
path.area.fill.tag-natural,
path.multipolygon.tag-natural {
fill: #ADD6A5;
path.fill.tag-natural {
fill: #b6e199;
}
path.area.stroke.tag-natural-water,
path.multipolygon.tag-natural-water {
stroke: #6382FF;
path.stroke.tag-natural-water {
stroke: #77d3de;
}
path.area.fill.tag-natural-water,
path.multipolygon.tag-natural-water {
fill: #ADBEFF;
path.fill.tag-natural-water {
fill: #77d3de;
}
path.area.stroke.tag-building,
path.multipolygon.tag-building {
stroke: #9E176A;
path.stroke.tag-building {
stroke: #e06e5f;
stroke-width: 1;
}
path.area.fill.tag-building,
path.multipolygon.tag-building {
fill: #ff6ec7;
path.fill.tag-building {
fill: #e06e5f;
}
path.area.stroke.tag-landuse,
path.area.stroke.tag-natural-wood,
path.area.stroke.tag-natural-tree,
path.area.stroke.tag-natural-grassland,
path.area.stroke.tag-leisure-park,
path.multipolygon.tag-landuse,
path.multipolygon.tag-natural-wood,
path.multipolygon.tag-natural-tree,
path.multipolygon.tag-natural-grassland,
path.multipolygon.tag-leisure-park {
stroke: #006B34;
path.stroke.tag-landuse,
path.stroke.tag-natural-wood,
path.stroke.tag-natural-tree,
path.stroke.tag-natural-grassland,
path.stroke.tag-leisure-park {
stroke: #8cd05f;
stroke-width: 1;
}
path.area.fill.tag-landuse,
path.area.fill.tag-natural-wood,
path.area.fill.tag-natural-tree,
path.area.fill.tag-natural-grassland,
path.area.fill.tag-leisure-park,
path.multipolygon.tag-landuse,
path.multipolygon.tag-natural-wood,
path.multipolygon.tag-natural-tree,
path.multipolygon.tag-natural-grassland,
path.multipolygon.tag-leisure-park {
fill: #189E59;
path.fill.tag-landuse,
path.fill.tag-natural-wood,
path.fill.tag-natural-tree,
path.fill.tag-natural-grassland,
path.fill.tag-leisure-park {
fill: #8cd05f;
fill-opacity: 0.2;
}
path.area.stroke.tag-amenity-parking,
path.multipolygon.tag-amenity-parking {
stroke: #beb267;
path.stroke.tag-amenity-parking {
stroke: #aaa;
stroke-width: 1;
}
path.area.fill.tag-amenity-parking,
path.multipolygon.tag-amenity-parking {
fill: #edecc0;
path.fill.tag-amenity-parking {
fill: #aaa;
}
path.multipolygon.tag-boundary {
path.fill.tag-boundary {
fill: none;
}
@@ -291,56 +308,57 @@ svg[data-zoom="16"] path.stroke.tag-highway {
path.stroke.tag-highway-motorway,
path.stroke.tag-highway-motorway_link,
path.stroke.tag-construction-motorway {
stroke:#809bc0;
stroke:#58a9ed;
}
path.casing.tag-highway-motorway,
path.casing.tag-highway-motorway_link,
path.casing.tag-construction-motorway {
stroke:#506077;
stroke:#2c5476;
}
path.stroke.tag-highway-trunk,
path.stroke.tag-highway-trunk_link,
path.stroke.tag-construction-trunk {
stroke:#97d397;
stroke:#8cd05f;
}
path.casing.tag-highway-trunk,
path.casing.tag-highway-trunk_link,
path.casing.tag-construction-trunk {
stroke:#477147;
stroke:#46682f;
}
path.stroke.tag-highway-primary,
path.stroke.tag-highway-primary_link,
path.stroke.tag-construction-primary {
stroke:#ec989a;
stroke:#e06d5f;
}
path.casing.tag-highway-primary,
path.casing.tag-highway-primary_link,
path.casing.tag-construction-primary {
stroke:#8d4346;
stroke:#70372f;
}
path.stroke.tag-highway-secondary,
path.stroke.tag-highway-secondary_link,
path.stroke.tag-construction-secondary {
stroke:#fecc8b;
stroke:#eab056;
}
path.casing.tag-highway-secondary,
path.casing.tag-highway-secondary_link,
path.casing.tag-construction-secondary {
stroke:#a37b48;
stroke:#75582b;
}
path.stroke.tag-highway-tertiary,
path.stroke.tag-highway-tertiary_link,
path.stroke.tag-construction-tertiary {
stroke:#ffffb3;
stroke:#ffff7e;
}
path.casing.tag-highway-tertiary,
path.casing.tag-highway-tertiary_link,
path.casing.tag-construction-tertiary {
stroke:#bbb;
stroke:#7f7f3f;
}
path.stroke.tag-highway-unclassified,
@@ -377,7 +395,7 @@ path.stroke.tag-highway-pedestrian {
shapeRendering: auto;
}
path.casing.tag-highway-pedestrian {
stroke:#84C382;
stroke:#8cd05f;
stroke-width:6 !important;
}
@@ -450,17 +468,17 @@ svg[data-zoom="16"] path.casing.tag-highway-bridleway {
}
path.stroke.tag-highway-footway {
stroke: #996600;
stroke: #ae8681;
}
path.stroke.tag-highway-cycleway {
stroke: #69f;
stroke: #58a9ed;
}
path.stroke.tag-highway-bridleway {
stroke: green;
stroke: #e06d5f;
}
path.stroke.tag-highway-steps {
stroke: #ff6257;
stroke: #81d25c;
stroke-width: 4;
stroke-linecap: butt;
stroke-dasharray: 3, 3;
@@ -472,7 +490,7 @@ path.casing.tag-highway-steps {
path.casing.tag-bridge-yes {
stroke-width: 14;
stroke: #000;
stroke: #333;
}
path.stroke.tag-highway-construction,
@@ -516,12 +534,16 @@ path.casing.tag-railway-subway {
/* waterways */
path.fill.tag-waterway {
fill: #77d3de;
}
path.stroke.tag-waterway {
stroke: #10539a;
stroke: #77d3de;
stroke-width: 2;
}
path.casing.tag-waterway {
stroke: #6AA2FF;
stroke: #77d3de;
stroke-width: 4;
}
@@ -540,11 +562,11 @@ svg[data-zoom="16"] path.casing.tag-waterway-river {
}
path.stroke.tag-waterway-ditch {
stroke: #10539a;
stroke: #6591ff;
stroke-width: 1;
}
path.casing.tag-waterway-ditch {
stroke: #999692;
stroke: #6591ff;
stroke-width: 3;
}
@@ -573,17 +595,22 @@ path.casing.tag-boundary {
path.casing.tag-boundary-protected_area,
path.casing.tag-boundary-national_park {
stroke: #4D9849;
stroke: #b0e298;
}
text {
font-size:10px;
pointer-events: none;
color: #222;
opacity: 1;
}
.oneway .textpath {
pointer-events: none;
font-size: 7px;
baseline-shift: 2px;
opacity: .7;
}
text.tag-oneway {
@@ -611,7 +638,7 @@ text.pathlabel,
text.pointlabel {
font-size: 12px;
font-weight: bold;
fill: black;
fill: #333;
text-anchor: middle;
pointer-events: none;
}
@@ -628,6 +655,12 @@ text.pointlabel {
dominant-baseline: middle;
}
/* Opera doesn't support dominant-baseline. See #715 */
.opera .pathlabel .textpath {
baseline-shift: -33%;
dominant-baseline: auto;
}
.pointlabel-halo,
.linelabel-halo,
.arealabel-halo {
@@ -637,7 +670,8 @@ text.pointlabel {
text.point {
font-size: 9px;
font-size: 10px;
baseline-shift: 2px;
}
/* Cursors */
@@ -666,9 +700,7 @@ text.point {
}
.mode-select .area,
.mode-browse .area,
.mode-select .multipolygon,
.mode-browse .multipolygon {
.mode-browse .area {
cursor: url(../img/cursor-select-area.png), pointer;
}
@@ -681,7 +713,6 @@ text.point {
.vertex:active,
.line:active,
.area:active,
.multipolygon:active,
.midpoint:active,
.mode-select .selected {
cursor: url(../img/cursor-select-acting.png), pointer;
@@ -697,14 +728,16 @@ text.point {
.mode-draw-line .behavior-hover .way,
.mode-draw-area .behavior-hover .way,
.mode-add-line .behavior-hover .way,
.mode-add-area .behavior-hover .way {
.mode-add-area .behavior-hover .way,
.behavior-drag-node.behavior-hover .way {
cursor:url(../img/cursor-draw-connect-line.png) 9 9, auto;
}
.mode-draw-line .behavior-hover .vertex,
.mode-draw-area .behavior-hover .vertex,
.mode-add-line .behavior-hover .vertex,
.mode-add-area .behavior-hover .vertex {
.mode-add-area .behavior-hover .vertex,
.behavior-drag-node.behavior-hover .vertex {
cursor:url(../img/cursor-draw-connect-vertex.png) 9 9, auto;
}
@@ -715,16 +748,19 @@ text.point {
/* Modes */
.mode-draw-line .vertex.active,
.mode-draw-area .vertex.active {
.mode-draw-area .vertex.active,
.behavior-drag-node .vertex.active {
display: none;
}
.mode-draw-line .way.active,
.mode-draw-area .way.active {
.mode-draw-area .way.active,
.behavior-drag-node .active {
pointer-events: none;
}
/* Ensure drawing doesn't interact with area fills. */
.mode-add-point .area,
.mode-draw-line .area,
.mode-draw-area .area,
.mode-add-line .area,
+1
View File
@@ -0,0 +1 @@
iD.data = {};
+112
View File
@@ -0,0 +1,112 @@
// from http://wiki.openstreetmap.org/wiki/Deprecated_features
// TODO: deal with deprecated 'class' tag
// does not deal with landuse=wood because of indecision
// we will not care about http://taginfo.openstreetmap.org/tags/bicycle_parking=sheffield
iD.data.deprecated = [
{
old: { barrier: 'wire_fence' },
replace: {
barrier: 'fence',
fence_type: 'chain'
}
},
{
old: { barrier: 'wood_fence' },
replace: {
barrier: 'fence',
fence_type: 'wood'
}
},
{
old: { highway: 'ford' },
replace: {
ford: 'yes'
}
},
{
old: { highway: 'ford' },
replace: {
ford: 'yes'
}
},
{
old: { highway: 'ford' },
replace: {
ford: 'yes'
}
},
{
old: { highway: 'stile' },
replace: {
barrier: 'stile'
}
},
{
old: { highway: 'incline' },
replace: {
highway: 'road',
incline: 'up'
}
},
{
old: { highway: 'incline_steep' },
replace: {
highway: 'road',
incline: 'up'
}
},
{
old: { highway: 'unsurfaced' },
replace: {
highway: 'road',
incline: 'unpaved'
}
},
{
old: { highway: 'unsurfaced' },
replace: {
highway: 'road',
incline: 'unpaved'
}
},
{
old: { landuse: 'wood' },
replace: {
highway: 'road',
incline: 'unpaved'
}
},
{
old: { natural: 'marsh' },
replace: {
natural: 'wetland',
wetland: 'marsh'
}
},
{
old: { shop: 'organic' },
replace: {
shop: 'supermarket',
organic: 'only'
}
},
{
old: { power_source: '*' },
replace: {
'generator:source': '$1'
}
},
{
old: { power_rating: '*' },
replace: {
'generator:output': '$1'
}
},
{
old: { bicycle_parking: 'organic' },
replace: {
shop: 'supermarket',
organic: 'only'
}
}
];
+10
View File
@@ -0,0 +1,10 @@
// entirely discarded tags
iD.data.discarded = [
'tiger:upload_uuid',
'tiger:tlid',
'tiger:source',
'tiger:separated',
'geobase:datasetName',
'geobase:uuid',
'sub_sea:type'
];
+609
View File
@@ -0,0 +1,609 @@
iD.data.imagery = [
{
"name": "Bing aerial imagery",
"template": "http://ecn.t{t}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&mkt=en-gb&n=z",
"description": "Satellite imagery.",
"scaleExtent": [
0,
20
],
"subdomains": [
"0",
"1",
"2",
"3"
],
"default": "yes",
"sourcetag": "Bing",
"logo": "bing_maps.png",
"logo_url": "http://www.bing.com/maps",
"terms_url": "http://opengeodata.org/microsoft-imagery-details"
},
{
"name": "MapBox Satellite",
"template": "http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.png",
"description": "Satellite and aerial imagery",
"scaleExtent": [
0,
16
],
"subdomains": [
"a",
"b",
"c"
],
"terms_url": "http://mapbox.com/tos/"
},
{
"name": "OpenStreetMap",
"template": "http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png",
"description": "The default OpenStreetMap layer.",
"scaleExtent": [
0,
18
],
"subdomains": [
"a",
"b",
"c"
]
},
{
"name": " TIGER 2012 Roads Overlay",
"template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-124.81,
24.055
],
[
-66.865,
49.386
]
]
},
{
"name": " TIGER 2012 Roads Overlay",
"template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-179.754,
50.858
],
[
-129.899,
71.463
]
]
},
{
"name": " TIGER 2012 Roads Overlay",
"template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-174.46,
18.702
],
[
-154.516,
26.501
]
]
},
{
"name": " USGS Topographic Maps",
"template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-125.991,
24.005
],
[
-65.988,
50.009
]
]
},
{
"name": " USGS Topographic Maps",
"template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-160.579,
18.902
],
[
-154.793,
22.508
]
]
},
{
"name": " USGS Topographic Maps",
"template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-178.001,
51.255
],
[
-130.004,
71.999
]
]
},
{
"name": " USGS Large Scale Aerial Imagery",
"template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-124.819,
24.496
],
[
-66.931,
49.443
]
]
},
{
"name": "British Columbia bc_mosaic",
"template": "http://{t}.imagery.paulnorman.ca/tiles/bc_mosaic/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c",
"d"
],
"extent": [
[
-123.441,
48.995
],
[
-121.346,
50.426
]
],
"sourcetag": "bc_mosaic",
"terms_url": "http://imagery.paulnorman.ca/tiles/about.html"
},
{
"name": "OS OpenData Streetview",
"template": "http://os.openstreetmap.org/sv/{z}/{x}/{y}.png",
"extent": [
[
-8.72,
49.86
],
[
1.84,
60.92
]
],
"sourcetag": "OS_OpenData_StreetView"
},
{
"name": "OS OpenData Locator",
"template": "http://tiles.itoworld.com/os_locator/{z}/{x}/{y}.png",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS_OpenData_Locator"
},
{
"name": "OS 1:25k historic (OSM)",
"template": "http://ooc.openstreetmap.org/os1/{z}/{x}/{y}.jpg",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS 1:25k"
},
{
"name": "OS 1:25k historic (NLS)",
"template": "http://geo.nls.uk/mapdata2/os/25000/{z}/{x}/{y}.png",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS 1:25k",
"logo": "icons/logo_nls70-nq8.png",
"logo_url": "http://geo.nls.uk/maps/"
},
{
"name": "OS 7th Series historic (OSM)",
"template": "http://ooc.openstreetmap.org/os7/{z}/{x}/{y}.jpg",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS7"
},
{
"name": "OS 7th Series historic (NLS)",
"template": "http://geo.nls.uk/mapdata2/os/seventh/{z}/{x}/{y}.png",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS7",
"logo": "icons/logo_nls70-nq8.png",
"logo_url": "http://geo.nls.uk/maps/"
},
{
"name": "OS New Popular Edition historic",
"template": "http://ooc.openstreetmap.org/npe/{z}/{x}/{y}.png",
"extent": [
[
-5.8,
49.8
],
[
1.9,
55.8
]
],
"sourcetag": "NPE"
},
{
"name": "OS Scottish Popular historic",
"template": "http://ooc.openstreetmap.org/npescotland/tiles/{z}/{x}/{y}.jpg",
"extent": [
[
-7.8,
54.5
],
[
-1.1,
61.1
]
],
"sourcetag": "NPE"
},
{
"name": "Surrey aerial",
"template": "http://gravitystorm.dev.openstreetmap.org/surrey/{z}/{x}/{y}.png",
"extent": [
[
-0.856,
51.071
],
[
0.062,
51.473
]
],
"sourcetag": "Surrey aerial"
},
{
"name": "Haiti - GeoEye Jan 13",
"template": "http://gravitystorm.dev.openstreetmap.org/imagery/haiti/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti GeoEye"
},
{
"name": "Haiti - GeoEye Jan 13+",
"template": "http://maps.nypl.org/tilecache/1/geoeye/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti GeoEye"
},
{
"name": "Haiti - DigitalGlobe",
"template": "http://maps.nypl.org/tilecache/1/dg_crisis/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti DigitalGlobe"
},
{
"name": "Haiti - Street names",
"template": "http://hypercube.telascience.org/tiles/1.0.0/haiti-city/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti streetnames"
},
{
"name": "NAIP",
"template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png",
"description": "National Agriculture Imagery Program",
"extent": [
[
-125.8,
24.2
],
[
-62.3,
49.5
]
],
"sourcetag": "NAIP"
},
{
"name": "NAIP",
"template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png",
"description": "National Agriculture Imagery Program",
"extent": [
[
-168.5,
55.3
],
[
-140,
71.5
]
],
"sourcetag": "NAIP"
},
{
"name": "Ireland - NLS Historic Maps",
"template": "http://geo.nls.uk/maps/ireland/gsgs4136/{z}/{x}/{y}.png",
"extent": [
[
-10.71,
51.32
],
[
-5.37,
55.46
]
],
"sourcetag": "NLS Historic Maps",
"logo": "icons/logo_nls70-nq8.png",
"logo_url": "http://geo.nls.uk/maps/"
},
{
"name": "Denmark - Fugro Aerial Imagery",
"template": "http://tile.openstreetmap.dk/fugro2005/{z}/{x}/{y}.jpg",
"extent": [
[
7.81,
54.44
],
[
15.49,
57.86
]
],
"sourcetag": "Fugro (2005)"
},
{
"name": "Denmark - Stevns Kommune",
"template": "http://tile.openstreetmap.dk/stevns/2009/{z}/{x}/{y}.jpg",
"extent": [
[
12.09144,
55.23403
],
[
12.47712,
55.43647
]
],
"sourcetag": "Stevns Kommune (2009)"
},
{
"name": "Austria - geoimage.at",
"template": "http://geoimage.openstreetmap.at/4d80de696cd562a63ce463a58a61488d/{z}/{x}/{y}.jpg",
"extent": [
[
9.36,
46.33
],
[
17.28,
49.09
]
],
"sourcetag": "geoimage.at"
},
{
"name": "Russia - Kosmosnimki.ru IRS Satellite",
"template": "http://irs.gis-lab.info/?layers=irs&request=GetTile&z={z}&x={x}&y={y}",
"extent": [
[
19.02,
40.96
],
[
77.34,
70.48
]
],
"sourcetag": "Kosmosnimki.ru IRS"
},
{
"name": "Belarus - Kosmosnimki.ru SPOT4 Satellite",
"template": "http://irs.gis-lab.info/?layers=spot&request=GetTile&z={z}&x={x}&y={y}",
"extent": [
[
23.16,
51.25
],
[
32.83,
56.19
]
],
"sourcetag": "Kosmosnimki.ru SPOT4"
},
{
"name": "Australia - Geographic Reference Image",
"template": "http://agri.openstreetmap.org/{z}/{x}/{y}.png",
"extent": [
[
96,
-44
],
[
168,
-9
]
],
"sourcetag": "AGRI"
},
{
"name": "Switzerland - Canton Aargau - AGIS 25cm 2011",
"template": "http://tiles.poole.ch/AGIS/OF2011/{z}/{x}/{y}.png",
"extent": [
[
7.69,
47.13
],
[
8.48,
47.63
]
],
"sourcetag": "AGIS OF2011"
},
{
"name": "Switzerland - Canton Solothurn - SOGIS 2007",
"template": "http://mapproxy.sosm.ch:8080/tiles/sogis2007/EPSG900913/{z}/{x}/{y}.png?origin=nw",
"extent": [
[
7.33,
47.06
],
[
8.04,
47.5
]
],
"sourcetag": "Orthofoto 2007 WMS Solothurn"
},
{
"name": "Poland - Media-Lab fleet GPS masstracks",
"template": "http://masstracks.media-lab.com.pl/{z}/{x}/{y}.png",
"extent": [
[
14,
48.9
],
[
24.2,
55
]
],
"sourcetag": "masstracks"
},
{
"name": "South Africa - CD:NGI Aerial",
"template": "http://{t}.aerial.openstreetmap.org.za/ngi-aerial/{z}/{x}/{y}.jpg",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
17.64,
-34.95
],
[
32.87,
-22.05
]
],
"sourcetag": "ngi-aerial"
}
];
+609
View File
@@ -0,0 +1,609 @@
[
{
"name": "Bing aerial imagery",
"template": "http://ecn.t{t}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&mkt=en-gb&n=z",
"description": "Satellite imagery.",
"scaleExtent": [
0,
20
],
"subdomains": [
"0",
"1",
"2",
"3"
],
"default": "yes",
"sourcetag": "Bing",
"logo": "bing_maps.png",
"logo_url": "http://www.bing.com/maps",
"terms_url": "http://opengeodata.org/microsoft-imagery-details"
},
{
"name": "MapBox Satellite",
"template": "http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.png",
"description": "Satellite and aerial imagery",
"scaleExtent": [
0,
16
],
"subdomains": [
"a",
"b",
"c"
],
"terms_url": "http://mapbox.com/tos/"
},
{
"name": "OpenStreetMap",
"template": "http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png",
"description": "The default OpenStreetMap layer.",
"scaleExtent": [
0,
18
],
"subdomains": [
"a",
"b",
"c"
]
},
{
"name": " TIGER 2012 Roads Overlay",
"template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-124.81,
24.055
],
[
-66.865,
49.386
]
]
},
{
"name": " TIGER 2012 Roads Overlay",
"template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-179.754,
50.858
],
[
-129.899,
71.463
]
]
},
{
"name": " TIGER 2012 Roads Overlay",
"template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-174.46,
18.702
],
[
-154.516,
26.501
]
]
},
{
"name": " USGS Topographic Maps",
"template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-125.991,
24.005
],
[
-65.988,
50.009
]
]
},
{
"name": " USGS Topographic Maps",
"template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-160.579,
18.902
],
[
-154.793,
22.508
]
]
},
{
"name": " USGS Topographic Maps",
"template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-178.001,
51.255
],
[
-130.004,
71.999
]
]
},
{
"name": " USGS Large Scale Aerial Imagery",
"template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
-124.819,
24.496
],
[
-66.931,
49.443
]
]
},
{
"name": "British Columbia bc_mosaic",
"template": "http://{t}.imagery.paulnorman.ca/tiles/bc_mosaic/{z}/{x}/{y}.png",
"subdomains": [
"a",
"b",
"c",
"d"
],
"extent": [
[
-123.441,
48.995
],
[
-121.346,
50.426
]
],
"sourcetag": "bc_mosaic",
"terms_url": "http://imagery.paulnorman.ca/tiles/about.html"
},
{
"name": "OS OpenData Streetview",
"template": "http://os.openstreetmap.org/sv/{z}/{x}/{y}.png",
"extent": [
[
-8.72,
49.86
],
[
1.84,
60.92
]
],
"sourcetag": "OS_OpenData_StreetView"
},
{
"name": "OS OpenData Locator",
"template": "http://tiles.itoworld.com/os_locator/{z}/{x}/{y}.png",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS_OpenData_Locator"
},
{
"name": "OS 1:25k historic (OSM)",
"template": "http://ooc.openstreetmap.org/os1/{z}/{x}/{y}.jpg",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS 1:25k"
},
{
"name": "OS 1:25k historic (NLS)",
"template": "http://geo.nls.uk/mapdata2/os/25000/{z}/{x}/{y}.png",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS 1:25k",
"logo": "icons/logo_nls70-nq8.png",
"logo_url": "http://geo.nls.uk/maps/"
},
{
"name": "OS 7th Series historic (OSM)",
"template": "http://ooc.openstreetmap.org/os7/{z}/{x}/{y}.jpg",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS7"
},
{
"name": "OS 7th Series historic (NLS)",
"template": "http://geo.nls.uk/mapdata2/os/seventh/{z}/{x}/{y}.png",
"extent": [
[
-9,
49.8
],
[
1.9,
61.1
]
],
"sourcetag": "OS7",
"logo": "icons/logo_nls70-nq8.png",
"logo_url": "http://geo.nls.uk/maps/"
},
{
"name": "OS New Popular Edition historic",
"template": "http://ooc.openstreetmap.org/npe/{z}/{x}/{y}.png",
"extent": [
[
-5.8,
49.8
],
[
1.9,
55.8
]
],
"sourcetag": "NPE"
},
{
"name": "OS Scottish Popular historic",
"template": "http://ooc.openstreetmap.org/npescotland/tiles/{z}/{x}/{y}.jpg",
"extent": [
[
-7.8,
54.5
],
[
-1.1,
61.1
]
],
"sourcetag": "NPE"
},
{
"name": "Surrey aerial",
"template": "http://gravitystorm.dev.openstreetmap.org/surrey/{z}/{x}/{y}.png",
"extent": [
[
-0.856,
51.071
],
[
0.062,
51.473
]
],
"sourcetag": "Surrey aerial"
},
{
"name": "Haiti - GeoEye Jan 13",
"template": "http://gravitystorm.dev.openstreetmap.org/imagery/haiti/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti GeoEye"
},
{
"name": "Haiti - GeoEye Jan 13+",
"template": "http://maps.nypl.org/tilecache/1/geoeye/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti GeoEye"
},
{
"name": "Haiti - DigitalGlobe",
"template": "http://maps.nypl.org/tilecache/1/dg_crisis/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti DigitalGlobe"
},
{
"name": "Haiti - Street names",
"template": "http://hypercube.telascience.org/tiles/1.0.0/haiti-city/{z}/{x}/{y}.jpg",
"extent": [
[
-74.5,
17.95
],
[
-71.58,
20.12
]
],
"sourcetag": "Haiti streetnames"
},
{
"name": "NAIP",
"template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png",
"description": "National Agriculture Imagery Program",
"extent": [
[
-125.8,
24.2
],
[
-62.3,
49.5
]
],
"sourcetag": "NAIP"
},
{
"name": "NAIP",
"template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png",
"description": "National Agriculture Imagery Program",
"extent": [
[
-168.5,
55.3
],
[
-140,
71.5
]
],
"sourcetag": "NAIP"
},
{
"name": "Ireland - NLS Historic Maps",
"template": "http://geo.nls.uk/maps/ireland/gsgs4136/{z}/{x}/{y}.png",
"extent": [
[
-10.71,
51.32
],
[
-5.37,
55.46
]
],
"sourcetag": "NLS Historic Maps",
"logo": "icons/logo_nls70-nq8.png",
"logo_url": "http://geo.nls.uk/maps/"
},
{
"name": "Denmark - Fugro Aerial Imagery",
"template": "http://tile.openstreetmap.dk/fugro2005/{z}/{x}/{y}.jpg",
"extent": [
[
7.81,
54.44
],
[
15.49,
57.86
]
],
"sourcetag": "Fugro (2005)"
},
{
"name": "Denmark - Stevns Kommune",
"template": "http://tile.openstreetmap.dk/stevns/2009/{z}/{x}/{y}.jpg",
"extent": [
[
12.09144,
55.23403
],
[
12.47712,
55.43647
]
],
"sourcetag": "Stevns Kommune (2009)"
},
{
"name": "Austria - geoimage.at",
"template": "http://geoimage.openstreetmap.at/4d80de696cd562a63ce463a58a61488d/{z}/{x}/{y}.jpg",
"extent": [
[
9.36,
46.33
],
[
17.28,
49.09
]
],
"sourcetag": "geoimage.at"
},
{
"name": "Russia - Kosmosnimki.ru IRS Satellite",
"template": "http://irs.gis-lab.info/?layers=irs&request=GetTile&z={z}&x={x}&y={y}",
"extent": [
[
19.02,
40.96
],
[
77.34,
70.48
]
],
"sourcetag": "Kosmosnimki.ru IRS"
},
{
"name": "Belarus - Kosmosnimki.ru SPOT4 Satellite",
"template": "http://irs.gis-lab.info/?layers=spot&request=GetTile&z={z}&x={x}&y={y}",
"extent": [
[
23.16,
51.25
],
[
32.83,
56.19
]
],
"sourcetag": "Kosmosnimki.ru SPOT4"
},
{
"name": "Australia - Geographic Reference Image",
"template": "http://agri.openstreetmap.org/{z}/{x}/{y}.png",
"extent": [
[
96,
-44
],
[
168,
-9
]
],
"sourcetag": "AGRI"
},
{
"name": "Switzerland - Canton Aargau - AGIS 25cm 2011",
"template": "http://tiles.poole.ch/AGIS/OF2011/{z}/{x}/{y}.png",
"extent": [
[
7.69,
47.13
],
[
8.48,
47.63
]
],
"sourcetag": "AGIS OF2011"
},
{
"name": "Switzerland - Canton Solothurn - SOGIS 2007",
"template": "http://mapproxy.sosm.ch:8080/tiles/sogis2007/EPSG900913/{z}/{x}/{y}.png?origin=nw",
"extent": [
[
7.33,
47.06
],
[
8.04,
47.5
]
],
"sourcetag": "Orthofoto 2007 WMS Solothurn"
},
{
"name": "Poland - Media-Lab fleet GPS masstracks",
"template": "http://masstracks.media-lab.com.pl/{z}/{x}/{y}.png",
"extent": [
[
14,
48.9
],
[
24.2,
55
]
],
"sourcetag": "masstracks"
},
{
"name": "South Africa - CD:NGI Aerial",
"template": "http://{t}.aerial.openstreetmap.org.za/ngi-aerial/{z}/{x}/{y}.jpg",
"subdomains": [
"a",
"b",
"c"
],
"extent": [
[
17.64,
-34.95
],
[
32.87,
-22.05
]
],
"sourcetag": "ngi-aerial"
}
]
+233
View File
@@ -0,0 +1,233 @@
<?xml version="1.0" encoding="UTF-8"?>
<imagery>
<set>
<name>Bing aerial imagery</name>
<url>http://ecn.t${0|1|2|3}.tiles.virtualearth.net/tiles/a$quadkey.jpeg?g=587&amp;mkt=en-gb&amp;n=z</url>
<scheme>microsoft</scheme>
<sourcetag>Bing</sourcetag>
<attribution_url>http://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/0,0?zl=1&amp;mapVersion=v1&amp;key=Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU&amp;include=ImageryProviders&amp;output=xml</attribution_url>
<logo>bing_maps.png</logo>
<logo_url>http://www.bing.com/maps</logo_url>
<terms_url>http://opengeodata.org/microsoft-imagery-details</terms_url>
<default>yes</default>
</set>
<set>
<name>MapBox Satellite</name>
<url>http://${a|b|c}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/$z/$x/$y.png</url>
<terms_url>http://mapbox.com/tos/</terms_url>
</set>
<set>
<name>MapQuest Open Aerial</name>
<url>http://oatile1.mqcdn.com/tiles/1.0.0/sat/$z/$x/$y.jpg</url>
<terms_url>http://developer.mapquest.com/web/products/open/map#terms</terms_url>
</set>
<set>
<name>OSM - Mapnik</name>
<url>http://${a|b|c}.tile.openstreetmap.org/$z/$x/$y.png</url>
</set>
<set>
<name>OSM - OpenCycleMap</name>
<url>http://tile.opencyclemap.org/cycle/$z/$x/$y.png</url>
</set>
<set>
<name>OSM - MapQuest</name>
<url>http://otile1.mqcdn.com/tiles/1.0.0/osm/$z/$x/$y.jpg</url>
</set>
<set minlat="24.055" minlon="-124.810" maxlat="49.386" maxlon="-66.865">
<name>OSM - Tiger Edited Map</name>
<type>900913</type>
<url>http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png</url>
</set>
<set minlat="50.858" minlon="-179.754" maxlat="71.463" maxlon="-129.899">
<name>OSM - Tiger Edited Map</name>
<type>900913</type>
<url>http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png</url>
</set>
<set minlat="18.702" minlon="-174.460" maxlat="26.501" maxlon="-154.516">
<name>OSM - Tiger Edited Map</name>
<type>900913</type>
<url>http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/$z/$x/$y.png</url>
</set>
<set minlat="24.055" minlon="-124.810" maxlat="49.386" maxlon="-66.865">
<name>OSM US TIGER 2012 Roads Overlay</name>
<type>900913</type>
<url>http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png</url>
</set>
<set minlat="50.858" minlon="-179.754" maxlat="71.463" maxlon="-129.899">
<name>OSM US TIGER 2012 Roads Overlay</name>
<type>900913</type>
<url>http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png</url>
</set>
<set minlat="18.702" minlon="-174.460" maxlat="26.501" maxlon="-154.516">
<name>OSM US TIGER 2012 Roads Overlay</name>
<type>900913</type>
<url>http://${a|b|c}.tile.openstreetmap.us/tiger2012_roads_expanded/$z/$x/$y.png</url>
</set>
<set minlat="24.005" minlon="-125.991" maxlat="50.009" maxlon="-65.988">
<name>OSM US USGS Topographic Maps</name>
<type>900913</type>
<url>http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png</url>
</set>
<set minlat="18.902" minlon="-160.579" maxlat="22.508" maxlon="-154.793">
<name>OSM US USGS Topographic Maps</name>
<type>900913</type>
<url>http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png</url>
</set>
<set minlat="51.255" minlon="-178.001" maxlat="71.999" maxlon="-130.004">
<name>OSM US USGS Topographic Maps</name>
<type>900913</type>
<url>http://${a|b|c}.tile.openstreetmap.us/usgs_scanned_topos/$z/$x/$y.png</url>
</set>
<set minlat="24.496" minlon="-124.819" maxlat="49.443" maxlon="-66.931">
<name>OSM US USGS Large Scale Aerial Imagery</name>
<type>900913</type>
<url>http://${a|b|c}.tile.openstreetmap.us/usgs_large_scale/$z/$x/$y.jpg</url>
</set>
<set minlat="48.995" minlon="-123.441" maxlat="50.426" maxlon="-121.346">
<name>British Columbia bc_mosaic</name>
<type>900913</type>
<url>http://${a|b|c|d}.imagery.paulnorman.ca/tiles/bc_mosaic/$z/$x/$y.png</url>
<terms_url>http://imagery.paulnorman.ca/tiles/about.html</terms_url>
<sourcetag>bc_mosaic</sourcetag>
</set>
<set minlat="49.86" minlon="-8.72" maxlat="60.92" maxlon="1.84">
<name>OS OpenData Streetview</name>
<url>http://os.openstreetmap.org/sv/$z/$x/$y.png</url>
<sourcetag>OS_OpenData_StreetView</sourcetag>
</set>
<set minlat="49.8" minlon="-9" maxlat="61.1" maxlon="1.9">
<name>OS OpenData Locator</name>
<url>http://tiles.itoworld.com/os_locator/$z/$x/$y.png</url>
<sourcetag>OS_OpenData_Locator</sourcetag>
<sourcekey>source:name</sourcekey>
</set>
<set minlat="49.8" minlon="-9" maxlat="61.1" maxlon="1.9">
<name>OS 1:25k historic (OSM)</name>
<url>http://ooc.openstreetmap.org/os1/$z/$x/$y.jpg</url>
<sourcetag>OS 1:25k</sourcetag>
</set>
<set minlat="49.8" minlon="-9" maxlat="61.1" maxlon="1.9">
<name>OS 1:25k historic (NLS)</name>
<scheme>tms</scheme>
<url>http://geo.nls.uk/mapdata2/os/25000/$z/$x/$y.png</url>
<logo>icons/logo_nls70-nq8.png</logo>
<logo_url>http://geo.nls.uk/maps/</logo_url>
<sourcetag>OS 1:25k</sourcetag>
</set>
<set minlat="49.8" minlon="-9" maxlat="61.1" maxlon="1.9">
<name>OS 7th Series historic (OSM)</name>
<url>http://ooc.openstreetmap.org/os7/$z/$x/$y.jpg</url>
<sourcetag>OS7</sourcetag>
</set>
<set minlat="49.8" minlon="-9" maxlat="61.1" maxlon="1.9">
<name>OS 7th Series historic (NLS)</name>
<scheme>tms</scheme>
<url>http://geo.nls.uk/mapdata2/os/seventh/$z/$x/$y.png</url>
<logo>icons/logo_nls70-nq8.png</logo>
<logo_url>http://geo.nls.uk/maps/</logo_url>
<sourcetag>OS7</sourcetag>
</set>
<set minlat="49.8" minlon="-5.8" maxlat="55.8" maxlon="1.9">
<name>OS New Popular Edition historic</name>
<url>http://ooc.openstreetmap.org/npe/$z/$x/$y.png</url>
<sourcetag>NPE</sourcetag>
</set>
<set minlat="54.5" minlon="-7.8" maxlat="61.1" maxlon="-1.1">
<name>OS Scottish Popular historic</name>
<url>http://ooc.openstreetmap.org/npescotland/tiles/$z/$x/$y.jpg</url>
<sourcetag>NPE</sourcetag>
</set>
<set minlat="51.071" minlon="-0.856" maxlat="51.473" maxlon="0.062">
<name>Surrey aerial</name>
<url>http://gravitystorm.dev.openstreetmap.org/surrey/$z/$x/$y.png</url>
<sourcetag>Surrey aerial</sourcetag>
</set>
<set minlat="17.95" minlon="-74.5" maxlat="20.12" maxlon="-71.58">
<name>Haiti - GeoEye Jan 13</name>
<url>http://gravitystorm.dev.openstreetmap.org/imagery/haiti/$z/$x/$y.jpg</url>
<sourcetag>Haiti GeoEye</sourcetag>
</set>
<set minlat="17.95" minlon="-74.5" maxlat="20.12" maxlon="-71.58">
<name>Haiti - GeoEye Jan 13+</name>
<url>http://maps.nypl.org/tilecache/1/geoeye/$z/$x/$y.jpg</url>
<sourcetag>Haiti GeoEye</sourcetag>
</set>
<set minlat="17.95" minlon="-74.5" maxlat="20.12" maxlon="-71.58">
<name>Haiti - DigitalGlobe</name>
<url>http://maps.nypl.org/tilecache/1/dg_crisis/$z/$x/$y.jpg</url>
<sourcetag>Haiti DigitalGlobe</sourcetag>
</set>
<set minlat="17.95" minlon="-74.5" maxlat="20.12" maxlon="-71.58">
<name>Haiti - Street names</name>
<url>http://hypercube.telascience.org/tiles/1.0.0/haiti-city/$z/$x/$y.jpg</url>
<sourcetag>Haiti streetnames</sourcetag>
</set>
<set minlat="24.2" minlon="-125.8" maxlat="49.5" maxlon="-62.3">
<name>National Agriculture Imagery Program</name>
<url>http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/$z/$x/$y.png</url>
<sourcetag>NAIP</sourcetag>
</set>
<set minlat="55.3" minlon="-168.5" maxlat="71.5" maxlon="-140">
<name>National Agriculture Imagery Program</name>
<url>http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/$z/$x/$y.png</url>
<sourcetag>NAIP</sourcetag>
</set>
<set minlat="51.32" minlon="-10.71" maxlat="55.46" maxlon="-5.37">
<name>Ireland - NLS Historic Maps</name>
<scheme>tms</scheme>
<sourcetag>NLS Historic Maps</sourcetag>
<url>http://geo.nls.uk/maps/ireland/gsgs4136/$z/$x/$y.png</url>
<logo>icons/logo_nls70-nq8.png</logo>
<logo_url>http://geo.nls.uk/maps/</logo_url>
</set>
<set minlat="54.44" minlon="7.81" maxlat="57.86" maxlon="15.49">
<name>Denmark - Fugro Aerial Imagery</name>
<url>http://tile.openstreetmap.dk/fugro2005/$z/$x/$y.jpg</url>
<sourcetag>Fugro (2005)</sourcetag>
</set>
<set minlat="55.23403" minlon="12.09144" maxlat="55.43647" maxlon="12.47712">
<name>Denmark - Stevns Kommune</name>
<url>http://tile.openstreetmap.dk/stevns/2009/$z/$x/$y.jpg</url>
<sourcetag>Stevns Kommune (2009)</sourcetag>
</set>
<set minlat="46.33" minlon="9.36" maxlat="49.09" maxlon="17.28">
<name>Austria - geoimage.at</name>
<url>http://geoimage.openstreetmap.at/4d80de696cd562a63ce463a58a61488d/$z/$x/$y.jpg</url>
<sourcetag>geoimage.at</sourcetag>
</set>
<set minlon="19.02" minlat="40.96" maxlon="77.34" maxlat="70.48">
<name>Russia - Kosmosnimki.ru IRS Satellite</name>
<url>http://irs.gis-lab.info/?layers=irs&amp;request=GetTile&amp;z=$z&amp;x=$x&amp;y=$y</url>
<sourcetag>Kosmosnimki.ru IRS</sourcetag>
</set>
<set minlon="23.16" minlat="51.25" maxlon="32.83" maxlat="56.19">
<name>Belarus - Kosmosnimki.ru SPOT4 Satellite</name>
<url>http://irs.gis-lab.info/?layers=spot&amp;request=GetTile&amp;z=$z&amp;x=$x&amp;y=$y</url>
<sourcetag>Kosmosnimki.ru SPOT4</sourcetag>
</set>
<set minlon="96" minlat="-44" maxlon="168" maxlat="-9">
<name>Australia - Geographic Reference Image</name>
<url>http://agri.openstreetmap.org/$z/$x/$y.png</url>
<sourcetag>AGRI</sourcetag>
</set>
<set minlat="47.13" minlon="7.69" maxlat="47.63" maxlon="8.48">
<name>Switzerland - Canton Aargau - AGIS 25cm 2011</name>
<url>http://tiles.poole.ch/AGIS/OF2011/$z/$x/$y.png</url>
<sourcetag>AGIS OF2011</sourcetag>
</set>
<set minlat="47.06" minlon="7.33" maxlat="47.5" maxlon="8.04">
<name>Switzerland - Canton Solothurn - SOGIS 2007</name>
<url>http://mapproxy.sosm.ch:8080/tiles/sogis2007/EPSG900913/$z/$x/$y.png?origin=nw</url>
<sourcetag>Orthofoto 2007 WMS Solothurn</sourcetag>
</set>
<set minlat="48.9" minlon="14" maxlat="55" maxlon="24.2">
<name>Poland - Media-Lab fleet GPS masstracks</name>
<url>http://masstracks.media-lab.com.pl/$z/$x/$y.png</url>
<sourcetag>masstracks</sourcetag>
</set>
<set minlat="-34.95" minlon="17.64" maxlat="-22.05" maxlon="32.87">
<name>South Africa - CD:NGI Aerial</name>
<url>http://${a|b|c}.aerial.openstreetmap.org.za/ngi-aerial/$z/$x/$y.jpg</url>
<sourcetag>ngi-aerial</sourcetag>
</set>
</imagery>
+87
View File
@@ -0,0 +1,87 @@
var fs = require('fs'),
cheerio = require('cheerio');
$ = cheerio.load(fs.readFileSync('imagery.xml'));
var imagery = [];
// CENSORSHIP! No, these are just layers that essentially duplicate other layers
// or which have no clear use case.
var censor = {
'MapQuest Open Aerial': true,
'OSM - OpenCycleMap': true,
'OSM - MapQuest': true
};
var replace = {
'OSM - Mapnik': 'OpenStreetMap',
'National Agriculture Imagery Program': 'NAIP'
};
var description = {
'MapBox Satellite': 'Satellite and aerial imagery',
'OpenStreetMap': 'The default OpenStreetMap layer.',
'OSM US TIGER 2012 Roads Overlay': 'Public domain road data from the US Government.',
'Bing aerial imagery': 'Satellite imagery.',
'NAIP': 'National Agriculture Imagery Program'
};
var scaleExtent = {
'MapBox Satellite': [0, 16],
'OpenStreetMap': [0, 18],
'OSM US TIGER 2012 Roads Overlay': [0, 17],
'Bing aerial imagery': [0, 20]
};
$('set').each(function(i) {
var elem = $(this);
var im = {
name: $(this).find('name').first().text(),
template: $(this).find('url').first().text()
};
// no luck with mapquest servers currently...
if (im.template.match(/mapquest/g)) return;
if (censor[im.name]) return;
im.name = im.name.replace('OSM US', '');
if (replace[im.name]) im.name = replace[im.name];
if (description[im.name]) im.description = description[im.name];
if (scaleExtent[im.name]) im.scaleExtent = scaleExtent[im.name];
var subdomains = [];
im.template = im.template
.replace('$quadkey', '{u}')
.replace(/\$(\w)/g, function(m) {
return '{' + m[1] + '}';
})
.replace(/\$\{([^}.]+)\}/g, function(m) {
subdomains = m.slice(2, m.length - 1).split('|');
return '{t}';
});
if (subdomains.length) im.subdomains = subdomains;
if (elem.attr('minlat')) {
im.extent = [
[+elem.attr('minlon'),
+elem.attr('minlat')],
[+elem.attr('maxlon'),
+elem.attr('maxlat')]];
}
['default', 'sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) {
if (elem.find(a).length) {
im[a] = elem.find(a).first().text();
}
});
imagery.push(im);
});
fs.writeFileSync('imagery.json', JSON.stringify(imagery, null, 4));
fs.writeFileSync('imagery.js', 'iD.data.imagery = ' + JSON.stringify(imagery, null, 4) + ';');
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 B

After

Width:  |  Height:  |  Size: 158 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 338 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

+110 -1071
View File
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 88 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

+85 -27
View File
@@ -9,8 +9,8 @@
<!-- mobile devices -->
<meta name='viewport' content='initial-scale=1.0 maximum-scale=1.0'>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />
<script src='js/lib/lodash.js'></script>
<script src='js/lib/d3.v3.js'></script>
@@ -34,19 +34,24 @@
<script src='js/id/presetdata.js'></script>
<script src='js/id/services/taginfo.js'></script>
<script src='data/data.js'></script>
<script src='data/deprecated.js'></script>
<script src='data/imagery.js'></script>
<script src='data/discarded.js'></script>
<script src="js/id/geo.js"></script>
<script src="js/id/geo/extent.js"></script>
<script src='js/id/renderer/background.js'></script>
<script src='js/id/renderer/background_source.js'></script>
<script src='js/id/renderer/map.js'></script>
<script src='js/id/renderer/layers.js'></script>
<script src="js/id/svg.js"></script>
<script src="js/id/svg/areas.js"></script>
<script src="js/id/svg/lines.js"></script>
<script src="js/id/svg/member_classes.js"></script>
<script src="js/id/svg/midpoints.js"></script>
<script src="js/id/svg/multipolygons.js"></script>
<script src="js/id/svg/points.js"></script>
<script src="js/id/svg/surface.js"></script>
<script src="js/id/svg/tag_classes.js"></script>
@@ -54,15 +59,18 @@
<script src="js/id/svg/labels.js"></script>
<script src="js/id/ui.js"></script>
<script src='js/id/ui/attribution.js'></script>
<script src='js/id/ui/radial_menu.js'></script>
<script src='js/id/ui/inspector.js'></script>
<script src='js/id/ui/modal.js'></script>
<script src='js/id/ui/cmd.js'></script>
<script src='js/id/ui/confirm.js'></script>
<script src='js/id/ui/commit.js'></script>
<script src='js/id/ui/success.js'></script>
<script src='js/id/ui/loading.js'></script>
<script src='js/id/ui/userpanel.js'></script>
<script src='js/id/ui/account.js'></script>
<script src='js/id/ui/layerswitcher.js'></script>
<script src='js/id/ui/modes.js'></script>
<script src='js/id/ui/contributors.js'></script>
<script src='js/id/ui/geocoder.js'></script>
<script src='js/id/ui/geolocate.js'></script>
@@ -70,33 +78,45 @@
<script src='js/id/ui/flash.js'></script>
<script src='js/id/ui/save.js'></script>
<script src='js/id/ui/splash.js'></script>
<script src='js/id/ui/restore.js'></script>
<script src='js/id/ui/tag_reference.js'></script>
<script src='js/id/ui/key_reference.js'></script>
<script src='js/id/ui/preset.js'></script>
<script src='js/id/ui/presetsearch.js'></script>
<script src='js/id/ui/presetfavs.js'></script>
<script src='js/id/ui/lasso.js'></script>
<script src='js/id/ui/source_switch.js'></script>
<script src='js/id/ui/toggle.js'></script>
<script src='js/id/ui/undo_redo.js'></script>
<script src='js/id/ui/zoom.js'></script>
<script src='js/id/actions.js'></script>
<script src="js/id/actions/add_midpoint.js"></script>
<script src='js/id/actions/add_entity.js'></script>
<script src='js/id/actions/add_vertex.js'></script>
<script src='js/id/actions/change_tags.js'></script>
<script src='js/id/actions/connect.js'></script>
<script src='js/id/actions/delete_multiple.js'></script>
<script src='js/id/actions/delete_node.js'></script>
<script src="js/id/actions/delete_relation.js"></script>
<script src="js/id/actions/delete_way.js"></script>
<script src='js/id/actions/disconnect.js'></script>
<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/circularize.js'></script>
<script src='js/id/actions/orthogonalize.js'></script>
<script src='js/id/actions/noop.js'></script>
<script src='js/id/actions/reverse_way.js'></script>
<script src='js/id/actions/split_way.js'></script>
<script src='js/id/actions/unjoin_node.js'></script>
<script src='js/id/actions/reverse.js'></script>
<script src='js/id/actions/split.js'></script>
<script src='js/id/behavior.js'></script>
<script src='js/id/behavior/add_way.js'></script>
<script src='js/id/behavior/drag.js'></script>
<script src='js/id/behavior/drag_midpoint.js'></script>
<script src='js/id/behavior/drag_node.js'></script>
<script src='js/id/behavior/draw.js'></script>
<script src='js/id/behavior/lasso.js'></script>
<script src='js/id/behavior/draw_way.js'></script>
<script src='js/id/behavior/hash.js'></script>
<script src='js/id/behavior/hover.js'></script>
@@ -114,41 +134,59 @@
<script src='js/id/operations.js'></script>
<script src='js/id/operations/circularize.js'></script>
<script src='js/id/operations/orthogonalize.js'></script>
<script src='js/id/operations/delete.js'></script>
<script src='js/id/operations/disconnect.js'></script>
<script src='js/id/operations/merge.js'></script>
<script src='js/id/operations/move.js'></script>
<script src='js/id/operations/reverse.js'></script>
<script src='js/id/operations/split.js'></script>
<script src='js/id/operations/unjoin.js'></script>
<script src='js/id/graph/entity.js'></script>
<script src='js/id/graph/graph.js'></script>
<script src='js/id/graph/history.js'></script>
<script src='js/id/graph/node.js'></script>
<script src='js/id/graph/relation.js'></script>
<script src='js/id/graph/way.js'></script>
<script src='js/id/core/difference.js'></script>
<script src='js/id/core/entity.js'></script>
<script src='js/id/core/graph.js'></script>
<script src='js/id/core/history.js'></script>
<script src='js/id/core/node.js'></script>
<script src='js/id/core/relation.js'></script>
<script src='js/id/core/way.js'></script>
<script src='js/id/controller.js'></script>
<script src='js/id/validate.js'></script>
<script src='js/id/connection.js'></script>
<script src='js/id/validate.js'></script>
<script src='locale/locale.js'></script>
<script src='js/lib/locale.js'></script>
<script src='locale/da.js'></script>
<script src='locale/de.js'></script>
<script src='locale/en.js'></script>
<script src='locale/es.js'></script>
<script src='locale/fr.js'></script>
<script src='locale/ja.js'></script>
<script src='locale/lv.js'></script>
<script src='locale/tr.js'></script>
</head>
<body>
<div id="iD"></div><script>
locale.current = 'en';
<div id='iD'></div><script>
locale
.current('en')
.current(iD.detect().locale);
var id = iD();
d3.json('keys.json', function(err, keys) {
d3.json('presets/presets_josm.json', function(err, presets_data) {
var id = iD();
id.connection().keys(keys)
.presetData(iD.presetData().data(presets_data))
.url('http://api06.dev.openstreetmap.org');
d3.select("#iD").call(id);
id.connection()
.keys(keys)
.presetData(iD.presetData().data(presets_data));
d3.select("#iD")
.call(id.ui())
});
});
</script></body>
</script>
<script type="text/javascript">
<!-- google analytics -->
<script type='text/javascript'>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-38039653-2']);
_gaq.push(['_trackPageview']);
@@ -156,6 +194,26 @@ _gaq.push(['_trackPageview']);
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
// javascript errors
var lastev = '';
window.onerror = function(message, file, lineNumber) {
var ev = ['_trackEvent', 'error', file + ':' + lineNumber, message + ''];
if (ev.join(',') !== lastev) {
_gaq.push(ev);
lastev = ev.join(',');
}
};
})();
</script>
<!-- crazyegg -->
<script type="text/javascript">
setTimeout(function(){var a=document.createElement("script");
var b=document.getElementsByTagName("script")[0];
a.src=document.location.protocol+"//dnn506yrbagrg.cloudfront.net/pages/scripts/0013/6714.js?"+Math.floor(new Date().getTime()/3600000);
a.async=true;a.type="text/javascript";b.parentNode.insertBefore(a,b)}, 1);
</script>
</body>
</html>
+11 -2
View File
@@ -16,8 +16,17 @@
</head>
<body>
<div id="iD"></div><script>
locale.current = 'en';
var id = iD();
id.connection().url('http://api06.dev.openstreetmap.org');
d3.select("#iD").call(id);
d3.json('keys.json', function(err, keys) {
id.connection()
.keys(keys)
.url('http://api06.dev.openstreetmap.org');
d3.select("#iD")
.call(id.ui())
});
</script></body>
</html>
+42 -36
View File
@@ -1,53 +1,59 @@
iD.actions.Circularize = function(wayId, map) {
iD.actions.Circularize = function(wayId, projection, count) {
count = count || 12;
function closestIndex(nodes, loc) {
var idx, min = Infinity, dist;
for (var i = 0; i < nodes.length; i++) {
dist = iD.geo.dist(nodes[i].loc, loc);
if (dist < min) {
min = dist;
idx = i;
}
}
return idx;
}
var action = function(graph) {
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
tags = {}, key, role;
var points = nodes.map(function(n) {
return map.projection(n.loc);
}),
nodes = _.uniq(graph.childNodes(way)),
points = nodes.map(function(n) { return projection(n.loc); }),
centroid = d3.geom.polygon(points).centroid(),
radius = d3.median(points, function(p) {
return iD.geo.dist(centroid, p);
}),
circular_nodes = [];
ids = [];
for (var i = 0; i < 12; i++) {
circular_nodes.push(iD.Node({ loc: map.projection.invert([
centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius,
centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius])
}));
for (var i = 0; i < count; i++) {
var node,
loc = projection.invert([
centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius,
centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius]);
if (nodes.length) {
var idx = closestIndex(nodes, loc);
node = nodes[idx];
nodes.splice(idx, 1);
} else {
node = iD.Node();
}
ids.push(node.id);
graph = graph.replace(node.move(loc));
}
circular_nodes.push(circular_nodes[0]);
ids.push(ids[0]);
graph = graph.replace(way.update({nodes: ids}));
for (i = 0; i < nodes.length; i++) {
if (graph.parentWays(nodes[i]).length > 1) {
var closest, closest_dist = Infinity, dist;
for (var j = 0; j < circular_nodes.length; j++) {
dist = iD.geo.dist(circular_nodes[j].loc, nodes[i].loc);
if (dist < closest_dist) {
closest_dist = dist;
closest = j;
}
}
circular_nodes.splice(closest, 1, nodes[i]);
if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]);
else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]);
} else {
graph = graph.remove(nodes[i]);
}
graph.parentWays(nodes[i]).forEach(function(parent) {
graph = graph.replace(parent.replaceNode(nodes[i].id,
ids[closestIndex(graph.childNodes(way), nodes[i].loc)]));
});
graph = iD.actions.DeleteNode(nodes[i].id)(graph);
}
for (i = 0; i < circular_nodes.length; i++) {
graph = graph.replace(circular_nodes[i]);
}
return graph.replace(way.update({
nodes: _.pluck(circular_nodes, 'id')
}));
return graph;
};
action.enabled = function(graph) {
+44
View File
@@ -0,0 +1,44 @@
// Connect the ways at the given nodes.
//
// The last node will survive. All other nodes will be replaced with
// the surviving node in parent ways, and then removed.
//
// Tags and relation memberships of of non-surviving nodes are merged
// to the survivor.
//
// This is the inverse of `iD.actions.Disconnect`.
//
// Reference:
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java
//
iD.actions.Connect = function(nodeIds) {
var action = function(graph) {
var survivor = graph.entity(_.last(nodeIds));
for (var i = 0; i < nodeIds.length - 1; i++) {
var node = graph.entity(nodeIds[i]), index;
graph.parentWays(node).forEach(function(parent) {
graph = graph.replace(parent.replaceNode(node.id, survivor.id));
});
graph.parentRelations(node).forEach(function(parent) {
graph = graph.replace(parent.replaceMember(node, survivor));
});
survivor = survivor.mergeTags(node.tags);
graph = iD.actions.DeleteNode(node.id)(graph);
}
graph = graph.replace(survivor);
return graph;
};
action.enabled = function(graph) {
return nodeIds.length > 1;
};
return action;
};
+18
View File
@@ -0,0 +1,18 @@
iD.actions.DeleteMultiple = function(ids) {
return function(graph) {
var actions = {
way: iD.actions.DeleteWay,
node: iD.actions.DeleteNode,
relation: iD.actions.DeleteRelation
};
ids.forEach(function(id) {
var entity = graph.entity(id);
if (entity) { // It may have been deleted aready.
graph = actions[entity.type](id)(graph);
}
});
return graph;
};
};
+13
View File
@@ -0,0 +1,13 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as
iD.actions.DeleteRelation = function(relationId) {
return function(graph) {
var relation = graph.entity(relationId);
graph.parentRelations(relation)
.forEach(function(parent) {
graph = graph.replace(parent.removeMember(relationId));
});
return graph.remove(relation);
};
};
+1 -1
View File
@@ -8,7 +8,7 @@ iD.actions.DeleteWay = function(wayId) {
graph = graph.replace(parent.removeMember(wayId));
});
way.nodes.forEach(function (nodeId) {
way.nodes.forEach(function(nodeId) {
var node = graph.entity(nodeId);
// Circular ways include nodes more than once, so they
+36
View File
@@ -0,0 +1,36 @@
iD.actions.DeprecateTags = function(entityId) {
return function(graph) {
var entity = graph.entity(entityId),
newtags = _.clone(entity.tags),
change = false,
rule;
// This handles deprecated tags with a single condition
for (var i = 0; i < iD.data.deprecated.length; i++) {
rule = iD.data.deprecated[i];
var match = _.pairs(rule.old)[0],
replacements = rule.replace ? _.pairs(rule.replace) : null;
if (entity.tags[match[0]] && match[1] === '*') {
var value = entity.tags[match[0]];
if (replacements && !newtags[replacements[0][0]]) {
newtags[replacements[0][0]] = value;
}
delete newtags[match[0]];
change = true;
} else if (entity.tags[match[0]] === match[1]) {
newtags = _.assign({}, rule.replace || {}, _.omit(newtags, match[0]));
change = true;
}
}
if (change) {
return graph.replace(entity.update({tags: newtags}));
} else {
return graph;
}
};
};
@@ -1,14 +1,16 @@
// Unjoin the ways at the given node.
// Disconect the ways at the given node.
//
// For testing convenience, accepts an ID to assign to the (first) new node.
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.
//
// This is the inverse of `iD.actions.Connect`.
//
// Reference:
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java
//
iD.actions.UnjoinNode = function(nodeId, newNodeId) {
iD.actions.Disconnect = function(nodeId, newNodeId) {
var action = function(graph) {
if (!action.enabled(graph))
return graph;
+76
View File
@@ -0,0 +1,76 @@
// Join ways at the end node they share.
//
// This is the inverse of `iD.actions.Split`.
//
// Reference:
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as
// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java
//
iD.actions.Join = function(ids) {
var idA = ids[0],
idB = ids[1];
function groupEntitiesByGeometry(graph) {
var entities = ids.map(function(id) { return graph.entity(id); });
return _.extend({line: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); }));
}
var action = function(graph) {
var a = graph.entity(idA),
b = graph.entity(idB),
nodes;
if (a.first() === b.first()) {
// a <-- b ==> c
// Expected result:
// a <-- b <-- c
b = iD.actions.Reverse(idB)(graph).entity(idB);
nodes = b.nodes.slice().concat(a.nodes.slice(1));
} else if (a.first() === b.last()) {
// a <-- b <== c
// Expected result:
// a <-- b <-- c
nodes = b.nodes.concat(a.nodes.slice(1));
} else if (a.last() === b.first()) {
// a --> b ==> c
// Expected result:
// a --> b --> c
nodes = a.nodes.concat(b.nodes.slice(1));
} else if (a.last() === b.last()) {
// a --> b <== c
// Expected result:
// a --> b --> c
b = iD.actions.Reverse(idB)(graph).entity(idB);
nodes = a.nodes.concat(b.nodes.slice().slice(1));
}
graph.parentRelations(b).forEach(function(parent) {
graph = graph.replace(parent.replaceMember(b, a));
});
graph = graph.replace(a.mergeTags(b.tags).update({ nodes: nodes }));
graph = iD.actions.DeleteWay(idB)(graph);
return graph;
};
action.enabled = function(graph) {
var geometries = groupEntitiesByGeometry(graph);
if (ids.length !== 2 || ids.length !== geometries.line.length)
return false;
var a = graph.entity(idA),
b = graph.entity(idB);
return a.first() === b.first() ||
a.first() === b.last() ||
a.last() === b.first() ||
a.last() === b.last();
};
return action;
};
+35
View File
@@ -0,0 +1,35 @@
iD.actions.Merge = function(ids) {
function groupEntitiesByGeometry(graph) {
var entities = ids.map(function(id) { return graph.entity(id); });
return _.extend({point: [], area: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); }));
}
var action = function(graph) {
var geometries = groupEntitiesByGeometry(graph),
area = geometries.area[0],
points = geometries.point;
points.forEach(function(point) {
area = area.mergeTags(point.tags);
graph.parentRelations(point).forEach(function(parent) {
graph = graph.replace(parent.replaceMember(point, area));
});
graph = graph.remove(point);
});
graph = graph.replace(area);
return graph;
};
action.enabled = function(graph) {
var geometries = groupEntitiesByGeometry(graph);
return geometries.area.length === 1 &&
geometries.point.length > 0 &&
(geometries.area.length + geometries.point.length) === ids.length;
};
return action;
};
+2 -2
View File
@@ -1,9 +1,9 @@
iD.actions.MoveWay = function(wayId, delta, projection) {
return function(graph) {
return graph.update(function (graph) {
return graph.update(function(graph) {
var way = graph.entity(wayId);
_.uniq(way.nodes).forEach(function (id) {
_.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]]);
+109
View File
@@ -0,0 +1,109 @@
/*
* Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as
*/
iD.actions.Orthogonalize = function(wayId, projection) {
var action = function(graph) {
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
points = nodes.map(function(n) { return projection(n.loc); }),
quad_nodes = [],
best, i, j;
var score = squareness();
for (i = 0; i < 1000; i++) {
var motions = points.map(stepMap);
for (j = 0; j < motions.length; j++) {
points[j] = addPoints(points[j],motions[j]);
}
var newScore = squareness();
if (newScore < score) {
best = _.clone(points);
score = newScore;
}
if (score < 1.0e-8) {
break;
}
}
points = best;
for (i = 0; i < points.length - 1; i++) {
graph = graph.replace(graph.entity(nodes[i].id).move(projection.invert(points[i])));
}
return graph;
function stepMap(b, i, array) {
var a = array[(i - 1 + array.length) % array.length],
c = array[(i + 1) % array.length],
p = subtractPoints(a, b),
q = subtractPoints(c, b);
var scale = iD.geo.dist(p, [0, 0]) + iD.geo.dist(q, [0, 0]);
p = normalizePoint(p, 1.0);
q = normalizePoint(q, 1.0);
var dotp = p[0] *q[0] + p[1] *q[1];
// nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270).
if (dotp < -0.707106781186547) {
dotp += 1.0;
}
return normalizePoint(addPoints(p, q), 0.1 * dotp * scale);
}
function squareness() {
var g = 0.0;
for (var i = 1; i < points.length - 1; i++) {
var score = scoreOfPoints(points[i - 1], points[i], points[i + 1]);
g += score;
}
var startScore = scoreOfPoints(points[points.length - 1], points[0], points[1]);
var endScore = scoreOfPoints(points[points.length - 2], points[points.length - 1], points[0]);
g += startScore;
g += endScore;
return g;
}
function scoreOfPoints(a, b, c) {
var p = subtractPoints(a, b),
q = subtractPoints(c, b);
p = normalizePoint(p, 1.0);
q = normalizePoint(q, 1.0);
var dotp = p[0] * q[0] + p[1] * q[1];
// score is constructed so that +1, -1 and 0 are all scored 0, any other angle
// is scored higher.
return 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1)));
}
function subtractPoints(a, b) {
return [a[0] - b[0], a[1] - b[1]];
}
function addPoints(a, b) {
return [a[0] + b[0], a[1] + b[1]];
}
function normalizePoint(point, thickness) {
var vector = [0, 0];
var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]);
if (length !== 0) {
vector[0] = point[0] / length;
vector[1] = point[1] / length;
}
vector[0] *= thickness;
vector[1] *= thickness;
return vector;
}
};
action.enabled = function(graph) {
return graph.entity(wayId).isClosed();
};
return action;
};
@@ -27,7 +27,7 @@
http://wiki.openstreetmap.org/wiki/Route#Members
http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java
*/
iD.actions.ReverseWay = function(wayId) {
iD.actions.Reverse = function(wayId) {
var replacements = [
[/:right$/, ':left'], [/:left$/, ':right'],
[/:forward$/, ':backward'], [/:backward$/, ':forward']
@@ -62,8 +62,8 @@ iD.actions.ReverseWay = function(wayId) {
tags[reverseKey(key)] = reverseValue(key, way.tags[key]);
}
graph.parentRelations(way).forEach(function (relation) {
relation.members.forEach(function (member, index) {
graph.parentRelations(way).forEach(function(relation) {
relation.members.forEach(function(member, index) {
if (member.id === way.id && (role = {forward: 'backward', backward: 'forward'}[member.role])) {
relation = relation.updateMember({role: role}, index);
graph = graph.replace(relation);
+102
View File
@@ -0,0 +1,102 @@
// Split a way at the given node.
//
// This is the inverse of `iD.actions.Join`.
//
// For testing convenience, accepts an ID to assign to the new way.
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.
//
// Reference:
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
//
iD.actions.Split = function(nodeId, newWayId) {
function candidateWays(graph) {
var node = graph.entity(nodeId),
parents = graph.parentWays(node);
return parents.filter(function(parent) {
return parent.isClosed() ||
(parent.first() !== nodeId &&
parent.last() !== nodeId);
});
}
var action = function(graph) {
var wayA = candidateWays(graph)[0],
wayB = iD.Way({id: newWayId, tags: wayA.tags}),
nodesA,
nodesB,
isArea = wayA.isArea();
if (wayA.isClosed()) {
var nodes = wayA.nodes.slice(0, -1),
idxA = _.indexOf(nodes, nodeId),
idxB = idxA + Math.floor(nodes.length / 2);
if (idxB >= nodes.length) {
idxB %= nodes.length;
nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1));
nodesB = nodes.slice(idxB, idxA + 1);
} else {
nodesA = nodes.slice(idxA, idxB + 1);
nodesB = nodes.slice(idxB).concat(nodes.slice(0, idxA + 1));
}
} else {
var idx = _.indexOf(wayA.nodes, nodeId);
nodesA = wayA.nodes.slice(0, idx + 1);
nodesB = wayA.nodes.slice(idx);
}
wayA = wayA.update({nodes: nodesA});
wayB = wayB.update({nodes: nodesB});
graph = graph.replace(wayA);
graph = graph.replace(wayB);
graph.parentRelations(wayA).forEach(function(relation) {
if (relation.isRestriction()) {
var via = relation.memberByRole('via');
if (via && wayB.contains(via.id)) {
relation = relation.updateMember({id: wayB.id}, relation.memberById(wayA.id).index);
graph = graph.replace(relation);
}
} else {
var role = relation.memberById(wayA.id).role,
last = wayB.last(),
i = relation.memberById(wayA.id).index,
j;
for (j = 0; j < relation.members.length; j++) {
var entity = graph.entity(relation.members[j].id);
if (entity && entity.type === 'way' && entity.contains(last)) {
break;
}
}
relation = relation.addMember({id: wayB.id, type: 'way', role: role}, i <= j ? i + 1 : i);
graph = graph.replace(relation);
}
});
if (isArea) {
var multipolygon = iD.Relation({
tags: _.extend({}, wayA.tags, {type: 'multipolygon'}),
members: [
{id: wayA.id, role: 'outer', type: 'way'},
{id: wayB.id, role: 'outer', type: 'way'}
]});
graph = graph.replace(multipolygon);
graph = graph.replace(wayA.update({tags: {}}));
graph = graph.replace(wayB.update({tags: {}}));
}
return graph;
};
action.enabled = function(graph) {
return candidateWays(graph).length === 1;
};
return action;
};
-69
View File
@@ -1,69 +0,0 @@
// Split a way at the given node.
//
// For testing convenience, accepts an ID to assign to the new way.
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.
//
// Reference:
// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
//
iD.actions.SplitWay = function(nodeId, newWayId) {
function candidateWays(graph) {
var node = graph.entity(nodeId),
parents = graph.parentWays(node);
return parents.filter(function (parent) {
return parent.first() !== nodeId &&
parent.last() !== nodeId;
})
}
var action = function(graph) {
if (!action.enabled(graph))
return graph;
var way = candidateWays(graph)[0],
idx = _.indexOf(way.nodes, nodeId);
// Create a 'b' way that contains all of the tags in the second
// half of this way
var newWay = iD.Way({id: newWayId, tags: way.tags, nodes: way.nodes.slice(idx)});
graph = graph.replace(newWay);
// Reduce the original way to only contain the first set of nodes
graph = graph.replace(way.update({nodes: way.nodes.slice(0, idx + 1)}));
graph.parentRelations(way).forEach(function(relation) {
if (relation.isRestriction()) {
var via = relation.memberByRole('via');
if (via && newWay.contains(via.id)) {
relation = relation.updateMember({id: newWay.id}, relation.memberById(way.id).index);
graph = graph.replace(relation);
}
} else {
var role = relation.memberById(way.id).role,
last = newWay.last(),
i = relation.memberById(way.id).index,
j;
for (j = 0; j < relation.members.length; j++) {
var entity = graph.entity(relation.members[j].id);
if (entity && entity.type === 'way' && entity.contains(last)) {
break;
}
}
relation = relation.addMember({id: newWay.id, type: 'way', role: role}, i <= j ? i + 1 : i);
graph = graph.replace(relation);
}
});
return graph;
};
action.enabled = function(graph) {
return candidateWays(graph).length === 1;
};
return action;
};
+9 -10
View File
@@ -1,18 +1,16 @@
iD.behavior.AddWay = function(mode) {
var map = mode.map,
controller = mode.controller,
event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'),
draw = iD.behavior.Draw(map);
iD.behavior.AddWay = function(context) {
var event = d3.dispatch('start', 'startFromWay', 'startFromNode'),
draw = iD.behavior.Draw(context);
var addWay = function(surface) {
draw.on('click', event.start)
.on('clickWay', event.startFromWay)
.on('clickNode', event.startFromNode)
.on('clickMidpoint', event.startFromMidpoint)
.on('cancel', addWay.cancel)
.on('finish', addWay.cancel);
map.fastEnable(false)
context.map()
.fastEnable(false)
.minzoom(16)
.dblclickEnable(false);
@@ -20,19 +18,20 @@ iD.behavior.AddWay = function(mode) {
};
addWay.off = function(surface) {
map.fastEnable(true)
context.map()
.fastEnable(true)
.minzoom(0)
.tail(false);
window.setTimeout(function() {
map.dblclickEnable(true);
context.map().dblclickEnable(true);
}, 1000);
surface.call(draw.off);
};
addWay.cancel = function() {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
};
return d3.rebind(addWay, event, 'on');
+16 -21
View File
@@ -14,7 +14,7 @@
* Delegation is supported via the `delegate` function.
*/
iD.behavior.drag = function () {
iD.behavior.drag = function() {
function d3_eventCancel() {
d3.event.stopPropagation();
d3.event.preventDefault();
@@ -24,8 +24,7 @@ iD.behavior.drag = function () {
origin = null,
selector = '',
filter = null,
keybinding = d3.keybinding('drag'),
event_, target;
event_, target, surface;
event.of = function(thiz, argumentz) {
return function(e1) {
@@ -50,27 +49,26 @@ iD.behavior.drag = function () {
moved = 0;
var w = d3.select(window)
.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove)
.on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true);
.on(touchId !== null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove)
.on(touchId !== null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true);
if (origin) {
offset = origin.apply(target, arguments);
offset = [ offset[0] - origin_[0], offset[1] - origin_[1] ];
offset = [offset[0] - origin_[0], offset[1] - origin_[1]];
} else {
offset = [ 0, 0 ];
offset = [0, 0];
}
if (touchId == null) d3_eventCancel();
if (touchId === null) d3_eventCancel();
function point() {
var p = target.parentNode;
return touchId != null ? d3.touches(p).filter(function (p) {
var p = target.parentNode || surface;
return touchId !== null ? d3.touches(p).filter(function(p) {
return p.identifier === touchId;
})[0] : d3.mouse(p);
}
function dragmove() {
if (!target.parentNode) return dragend();
var p = point(),
dx = p[0] - origin_[0],
@@ -103,8 +101,8 @@ iD.behavior.drag = function () {
if (d3.event.target === eventTarget) w.on("click.drag", click, true);
}
w.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null)
.on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null);
w.on(touchId !== null ? "touchmove.drag-" + touchId : "mousemove.drag", null)
.on(touchId !== null ? "touchend.drag-" + touchId : "mouseup.drag", null);
}
function click() {
@@ -137,9 +135,6 @@ iD.behavior.drag = function () {
drag.off = function(selection) {
selection.on("mousedown.drag" + selector, null)
.on("touchstart.drag" + selector, null);
keybinding
.on('⌘+Z', null)
.on('⌃+Z', null);
};
drag.delegate = function(_) {
@@ -174,11 +169,11 @@ iD.behavior.drag = function () {
return drag;
};
keybinding
.on('⌘+Z', drag.cancel)
.on('⌃+Z', drag.cancel);
d3.select(document).call(keybinding);
drag.surface = function() {
if (!arguments.length) return surface;
surface = arguments[0];
return drag;
};
return d3.rebind(drag, event, "on");
};
-32
View File
@@ -1,32 +0,0 @@
iD.behavior.DragMidpoint = function(mode) {
var history = mode.history,
projection = mode.map.projection;
var behavior = iD.behavior.drag()
.delegate(".midpoint")
.origin(function(d) {
return projection(d.loc);
})
.on('start', function(d) {
var node = iD.Node();
history.perform(iD.actions.AddMidpoint(d, node));
var vertex = d3.selectAll('.vertex')
.filter(function(data) { return data.id === node.id; });
behavior.target(vertex.node(), vertex.datum());
})
.on('move', function(d) {
d3.event.sourceEvent.stopPropagation();
history.replace(
iD.actions.MoveNode(d.id, projection.invert(d3.event.point)));
})
.on('end', function() {
history.replace(
iD.actions.Noop(),
t('operations.add.annotation.vertex'));
});
return behavior;
};
+138 -32
View File
@@ -1,10 +1,9 @@
iD.behavior.DragNode = function(mode) {
var history = mode.history,
size = mode.map.size(),
nudgeInterval,
projection = mode.map.projection;
iD.behavior.DragNode = function(context) {
var nudgeInterval,
wasMidpoint,
cancelled;
function edge(point) {
function edge(point, size) {
var pad = [30, 100, 30, 100];
if (point[0] > size[0] - pad[0]) return [-10, 0];
else if (point[0] < pad[2]) return [10, 0];
@@ -16,43 +15,150 @@ iD.behavior.DragNode = function(mode) {
function startNudge(nudge) {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = window.setInterval(function() {
mode.map.pan(nudge).redraw();
context.pan(nudge);
}, 50);
}
function stopNudge(nudge) {
function stopNudge() {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = null;
}
function annotation(entity) {
return t('operations.move.annotation.' + entity.geometry(mode.history.graph()));
function moveAnnotation(entity) {
return t('operations.move.annotation.' + entity.geometry(context.graph()));
}
return iD.behavior.drag()
.delegate(".node")
.origin(function(entity) {
return projection(entity.loc);
})
.on('start', function() {
history.perform(
function connectAnnotation(datum) {
return t('operations.connect.annotation.' + datum.geometry(context.graph()));
}
function origin(entity) {
return context.projection(entity.loc);
}
function start(entity) {
cancelled = d3.event.sourceEvent.shiftKey;
if (cancelled) return behavior.cancel();
context.history()
.on('undone.drag-node', cancel);
wasMidpoint = entity.type === 'midpoint';
if (wasMidpoint) {
var midpoint = entity;
entity = iD.Node();
context.perform(iD.actions.AddMidpoint(midpoint, entity));
var vertex = context.surface()
.selectAll('.vertex')
.filter(function(d) { return d.id === entity.id; });
behavior.target(vertex.node(), entity);
} else {
context.perform(
iD.actions.Noop());
})
.on('move', function(entity) {
d3.event.sourceEvent.stopPropagation();
}
var nudge = edge(d3.event.point);
if (nudge) startNudge(nudge);
else stopNudge();
var activeIDs = _.pluck(context.graph().parentWays(entity), 'id');
activeIDs.push(entity.id);
history.replace(
iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)),
annotation(entity));
})
.on('end', function(entity) {
stopNudge();
history.replace(
context.surface()
.classed('behavior-drag-node', true)
.selectAll('.node, .way')
.filter(function(d) { return activeIDs.indexOf(d.id) >= 0; })
.classed('active', true);
}
function datum() {
if (d3.event.sourceEvent.altKey) {
return {};
} else {
return d3.event.sourceEvent.target.__data__ || {};
}
}
function move(entity) {
if (cancelled) return;
d3.event.sourceEvent.stopPropagation();
var nudge = edge(d3.event.point, context.map().size());
if (nudge) startNudge(nudge);
else stopNudge();
var loc = context.map().mouseCoordinates();
var d = datum();
if (d.type === 'node' && d.id !== entity.id) {
loc = d.loc;
} else if (d.type === 'way') {
var point = d3.mouse(context.surface().node()),
index = iD.geo.chooseIndex(d, point, context);
if (iD.geo.dist(point, context.projection(index.loc)) < 10) {
loc = index.loc;
}
}
context.replace(iD.actions.MoveNode(entity.id, loc));
}
function end(entity) {
if (cancelled) return;
off();
var d = datum();
if (d.type === 'way') {
var point = d3.mouse(context.surface().node()),
choice = iD.geo.chooseIndex(d, point, context);
if (iD.geo.dist(point, context.projection(choice.loc)) < 10) {
context.replace(
iD.actions.MoveNode(entity.id, choice.loc),
iD.actions.AddVertex(d.id, entity.id, choice.index),
connectAnnotation(d));
return;
}
}
if (d.type === 'node' && d.id !== entity.id) {
context.replace(
iD.actions.Connect([entity.id, d.id]),
connectAnnotation(d));
} else if (wasMidpoint) {
context.replace(
iD.actions.Noop(),
annotation(entity));
});
t('operations.add.annotation.vertex'));
} else {
context.replace(
iD.actions.Noop(),
moveAnnotation(entity));
}
}
function off() {
context.history()
.on('undone.drag_node', null);
context.surface()
.classed('behavior-drag-node', false)
.selectAll('.active')
.classed('active', false);
stopNudge();
}
function cancel() {
off();
behavior.cancel();
}
var behavior = iD.behavior.drag()
.delegate("g.node, g.point, g.midpoint")
.surface(context.surface().node())
.origin(origin)
.on('start', start)
.on('move', move)
.on('end', end);
return behavior;
};
+48 -32
View File
@@ -1,56 +1,75 @@
iD.behavior.Draw = function(map) {
var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'),
iD.behavior.Draw = function(context) {
var event = d3.dispatch('move', 'click', 'clickWay',
'clickNode', 'undo', 'cancel', 'finish'),
keybinding = d3.keybinding('draw'),
down, surface, hover;
hover = iD.behavior.Hover(),
closeTolerance = 4,
tolerance = 12;
function datum() {
if (d3.event.altKey) {
return {};
} else {
return d3.event.target.__data__ || {};
}
if (d3.event.altKey) return {};
else return d3.event.target.__data__ || {};
}
function mousedown() {
down = true;
}
function mouseup() {
down = false;
function point() {
var p = target.node().parentNode;
return touchId !== null ? d3.touches(p).filter(function(p) {
return p.identifier === touchId;
})[0] : d3.mouse(p);
}
var target = d3.select(this),
touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null,
time = +new Date(),
pos = point();
target.on('mousemove.draw', null);
d3.select(window).on('mouseup.draw', function() {
target.on('mousemove.draw', mousemove);
if (iD.geo.dist(pos, point()) < closeTolerance ||
(iD.geo.dist(pos, point()) < tolerance &&
(+new Date() - time) < 500)) {
click();
}
if (target.node() === d3.event.target) {
d3.select(window).on('click.draw', function() {
d3.select(window).on('click.draw', null);
d3.event.stopPropagation();
});
}
});
}
function mousemove() {
if (!down) {
event.move(datum());
}
event.move(datum());
}
function click() {
var d = datum();
if (d.type === 'way') {
var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map);
var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context);
event.clickWay(d, choice.loc, choice.index);
} else if (d.type === 'node') {
event.clickNode(d);
} else if (d.type === 'midpoint') {
event.clickMidpoint(d);
} else {
event.click(map.mouseCoordinates());
event.click(context.map().mouseCoordinates());
}
}
function keydown() {
if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) {
surface.call(hover.off);
context.uninstall(hover);
}
}
function keyup() {
if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) {
surface.call(hover);
context.install(hover);
}
}
@@ -70,8 +89,7 @@ iD.behavior.Draw = function(map) {
}
function draw(selection) {
surface = selection;
hover = iD.behavior.Hover();
context.install(hover);
keybinding
.on('⌫', backspace)
@@ -81,10 +99,7 @@ iD.behavior.Draw = function(map) {
selection
.on('mousedown.draw', mousedown)
.on('mouseup.draw', mouseup)
.on('mousemove.draw', mousemove)
.on('click.draw', click)
.call(hover);
.on('mousemove.draw', mousemove);
d3.select(document)
.call(keybinding)
@@ -95,12 +110,13 @@ iD.behavior.Draw = function(map) {
}
draw.off = function(selection) {
context.uninstall(hover);
selection
.on('mousedown.draw', null)
.on('mouseup.draw', null)
.on('mousemove.draw', null)
.on('click.draw', null)
.call(hover.off);
.on('mousemove.draw', null);
d3.select(window).on('mouseup.draw', null);
d3.select(document)
.call(keybinding.off)
+78 -56
View File
@@ -1,35 +1,58 @@
iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
var map = mode.map,
history = mode.history,
controller = mode.controller,
way = history.graph().entity(wayId),
iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
var way = context.entity(wayId),
isArea = way.geometry() === 'area',
finished = false,
annotation = t((way.isDegenerate() ?
'operations.start.annotation.' :
'operations.continue.annotation.') + way.geometry(history.graph())),
draw = iD.behavior.Draw(map);
'operations.continue.annotation.') + context.geometry(wayId)),
draw = iD.behavior.Draw(context);
var node = iD.Node({loc: map.mouseCoordinates()}),
nodeId = node.id;
var startIndex = typeof index === 'undefined' ? way.nodes.length - 1 : 0,
start = iD.Node({loc: context.graph().entity(way.nodes[startIndex]).loc}),
end = iD.Node({loc: context.map().mouseCoordinates()}),
segment = iD.Way({
nodes: [start.id, end.id],
tags: _.clone(way.tags)
});
history[way.isDegenerate() ? 'replace' : 'perform'](
iD.actions.AddEntity(node),
iD.actions.AddVertex(wayId, node.id, index));
var f = context[way.isDegenerate() ? 'replace' : 'perform'];
if (isArea) {
f(iD.actions.AddEntity(end),
iD.actions.AddVertex(wayId, end.id, index));
} else {
f(iD.actions.AddEntity(start),
iD.actions.AddEntity(end),
iD.actions.AddEntity(segment));
}
function move(datum) {
var loc = map.mouseCoordinates();
var loc = context.map().mouseCoordinates();
if (datum.type === 'node' || datum.type === 'midpoint') {
if (datum.id === end.id || datum.id === segment.id) {
context.surface().selectAll('.way, .node')
.filter(function(d) {
return d.id === end.id || d.id === segment.id;
})
.classed('active', true);
} else if (datum.type === 'node') {
loc = datum.loc;
} else if (datum.type === 'way') {
loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc;
loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc;
}
history.replace(iD.actions.MoveNode(nodeId, loc));
context.replace(iD.actions.MoveNode(end.id, loc));
}
function undone() {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
}
function lineActives(d) {
return d.id === segment.id || d.id === start.id || d.id === end.id;
}
function areaActives(d) {
return d.id === wayId || d.id === end.id;
}
var drawWay = function(surface) {
@@ -37,123 +60,122 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
.on('click', drawWay.add)
.on('clickWay', drawWay.addWay)
.on('clickNode', drawWay.addNode)
.on('clickMidpoint', drawWay.addMidpoint)
.on('undo', history.undo)
.on('undo', context.undo)
.on('cancel', drawWay.cancel)
.on('finish', drawWay.finish);
map.fastEnable(false)
context.map()
.fastEnable(false)
.minzoom(16)
.dblclickEnable(false);
surface.call(draw)
.selectAll('.way, .node')
.filter(function (d) { return d.id === wayId || d.id === nodeId; })
.filter(isArea ? areaActives : lineActives)
.classed('active', true);
history.on('undone.draw', undone);
context.history()
.on('undone.draw', undone);
};
drawWay.off = function(surface) {
if (!finished)
history.pop();
context.pop();
map.fastEnable(true)
context.map()
.fastEnable(true)
.minzoom(0)
.tail(false);
window.setTimeout(function() {
map.dblclickEnable(true);
context.map().dblclickEnable(true);
}, 1000);
surface.call(draw.off)
.selectAll('.way, .node')
.classed('active', false);
history.on('undone.draw', null);
context.history()
.on('undone.draw', null);
};
function ReplaceTemporaryNode(newNode) {
return function(graph) {
return graph
.replace(way.removeNode(nodeId).addNode(newNode.id, index))
.remove(node);
}
if (isArea) {
return graph
.replace(way.removeNode(end.id).addNode(newNode.id, index))
.remove(end);
} else {
return graph
.replace(graph.entity(wayId).addNode(newNode.id, index))
.remove(end)
.remove(segment)
.remove(start);
}
};
}
// Accept the current position of the temporary node and continue drawing.
drawWay.add = function(loc) {
var newNode = iD.Node({loc: loc});
history.replace(
context.replace(
iD.actions.AddEntity(newNode),
ReplaceTemporaryNode(newNode),
annotation);
finished = true;
controller.enter(mode);
context.enter(mode);
};
// Connect the way to an existing way.
drawWay.addWay = function(way, loc, wayIndex) {
var newNode = iD.Node({loc: loc});
history.perform(
context.perform(
iD.actions.AddEntity(newNode),
iD.actions.AddVertex(way.id, newNode.id, wayIndex),
ReplaceTemporaryNode(newNode),
annotation);
finished = true;
controller.enter(mode);
context.enter(mode);
};
// Connect the way to an existing node and continue drawing.
drawWay.addNode = function(node) {
history.perform(
context.perform(
ReplaceTemporaryNode(node),
annotation);
finished = true;
controller.enter(mode);
};
// Add a midpoint, connect the way to it, and continue drawing.
drawWay.addMidpoint = function(midpoint) {
var node = iD.Node();
history.perform(
iD.actions.AddMidpoint(midpoint, node),
ReplaceTemporaryNode(node),
annotation);
finished = true;
controller.enter(mode);
context.enter(mode);
};
// Finish the draw operation, removing the temporary node. If the way has enough
// nodes to be valid, it's selected. Otherwise, return to browse mode.
drawWay.finish = function() {
history.pop();
context.pop();
finished = true;
var way = history.graph().entity(wayId);
var way = context.entity(wayId);
if (way) {
controller.enter(iD.modes.Select([way.id], true));
context.enter(iD.modes.Select(context, [way.id], true));
} else {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
}
};
// Cancel the draw operation and return to browse, deleting everything drawn.
drawWay.cancel = function() {
history.perform(
context.perform(
d3.functor(baseGraph),
t('operations.cancel_draw.annotation'));
finished = true;
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
};
return d3.rebind(drawWay, event, 'on');
return drawWay;
};
+18 -19
View File
@@ -1,4 +1,4 @@
iD.behavior.Hash = function(controller, map) {
iD.behavior.Hash = function(context) {
var s0 = null, // cached location.hash
lat = 90 - 1e-8; // allowable latitude range
@@ -27,13 +27,13 @@ iD.behavior.Hash = function(controller, map) {
};
var move = _.throttle(function() {
var s1 = formatter(map);
var s1 = formatter(context.map());
if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map!
}, 100);
}, 500);
function hashchange() {
if (location.hash === s0) return; // ignore spurious hashchange events
if (parser(map, (s0 = location.hash).substring(1))) {
if (parser(context.map(), (s0 = location.hash).substring(1))) {
move(); // replace bogus hash
}
}
@@ -42,40 +42,39 @@ iD.behavior.Hash = function(controller, map) {
// do so before any features are loaded. thus wait for the feature to
// be loaded and then select
function willselect(id) {
map.on('drawn.hash', function() {
var entity = map.history().graph().entity(id);
if (entity === undefined) return;
else selectoff();
controller.enter(iD.modes.Select([entity.id]));
map.on('drawn.hash', null);
context.map().on('drawn.hash', function() {
if (!context.entity(id)) return;
selectoff();
context.enter(iD.modes.Select(context, [id]));
});
controller.on('enter.hash', function() {
if (controller.mode.id !== 'browse') selectoff();
context.on('enter.hash', function() {
if (context.mode().id !== 'browse') selectoff();
});
}
function selectoff() {
map.on('drawn.hash', null);
context.map().on('drawn.hash', null);
}
function hash() {
map.on('move.hash', move);
context.map()
.on('move.hash', move);
d3.select(window)
.on('hashchange.hash', hashchange);
if (location.hash) {
var q = iD.util.stringQs(location.hash.substring(1));
if (q.id) {
willselect(q.id);
}
if (q.id) willselect(q.id);
hashchange();
hash.hadHash = true;
if (q.map) hash.hadHash = true;
}
}
hash.off = function() {
map.on('move.hash', null);
context.map()
.on('move.hash', null);
d3.select(window)
.on('hashchange.hash', null);
+7 -5
View File
@@ -7,20 +7,22 @@
Only one of these elements can have the :hover pseudo-class, but all of them will
have the .hover class.
*/
iD.behavior.Hover = function () {
iD.behavior.Hover = function() {
var hover = function(selection) {
selection.classed('behavior-hover', true);
selection.on('mouseover.hover', function () {
function mouseover() {
var datum = d3.event.target.__data__;
if (datum) {
selection.selectAll('*')
.filter(function (d) { return d === datum; })
.filter(function(d) { return d === datum; })
.classed('hover', true);
}
});
}
selection.on('mouseout.hover', function () {
selection.on('mouseover.hover', mouseover);
selection.on('mouseout.hover', function() {
selection.selectAll('.hover')
.classed('hover', false);
});
+71
View File
@@ -0,0 +1,71 @@
iD.behavior.Lasso = function(context) {
var behavior = function(selection) {
var timeout = null,
// the position of the first mousedown
pos = null,
lasso;
function mousedown() {
if (d3.event.shiftKey === true) {
pos = [d3.event.clientX, d3.event.clientY];
lasso = iD.ui.Lasso().a(d3.mouse(context.surface().node()));
context.surface().call(lasso);
selection
.on('mousemove.lasso', mousemove)
.on('mouseup.lasso', mouseup);
d3.event.stopPropagation();
d3.event.preventDefault();
}
}
function mousemove() {
lasso.b(d3.mouse(context.surface().node()));
}
function normalize(a, b) {
return [
[Math.min(a[0], b[0]), Math.min(a[1], b[1])],
[Math.max(a[0], b[0]), Math.max(a[1], b[1])]];
}
function mouseup() {
var extent = iD.geo.Extent(
normalize(context.projection.invert(lasso.a()),
context.projection.invert(lasso.b())));
lasso.close();
selection
.on('mousemove.lasso', null)
.on('mouseup.lasso', null);
if (d3.event.clientX !== pos[0] || d3.event.clientY !== pos[1]) {
var selected = context.graph().intersects(extent).filter(function (entity) {
return entity.type === 'node';
});
if (selected.length) {
context.enter(iD.modes.Select(context, _.pluck(selected, 'id')));
}
}
}
selection
.on('mousedown.lasso', mousedown);
};
behavior.off = function(selection) {
selection.on('mousedown.lasso', null);
};
return behavior;
};
+25 -12
View File
@@ -1,16 +1,29 @@
iD.behavior.Select = function(mode) {
var controller = mode.controller;
function click() {
var datum = d3.select(d3.event.target).datum();
if (datum instanceof iD.Entity) {
controller.enter(iD.modes.Select([datum.id]));
} else {
controller.enter(iD.modes.Browse());
}
}
iD.behavior.Select = function(context) {
var behavior = function(selection) {
function click() {
var datum = d3.event.target.__data__;
if (!(datum instanceof iD.Entity)) {
if (!d3.event.shiftKey)
context.enter(iD.modes.Browse(context));
} else if (!d3.event.shiftKey) {
// Avoid re-entering Select mode with same entity.
if (context.selection().length !== 1 || context.selection()[0] !== datum.id) {
context.enter(iD.modes.Select(context, [datum.id]));
} else {
context.mode().reselect();
}
} else if (context.selection().indexOf(datum.id) >= 0) {
var selection = _.without(context.selection(), datum.id);
context.enter(selection.length ?
iD.modes.Select(context, selection) :
iD.modes.Browse(context));
} else {
context.enter(iD.modes.Select(context, context.selection().concat([datum.id])));
}
}
selection.on('click.select', click);
};
+81 -73
View File
@@ -1,4 +1,4 @@
iD.Connection = function() {
iD.Connection = function(context) {
var event = d3.dispatch('auth', 'load'),
url = 'http://www.openstreetmap.org',
@@ -9,7 +9,13 @@ iD.Connection = function() {
keys,
inflight = {},
loadedTiles = {},
oauth = iD.OAuth().url(url);
oauth = iD.OAuth(context).url(url),
ndStr = 'nd',
tagStr = 'tag',
memberStr = 'member',
nodeStr = 'node',
wayStr = 'way',
relationStr = 'relation';
function changesetUrl(changesetId) {
return url + '/browse/changeset/' + changesetId;
@@ -36,92 +42,99 @@ iD.Connection = function() {
}
function getNodes(obj) {
var nelems = obj.getElementsByTagName('nd'), nodes = new Array(nelems.length);
for (var i = 0, l = nelems.length; i < l; i++) {
nodes[i] = 'n' + nelems[i].attributes.ref.nodeValue;
var elems = obj.getElementsByTagName(ndStr),
nodes = new Array(elems.length);
for (var i = 0, l = elems.length; i < l; i++) {
nodes[i] = 'n' + elems[i].attributes.ref.nodeValue;
}
return nodes;
}
function getTags(obj) {
var tags = {}, tagelems = obj.getElementsByTagName('tag');
for (var i = 0, l = tagelems.length; i < l; i++) {
var item = tagelems[i];
tags[item.attributes.k.nodeValue] = item.attributes.v.nodeValue;
var elems = obj.getElementsByTagName(tagStr),
tags = {};
for (var i = 0, l = elems.length; i < l; i++) {
var attrs = elems[i].attributes;
tags[attrs.k.nodeValue] = attrs.v.nodeValue;
}
return tags;
}
function getMembers(obj) {
var elems = obj.getElementsByTagName('member'),
var elems = obj.getElementsByTagName(memberStr),
members = new Array(elems.length);
for (var i = 0, l = elems.length; i < l; i++) {
var attrs = elems[i].attributes;
members[i] = {
id: elems[i].attributes.type.nodeValue[0] + elems[i].attributes.ref.nodeValue,
type: elems[i].attributes.type.nodeValue,
role: elems[i].attributes.role.nodeValue
id: attrs.type.nodeValue[0] + attrs.ref.nodeValue,
type: attrs.type.nodeValue,
role: attrs.role.nodeValue
};
}
return members;
}
function nodeData(obj) {
var o = { type: 'node', tags: getTags(obj) };
for (var i = 0, l = obj.attributes.length; i < l; i++) {
o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue;
}
if (o.lon && o.lat) {
o.loc = [parseFloat(o.lon), parseFloat(o.lat)];
delete o.lon; delete o.lat;
}
o.id = iD.Entity.id.fromOSM('node', o.id);
return new iD.Node(o);
}
var parsers = {
node: function nodeData(obj) {
var attrs = obj.attributes;
return new iD.Node({
id: iD.Entity.id.fromOSM(nodeStr, attrs.id.nodeValue),
loc: [parseFloat(attrs.lon.nodeValue), parseFloat(attrs.lat.nodeValue)],
version: attrs.version.nodeValue,
changeset: attrs.changeset.nodeValue,
user: attrs.user.nodeValue,
uid: attrs.uid.nodeValue,
visible: attrs.visible.nodeValue,
timestamp: attrs.timestamp.nodeValue,
tags: getTags(obj)
});
},
function wayData(obj) {
var o = { type: 'way', nodes: getNodes(obj),
tags: getTags(obj)
};
for (var i = 0, l = obj.attributes.length; i < l; i++) {
o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue;
}
o.id = iD.Entity.id.fromOSM('way', o.id);
return new iD.Way(o);
}
way: function wayData(obj) {
var attrs = obj.attributes;
return new iD.Way({
id: iD.Entity.id.fromOSM(wayStr, attrs.id.nodeValue),
version: attrs.version.nodeValue,
changeset: attrs.changeset.nodeValue,
user: attrs.user.nodeValue,
uid: attrs.uid.nodeValue,
visible: attrs.visible.nodeValue,
timestamp: attrs.timestamp.nodeValue,
tags: getTags(obj),
nodes: getNodes(obj)
});
},
function relationData(obj) {
var o = {
type: 'relation', members: getMembers(obj),
tags: getTags(obj)
};
for (var i = 0, l = obj.attributes.length; i < l; i++) {
o[obj.attributes[i].nodeName] = obj.attributes[i].nodeValue;
relation: function relationData(obj) {
var attrs = obj.attributes;
return new iD.Relation({
id: iD.Entity.id.fromOSM(relationStr, attrs.id.nodeValue),
version: attrs.version.nodeValue,
changeset: attrs.changeset.nodeValue,
user: attrs.user.nodeValue,
uid: attrs.uid.nodeValue,
visible: attrs.visible.nodeValue,
timestamp: attrs.timestamp.nodeValue,
tags: getTags(obj),
members: getMembers(obj)
});
}
o.id = iD.Entity.id.fromOSM('relation', o.id);
return new iD.Relation(o);
}
};
function parse(dom) {
if (!dom || !dom.childNodes) return new Error('Bad request');
var root = dom.childNodes[0];
var entities = {};
var root = dom.childNodes[0],
children = root.childNodes,
entities = {};
var i, o, l;
for (i = 0, l = root.childNodes.length; i < l; i++) {
switch(root.childNodes[i].nodeName) {
case 'node':
o = nodeData(root.childNodes[i]);
entities[o.id] = o;
break;
case 'way':
o = wayData(root.childNodes[i]);
entities[o.id] = o;
break;
case 'relation':
o = relationData(root.childNodes[i]);
entities[o.id] = o;
break;
for (i = 0, l = children.length; i < l; i++) {
var child = children[i],
parser = parsers[child.nodeName];
if (parser) {
o = parser(child);
entities[o.id] = o;
}
}
@@ -187,21 +200,21 @@ iD.Connection = function() {
content: JXON.stringify(connection.changesetJXON({
imagery_used: imagery_used.join(';'),
comment: comment,
created_by: 'iD ' + (version || '')
created_by: 'iD ' + iD.version
}))
}, function (err, changeset_id) {
}, function(err, changeset_id) {
if (err) return callback(err);
oauth.xhr({
method: 'POST',
path: '/api/0.6/changeset/' + changeset_id + '/upload',
options: { header: { 'Content-Type': 'text/xml' } },
content: JXON.stringify(connection.osmChangeJXON(user.id, changeset_id, changes))
}, function (err) {
}, function(err) {
if (err) return callback(err);
oauth.xhr({
method: 'PUT',
path: '/api/0.6/changeset/' + changeset_id + '/close'
}, function (err) {
}, function(err) {
callback(err, changeset_id);
});
});
@@ -210,13 +223,14 @@ iD.Connection = function() {
function userDetails(callback) {
function done(err, user_details) {
if (err) return callback(err);
var u = user_details.getElementsByTagName('user')[0],
img = u.getElementsByTagName('img'),
image_url = '';
if (img && img[0].getAttribute('href')) {
image_url = img[0].getAttribute('href');
}
callback(connection.user({
callback(undefined, connection.user({
display_name: u.attributes.display_name.nodeValue,
image_url: image_url,
id: u.attributes.id.nodeValue
@@ -329,12 +343,6 @@ iD.Connection = function() {
return oauth.authenticate(done);
};
connection.version = function(_) {
if (!arguments.length) return version;
version = _;
return connection;
};
connection.bboxFromAPI = bboxFromAPI;
connection.changesetUrl = changesetUrl;
connection.loadFromURL = loadFromURL;
-23
View File
@@ -1,23 +0,0 @@
// A controller holds a single action at a time and calls `.enter` and `.exit`
// to bind and unbind actions.
iD.Controller = function(map, history) {
var event = d3.dispatch('enter', 'exit');
var controller = { mode: null };
controller.enter = function(mode) {
mode.controller = controller;
mode.history = history;
mode.map = map;
if (controller.mode) {
controller.mode.exit();
event.exit(controller.mode);
}
mode.enter();
controller.mode = mode;
event.enter(mode);
};
return d3.rebind(controller, event, 'on');
};
+123
View File
@@ -0,0 +1,123 @@
/*
iD.Difference represents the difference between two graphs.
It knows how to calculate the set of entities that were
created, modified, or deleted, and also contains the logic
for recursively extending a difference to the complete set
of entities that will require a redraw, taking into account
child and parent relationships.
*/
iD.Difference = function(base, head) {
var changes = {}, length = 0;
_.each(head.entities, function(h, id) {
var b = base.entities[id];
if (h !== b) {
changes[id] = {base: b, head: h};
length++;
}
});
_.each(base.entities, function(b, id) {
var h = head.entities[id];
if (!changes[id] && h !== b) {
changes[id] = {base: b, head: h};
length++;
}
});
var difference = {};
difference.length = function() {
return length;
};
difference.changes = function() {
return changes;
};
difference.extantIDs = function() {
var result = [];
_.each(changes, function(change, id) {
if (change.head) result.push(id);
});
return result;
};
difference.modified = function() {
var result = [];
_.each(changes, function(change) {
if (change.base && change.head) result.push(change.head);
});
return result;
};
difference.created = function() {
var result = [];
_.each(changes, function(change) {
if (!change.base && change.head) result.push(change.head);
});
return result;
};
difference.deleted = function() {
var result = [];
_.each(changes, function(change) {
if (change.base && !change.head) result.push(change.base);
});
return result;
};
difference.complete = function(extent) {
var result = {}, id, change;
function addParents(parents) {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (parent.id in result)
continue;
result[parent.id] = parent;
addParents(head.parentRelations(parent));
}
}
for (id in changes) {
change = changes[id];
var h = change.head,
b = change.base,
entity = h || b;
if (extent &&
(!h || !h.intersects(extent, head)) &&
(!b || !b.intersects(extent, base)))
continue;
result[id] = h;
if (entity.type === 'way') {
var nh = h ? h.nodes : [],
nb = b ? b.nodes : [],
diff;
diff = _.difference(nh, nb);
for (var i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
}
diff = _.difference(nb, nh);
for (i = 0; i < diff.length; i++) {
result[diff[i]] = head.entity(diff[i]);
}
}
addParents(head.parentWays(entity));
addParents(head.parentRelations(entity));
}
return result;
};
return difference;
};
+35 -13
View File
@@ -9,22 +9,22 @@ iD.Entity = function(attrs) {
return (new iD.Entity()).initialize(arguments);
};
iD.Entity.id = function (type) {
iD.Entity.id = function(type) {
return iD.Entity.id.fromOSM(type, iD.Entity.id.next[type]--);
};
iD.Entity.id.next = {node: -1, way: -1, relation: -1};
iD.Entity.id.fromOSM = function (type, id) {
iD.Entity.id.fromOSM = function(type, id) {
return type[0] + id;
};
iD.Entity.id.toOSM = function (id) {
iD.Entity.id.toOSM = function(id) {
return id.slice(1);
};
// A function suitable for use as the second argument to d3.selection#data().
iD.Entity.key = function (entity) {
iD.Entity.key = function(entity) {
return entity.id;
};
@@ -43,7 +43,6 @@ iD.Entity.prototype = {
if (!this.id && this.type) {
this.id = iD.Entity.id(this.type);
this._updated = true;
}
if (iD.debug) {
@@ -63,15 +62,21 @@ iD.Entity.prototype = {
},
update: function(attrs) {
return iD.Entity(this, attrs, {_updated: true});
return iD.Entity(this, attrs);
},
created: function() {
return this._updated && this.osmId().charAt(0) === '-';
},
modified: function() {
return this._updated && this.osmId().charAt(0) !== '-';
mergeTags: function(tags) {
var merged = _.clone(this.tags);
for (var k in tags) {
var t1 = merged[k],
t2 = tags[k];
if (t1 && t1 !== t2) {
merged[k] = t1 + "; " + t2;
} else {
merged[k] = t2;
}
}
return this.update({tags: merged});
},
intersects: function(extent, resolver) {
@@ -79,7 +84,7 @@ iD.Entity.prototype = {
},
hasInterestingTags: function() {
return _.keys(this.tags).some(function (key) {
return _.keys(this.tags).some(function(key) {
return key != "attribution" &&
key != "created_by" &&
key != "source" &&
@@ -88,6 +93,23 @@ iD.Entity.prototype = {
});
},
deprecatedTags: function() {
var tags = _.pairs(this.tags);
var deprecated = {};
iD.data.deprecated.forEach(function(d) {
var match = _.pairs(d.old)[0];
tags.forEach(function(t) {
if (t[0] == match[0] &&
(t[1] == match[1] || match[1] == '*')) {
deprecated[t[0]] = t[1];
}
});
});
return deprecated;
},
friendlyName: function() {
// Generate a string such as 'river' or 'Fred's House' for an entity.
if (!this.tags || !Object.keys(this.tags).length) { return ''; }
+45 -56
View File
@@ -93,6 +93,7 @@ iD.Graph.prototype = {
rebase: function(entities) {
var base = this.base(),
i, k, child, id, keys;
// Merging of data only needed if graph is the base graph
if (!this.inherited) {
for (i in entities) {
@@ -110,7 +111,7 @@ iD.Graph.prototype = {
if (base.parentWays[child]) {
for (k = 0; k < base.parentWays[child].length; k++) {
id = base.parentWays[child][k];
if (this.entity(id) && !_.contains(this._parentWays[child], id)) {
if (!this.entities.hasOwnProperty(id) && !_.contains(this._parentWays[child], id)) {
this._parentWays[child].push(id);
}
}
@@ -123,7 +124,7 @@ iD.Graph.prototype = {
if (base.parentRels[child]) {
for (k = 0; k < base.parentRels[child].length; k++) {
id = base.parentRels[child][k];
if (this.entity(id) && !_.contains(this._parentRels[child], id)) {
if (!this.entities.hasOwnProperty(id) && !_.contains(this._parentRels[child], id)) {
this._parentRels[child].push(id);
}
}
@@ -189,14 +190,17 @@ iD.Graph.prototype = {
},
replace: function(entity) {
return this.update(function () {
if (this.entities[entity.id] === entity)
return this;
return this.update(function() {
this._updateCalculated(this.entities[entity.id], entity);
this.entities[entity.id] = entity;
});
},
remove: function(entity) {
return this.update(function () {
return this.update(function() {
this._updateCalculated(entity, undefined);
this.entities[entity.id] = undefined;
});
@@ -227,70 +231,55 @@ iD.Graph.prototype = {
var items = [];
for (var i in this.entities) {
var entity = this.entities[i];
if (entity && entity.intersects(extent, this)) {
if (entity && this.hasAllChildren(entity) && entity.intersects(extent, this)) {
items.push(entity);
}
}
return items;
},
difference: function (graph) {
function diff(a, b) {
var result = [],
keys = Object.keys(a.entities),
entity, oldentity, id, i;
for (i = 0; i < keys.length; i++) {
id = keys[i];
entity = a.entities[id];
oldentity = b.entities[id];
if (entity !== oldentity) {
// maybe adding affected children better belongs in renderer/map.js?
if (entity && entity.type === 'way' &&
oldentity && oldentity.type === 'way') {
result = result
.concat(_.difference(entity.nodes, oldentity.nodes))
.concat(_.difference(oldentity.nodes, entity.nodes));
} else if (entity && entity.type === 'way') {
result = result.concat(entity.nodes);
} else if (oldentity && oldentity.type === 'way') {
result = result.concat(oldentity.nodes);
}
result.push(id);
hasAllChildren: function(entity) {
// we're only checking changed entities, since we assume fetched data
// must have all children present
if (this.entities.hasOwnProperty(entity.id)) {
if (entity.type === 'way') {
for (i = 0; i < entity.nodes.length; i++) {
if (!this.entities[entity.nodes[i]]) return false;
}
} else if (entity.type === 'relation') {
for (i = 0; i < entity.members.length; i++) {
if (!this.entities[entity.members[i].id]) return false;
}
}
return result;
}
return _.unique(diff(this, graph).concat(diff(graph, this)).sort());
return true;
},
modified: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && base[id]) result.push(id);
});
return result;
},
// Obliterates any existing entities
load: function(entities) {
created: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (entity && !base[id]) result.push(id);
});
return result;
},
var base = this.base(),
i, entity, prefix;
this.entities = Object.create(base.entities);
deleted: function() {
var result = [], base = this.base().entities;
_.each(this.entities, function(entity, id) {
if (!entity && base[id]) result.push(id);
});
return result;
for (i in entities) {
entity = entities[i];
prefix = i[0];
if (entity === 'undefined') {
this.entities[i] = undefined;
} else if (prefix == 'n') {
this.entities[i] = new iD.Node(entity);
} else if (prefix == 'w') {
this.entities[i] = new iD.Way(entity);
} else if (prefix == 'r') {
this.entities[i] = new iD.Relation(entity);
}
this._updateCalculated(base.entities[i], this.entities[i]);
}
return this;
}
};
+245
View File
@@ -0,0 +1,245 @@
iD.History = function(context) {
var stack, index,
imagery_used = 'Bing',
dispatch = d3.dispatch('change', 'undone', 'redone'),
lock = false;
function perform(actions) {
actions = Array.prototype.slice.call(actions);
var annotation;
if (!_.isFunction(_.last(actions))) {
annotation = actions.pop();
}
var graph = stack[index].graph;
for (var i = 0; i < actions.length; i++) {
graph = actions[i](graph);
}
return {
graph: graph,
annotation: annotation,
imagery_used: imagery_used
};
}
function change(previous) {
var difference = iD.Difference(previous, history.graph());
dispatch.change(difference);
return difference;
}
function getKey(n) {
return 'iD_' + window.location.origin + '_' + n;
}
var history = {
graph: function() {
return stack[index].graph;
},
merge: function(entities) {
for (var i = 0; i < stack.length; i++) {
stack[i].graph.rebase(entities);
}
dispatch.change();
},
perform: function() {
var previous = stack[index].graph;
stack = stack.slice(0, index + 1);
stack.push(perform(arguments));
index++;
return change(previous);
},
replace: function() {
var previous = stack[index].graph;
// assert(index == stack.length - 1)
stack[index] = perform(arguments);
return change(previous);
},
pop: function() {
var previous = stack[index].graph;
if (index > 0) {
index--;
stack.pop();
return change(previous);
}
},
undo: function() {
var previous = stack[index].graph;
// Pop to the first annotated state.
while (index > 0) {
if (stack[index].annotation) break;
index--;
}
// Pop to the next annotated state.
while (index > 0) {
index--;
if (stack[index].annotation) break;
}
dispatch.undone();
return change(previous);
},
redo: function() {
var previous = stack[index].graph;
while (index < stack.length - 1) {
index++;
if (stack[index].annotation) break;
}
dispatch.redone();
return change(previous);
},
undoAnnotation: function() {
var i = index;
while (i >= 0) {
if (stack[i].annotation) return stack[i].annotation;
i--;
}
},
redoAnnotation: function() {
var i = index + 1;
while (i <= stack.length - 1) {
if (stack[i].annotation) return stack[i].annotation;
i++;
}
},
difference: function() {
var base = stack[0].graph,
head = stack[index].graph;
return iD.Difference(base, head);
},
changes: function() {
var difference = history.difference();
function discardTags(entity) {
if (_.isEmpty(entity.tags)) {
return entity;
} else {
return entity.update({
tags: _.omit(entity.tags, iD.data.discarded)
});
}
}
return {
modified: difference.modified().map(discardTags),
created: difference.created().map(discardTags),
deleted: difference.deleted()
};
},
hasChanges: function() {
return this.difference().length() > 0;
},
numChanges: function() {
return this.difference().length();
},
imagery_used: function(source) {
if (source) imagery_used = source;
else return _.without(
_.unique(_.pluck(stack.slice(1, index + 1), 'imagery_used')),
undefined, 'Custom');
},
reset: function() {
stack = [{graph: iD.Graph()}];
index = 0;
dispatch.change();
},
save: function() {
if (!lock) return;
context.storage(getKey('lock'), null);
if (stack.length <= 1) return;
var json = JSON.stringify(stack.map(function(i) {
return {
annotation: i.annotation,
imagery_used: i.imagery_used,
entities: i.graph.entities
};
}), function includeUndefined(key, value) {
if (typeof value === 'undefined') return 'undefined';
return value;
});
context.storage(getKey('history'), json);
context.storage(getKey('nextIDs'), JSON.stringify(iD.Entity.id.next));
context.storage(getKey('index'), index);
},
clearSaved: function() {
if (!lock) return;
context.storage(getKey('history'), null);
context.storage(getKey('nextIDs'), null);
context.storage(getKey('index'), null);
},
lock: function() {
if (context.storage(getKey('lock'))) return false;
context.storage(getKey('lock'), true);
lock = true;
return lock;
},
restorableChanges: function() {
return lock && !!context.storage(getKey('history'));
},
load: function() {
if (!lock) return;
var json = context.storage(getKey('history')),
nextIDs = context.storage(getKey('nextIDs')),
index_ = context.storage(getKey('index'));
if (!json) return;
if (nextIDs) iD.Entity.id.next = JSON.parse(nextIDs);
if (index_ !== null) index = parseInt(index_, 10);
context.storage(getKey('history', null));
context.storage(getKey('nextIDs', null));
context.storage(getKey('index', null));
stack = JSON.parse(json).map(function(d, i) {
d.graph = iD.Graph(stack[0].graph).load(d.entities);
return d;
});
stack[0].graph.inherited = false;
dispatch.change();
},
_getKey: getKey
};
history.reset();
return d3.rebind(history, dispatch, 'on');
};
+2 -2
View File
@@ -12,7 +12,7 @@ _.extend(iD.Node.prototype, {
type: "node",
extent: function() {
return iD.geo.Extent(this.loc);
return new iD.geo.Extent(this.loc);
},
geometry: function(graph) {
@@ -47,6 +47,6 @@ _.extend(iD.Node.prototype, {
type: 'Point',
coordinates: this.loc
}
}
};
}
});
@@ -14,7 +14,7 @@ _.extend(iD.Relation.prototype, {
extent: function(resolver) {
return resolver.transient(this, 'extent', function() {
return this.members.reduce(function (extent, member) {
return this.members.reduce(function(extent, member) {
member = resolver.entity(member.id);
if (member) {
return extent.extend(member.extent(resolver));
@@ -26,7 +26,7 @@ _.extend(iD.Relation.prototype, {
},
geometry: function() {
return 'relation';
return this.isMultipolygon() ? 'area' : 'relation';
},
// Return the first member with the given role. A copy of the member object
@@ -49,6 +49,16 @@ _.extend(iD.Relation.prototype, {
}
},
// Return the first member with the given id and role. A copy of the member object
// is returned, extended with an 'index' property whose value is the member index.
memberByIdAndRole: function(id, role) {
for (var i = 0; i < this.members.length; i++) {
if (this.members[i].id === id && this.members[i].role === role) {
return _.extend({}, this.members[i], {index: i});
}
}
},
addMember: function(member, index) {
var members = this.members.slice();
members.splice(index === undefined ? members.length : index, 0, member);
@@ -66,6 +76,28 @@ _.extend(iD.Relation.prototype, {
return this.update({members: members});
},
// Wherever a member appears with id `needle.id`, replace it with a member
// with id `replacement.id`, type `replacement.type`, and the original role,
// unless a member already exists with that id and role. Return an updated
// relation.
replaceMember: function(needle, replacement) {
if (!this.memberById(needle.id))
return this;
var members = [];
for (var i = 0; i < this.members.length; i++) {
var member = this.members[i];
if (member.id !== needle.id) {
members.push(member);
} else if (!this.memberByIdAndRole(replacement.id, member.role)) {
members.push({id: replacement.id, type: replacement.type, role: member.role});
}
}
return this.update({members: members});
},
asJXON: function(changeset_id) {
var r = {
relation: {
@@ -83,6 +115,31 @@ _.extend(iD.Relation.prototype, {
return r;
},
asGeoJSON: function(resolver) {
if (this.isMultipolygon()) {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'MultiPolygon',
coordinates: this.multipolygon(resolver)
}
};
} else {
return {
type: 'FeatureCollection',
properties: this.tags,
features: this.members.map(function(member) {
return _.extend({role: member.role}, resolver.entity(member.id).asGeoJSON(resolver));
})
};
}
},
isMultipolygon: function() {
return this.tags.type === 'multipolygon';
},
isRestriction: function() {
return !!(this.tags.type && this.tags.type.match(/^restriction:?/));
},
@@ -99,8 +156,8 @@ _.extend(iD.Relation.prototype, {
//
multipolygon: function(resolver) {
var members = this.members
.filter(function (m) { return m.type === 'way' && resolver.entity(m.id); })
.map(function (m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; });
.filter(function(m) { return m.type === 'way' && resolver.entity(m.id); })
.map(function(m) { return { role: m.role || 'outer', id: m.id, nodes: resolver.childNodes(resolver.entity(m.id)) }; });
function join(ways) {
var joined = [], current, first, last, i, how, what;
@@ -145,30 +202,28 @@ _.extend(iD.Relation.prototype, {
}
}
return joined;
return joined.map(function(nodes) { return _.pluck(nodes, 'loc'); });
}
function findOuter(inner) {
var o, outer;
inner = _.pluck(inner, 'loc');
for (o = 0; o < outers.length; o++) {
outer = _.pluck(outers[o], 'loc');
outer = outers[o];
if (iD.geo.polygonContainsPolygon(outer, inner))
return o;
}
for (o = 0; o < outers.length; o++) {
outer = _.pluck(outers[o], 'loc');
outer = outers[o];
if (iD.geo.polygonIntersectsPolygon(outer, inner))
return o;
}
}
var outers = join(members.filter(function (m) { return m.role === 'outer'; })),
inners = join(members.filter(function (m) { return m.role === 'inner'; })),
result = outers.map(function (o) { return [o]; });
var outers = join(members.filter(function(m) { return m.role === 'outer'; })),
inners = join(members.filter(function(m) { return m.role === 'inner'; })),
result = outers.map(function(o) { return [o]; });
for (var i = 0; i < inners.length; i++) {
var o = findOuter(inners[i]);
+45 -14
View File
@@ -14,7 +14,7 @@ _.extend(iD.Way.prototype, {
extent: function(resolver) {
return resolver.transient(this, 'extent', function() {
return this.nodes.reduce(function (extent, id) {
return this.nodes.reduce(function(extent, id) {
return extent.extend(resolver.entity(id).extent(resolver));
}, iD.geo.Extent());
});
@@ -47,11 +47,14 @@ _.extend(iD.Way.prototype, {
// - doesn't have area=no
// - doesn't have highway tag
isArea: function() {
return this.tags.area === 'yes' ||
(this.isClosed() &&
this.tags.area !== 'no' &&
!this.tags.highway &&
!this.tags.barrier);
if (this.tags.area === 'yes')
return true;
if (!this.isClosed() || this.tags.area === 'no')
return false;
for (var key in this.tags)
if (key in iD.Way.areaKeys)
return true;
return false;
},
isDegenerate: function() {
@@ -74,6 +77,19 @@ _.extend(iD.Way.prototype, {
return this.update({nodes: nodes});
},
replaceNode: function(needle, replacement) {
if (this.nodes.indexOf(needle) < 0)
return this;
var nodes = this.nodes.slice();
for (var i = 0; i < nodes.length; i++) {
if (nodes[i] === needle) {
nodes[i] = replacement;
}
}
return this.update({nodes: nodes});
},
removeNode: function(id) {
var nodes = _.without(this.nodes, id);
@@ -103,13 +119,28 @@ _.extend(iD.Way.prototype, {
},
asGeoJSON: function(resolver) {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'LineString',
coordinates: _.pluck(resolver.childNodes(this), 'loc')
}
};
if (this.isArea()) {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'Polygon',
coordinates: [_.pluck(resolver.childNodes(this), 'loc')]
}
};
} else {
return {
type: 'Feature',
properties: this.tags,
geometry: {
type: 'LineString',
coordinates: _.pluck(resolver.childNodes(this), 'loc')
}
};
}
}
});
iD.Way.areaKeys = iD.util.trueObj(['area', 'building', 'leisure', 'tourism', 'ruins',
'historic', 'landuse', 'military', 'natural', 'amenity', 'shop', 'man_made',
'public_transport']);
+8 -7
View File
@@ -9,16 +9,17 @@ iD.geo.interp = function(p1, p2, t) {
p1[1] + (p2[1] - p1[1]) * t];
};
// http://jsperf.com/id-dist-optimization
iD.geo.dist = function(a, b) {
return Math.sqrt(Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2));
var x = a[0] - b[0], y = a[1] - b[1];
return Math.sqrt((x * x) + (y * y));
};
iD.geo.chooseIndex = function(way, point, map) {
iD.geo.chooseIndex = function(way, point, context) {
var dist = iD.geo.dist,
graph = map.history().graph(),
graph = context.graph(),
nodes = graph.childNodes(way),
projNodes = nodes.map(function(n) { return map.projection(n.loc); });
projNodes = nodes.map(function(n) { return context.projection(n.loc); });
for (var i = 0, changes = []; i < projNodes.length - 1; i++) {
changes[i] =
@@ -63,13 +64,13 @@ iD.geo.pointInPolygon = function(point, polygon) {
};
iD.geo.polygonContainsPolygon = function(outer, inner) {
return _.every(inner, function (point) {
return _.every(inner, function(point) {
return iD.geo.pointInPolygon(point, outer);
});
};
iD.geo.polygonIntersectsPolygon = function(outer, inner) {
return _.some(inner, function (point) {
return _.some(inner, function(point) {
return iD.geo.pointInPolygon(point, outer);
});
};
+6 -6
View File
@@ -1,4 +1,4 @@
iD.geo.Extent = function (min, max) {
iD.geo.Extent = function geoExtent(min, max) {
if (!(this instanceof iD.geo.Extent)) return new iD.geo.Extent(min, max);
if (min instanceof iD.geo.Extent) {
return min;
@@ -14,21 +14,21 @@ iD.geo.Extent = function (min, max) {
iD.geo.Extent.prototype = [[], []];
_.extend(iD.geo.Extent.prototype, {
extend: function (obj) {
obj = iD.geo.Extent(obj);
extend: function(obj) {
if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj);
return iD.geo.Extent([Math.min(obj[0][0], this[0][0]),
Math.min(obj[0][1], this[0][1])],
[Math.max(obj[1][0], this[1][0]),
Math.max(obj[1][1], this[1][1])]);
},
center: function () {
center: function() {
return [(this[0][0] + this[1][0]) / 2,
(this[0][1] + this[1][1]) / 2];
},
intersects: function (obj) {
obj = iD.geo.Extent(obj);
intersects: function(obj) {
if (!(obj instanceof iD.geo.Extent)) obj = new iD.geo.Extent(obj);
return obj[0][0] <= this[1][0] &&
obj[0][1] <= this[1][1] &&
obj[1][0] >= this[0][0] &&
-158
View File
@@ -1,158 +0,0 @@
iD.History = function() {
var stack, index,
imagery_used = 'Bing',
dispatch = d3.dispatch('change', 'undone', 'redone');
function perform(actions) {
actions = Array.prototype.slice.call(actions);
var annotation;
if (!_.isFunction(_.last(actions))) {
annotation = actions.pop();
}
var graph = stack[index].graph;
for (var i = 0; i < actions.length; i++) {
graph = actions[i](graph);
}
return {graph: graph, annotation: annotation, imagery_used: imagery_used};
}
function change(previous) {
dispatch.change(history.graph().difference(previous));
}
var history = {
graph: function () {
return stack[index].graph;
},
merge: function (entities) {
for (var i = 0; i < stack.length; i++) {
stack[i].graph.rebase(entities);
}
},
perform: function () {
var previous = stack[index].graph;
stack = stack.slice(0, index + 1);
stack.push(perform(arguments));
index++;
change(previous);
},
replace: function () {
var previous = stack[index].graph;
// assert(index == stack.length - 1)
stack[index] = perform(arguments);
change(previous);
},
pop: function () {
var previous = stack[index].graph;
if (index > 0) {
index--;
stack.pop();
change(previous);
}
},
undo: function () {
var previous = stack[index].graph;
// Pop to the first annotated state.
while (index > 0) {
if (stack[index].annotation) break;
index--;
}
// Pop to the next annotated state.
while (index > 0) {
index--;
if (stack[index].annotation) break;
}
dispatch.undone();
change(previous);
},
redo: function () {
var previous = stack[index].graph;
while (index < stack.length - 1) {
index++;
if (stack[index].annotation) break;
}
dispatch.redone();
change(previous);
},
undoAnnotation: function () {
var i = index;
while (i >= 0) {
if (stack[i].annotation) return stack[i].annotation;
i--;
}
},
redoAnnotation: function () {
var i = index + 1;
while (i <= stack.length - 1) {
if (stack[i].annotation) return stack[i].annotation;
i++;
}
},
changes: function () {
var initial = stack[0].graph,
current = stack[index].graph;
return {
modified: current.modified().map(function (id) {
return current.entity(id);
}),
created: current.created().map(function (id) {
return current.entity(id);
}),
deleted: current.deleted().map(function (id) {
return initial.entity(id);
})
};
},
hasChanges: function() {
return !!this.numChanges();
},
numChanges: function() {
return d3.sum(d3.values(this.changes()).map(function(c) {
return c.length;
}));
},
imagery_used: function(source) {
if (source) imagery_used = source;
else return _.without(
_.unique(_.pluck(stack.slice(1, index + 1), 'imagery_used')),
undefined, 'Custom');
},
reset: function () {
stack = [{graph: iD.Graph()}];
index = 0;
dispatch.change();
}
};
history.reset();
return d3.rebind(history, dispatch, 'on');
};
+129 -270
View File
@@ -1,290 +1,149 @@
window.iD = function(container) {
// the reported, displayed version of iD.
var version = '0.0.0-alpha1';
window.iD = function () {
var context = {},
storage = localStorage || {};
var connection = iD.Connection()
.version(version),
history = iD.History(),
map = iD.Map()
.connection(connection)
.history(history),
controller = iD.Controller(map, history);
context.storage = function(k, v) {
if (arguments.length === 1) return storage[k];
else if (v === null) delete storage[k];
else storage[k] = v;
};
map.background.source(iD.BackgroundSource.Bing);
var history = iD.History(context),
dispatch = d3.dispatch('enter', 'exit'),
mode,
container,
ui = iD.ui(context),
map = iD.Map(context);
function editor(container) {
if (!iD.supported()) {
container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' +
'and Internet Explorer 9 and above. Please upgrade your browser ' +
'or use Potlatch 2 to edit the map.')
.style('text-align:center;font-style:italic;');
return;
// the connection requires .storage() to be available on calling.
var connection = iD.Connection(context);
connection.on('load.context', function loadContext(err, result) {
history.merge(result);
});
/* Straight accessors. Avoid using these if you can. */
context.ui = function() { return ui; };
context.connection = function() { return connection; };
context.history = function() { return history; };
context.map = function() { return map; };
/* History */
context.graph = history.graph;
context.perform = history.perform;
context.replace = history.replace;
context.pop = history.pop;
context.undo = history.undo;
context.redo = history.redo;
context.changes = history.changes;
/* Graph */
context.entity = function(id) {
return history.graph().entity(id);
};
context.geometry = function(id) {
return context.entity(id).geometry(history.graph());
};
/* Modes */
context.enter = function(newMode) {
if (mode) {
mode.exit();
dispatch.exit(mode);
}
function hintprefix(x, y) {
return '<span>' + y + '</span>' + '<div class="keyhint-wrap"><span class="keyhint"> ' + x + '</span></div>';
mode = newMode;
mode.enter();
dispatch.enter(mode);
};
context.mode = function() {
return mode;
};
context.selection = function() {
if (mode.id === 'select') {
return mode.selection();
} else {
return [];
}
};
var m = container.append('div')
.attr('id', 'map')
.call(map);
/* Behaviors */
context.install = function(behavior) {
context.surface().call(behavior);
};
var bar = container.append('div')
.attr('id', 'bar')
.attr('class','pad1 fillD');
context.uninstall = function(behavior) {
context.surface().call(behavior.off);
};
var limiter = bar.append('div')
.attr('class', 'limiter');
/* Map */
context.background = function() { return map.background; };
context.surface = function() { return map.surface; };
context.projection = map.projection;
context.tail = map.tail;
context.redraw = map.redraw;
context.pan = map.pan;
context.zoomIn = map.zoomIn;
context.zoomOut = map.zoomOut;
var buttons_joined = limiter.append('div')
.attr('class', 'button-wrap joined col4');
context.container = function(_) {
if (!arguments.length) return container;
container = _;
return context;
};
var buttons = buttons_joined.selectAll('button.add-button')
.data([iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()])
.enter().append('button')
.attr('tabindex', -1)
.attr('class', function (mode) { return mode.title + ' add-button col3'; })
.call(bootstrap.tooltip().placement('bottom').html(true))
.attr('data-original-title', function (mode) {
return hintprefix(mode.key, mode.description);
})
.on('click.editor', function (mode) { controller.enter(mode); });
function disableTooHigh() {
if (map.editable()) {
notice.message(false);
buttons.attr('disabled', null);
} else {
buttons.attr('disabled', 'disabled');
notice.message(true);
controller.enter(iD.modes.Browse());
}
}
var notice = iD.ui.notice(limiter)
.message(false)
.on('zoom', function() { map.zoom(16); });
map.on('move.editor', _.debounce(function() {
disableTooHigh();
contributors.call(iD.ui.contributors(map));
}, 500));
buttons.append('span')
.attr('class', function(d) {
return d.id + ' icon icon-pre-text';
});
buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; });
controller.on('enter.editor', function (entered) {
buttons.classed('active', function (mode) { return entered.button === mode.button; });
container.classed("mode-" + entered.id, true);
});
controller.on('exit.editor', function (exited) {
container.classed("mode-" + exited.id, false);
});
var undo_buttons = limiter.append('div')
.attr('class', 'button-wrap joined col1'),
undo_tooltip = bootstrap.tooltip().placement('bottom').html(true);
undo_buttons.append('button')
.attr({ id: 'undo', 'class': 'col6' })
.property('disabled', true)
.html("<span class='undo icon'></span><small></small>")
.on('click.editor', history.undo)
.call(undo_tooltip);
undo_buttons.append('button')
.attr({ id: 'redo', 'class': 'col6' })
.property('disabled', true)
.html("<span class='redo icon'><small></small>")
.on('click.editor', history.redo)
.call(undo_tooltip);
var save_button = limiter.append('div').attr('class','button-wrap col1').append('button')
.attr('class', 'save col12')
.call(iD.ui.save().map(map).controller(controller));
var zoom = container.append('div')
.attr('class', 'zoombuttons map-control')
.selectAll('button')
.data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']])
.enter()
.append('button')
.attr('tabindex', -1)
.attr('class', function(d) { return d[0]; })
.attr('title', function(d) { return d[3]; })
.on('click.editor', function(d) { return d[2](); })
.append('span')
.attr('class', function(d) {
return d[0] + ' icon';
});
if (navigator.geolocation) {
container.append('div')
.call(iD.ui.geolocate(map));
}
var gc = container.append('div').attr('class', 'geocode-control map-control')
.call(iD.ui.geocoder().map(map));
container.append('div').attr('class', 'map-control layerswitcher-control')
.call(iD.ui.layerswitcher(map));
container.append('div')
.style('display', 'none')
.attr('class', 'inspector-wrap fr col5');
var about = container.append('div')
.attr('class','col12 about-block fillD pad1');
about.append('div')
.attr('class', 'user-container')
.append('div')
.attr('class', 'hello');
var aboutList = about.append('ul')
.attr('id','about')
.attr('class','link-list');
var linkList = aboutList.append('ul')
.attr('id','about')
.attr('class','pad1 fillD about-block link-list');
linkList.append('li').append('a').attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD').text(version);
linkList.append('li').append('a').attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD/issues').text('report a bug');
var imagery = linkList.append('li').attr('id', 'attribution');
imagery.append('span').text('imagery');
imagery.append('a').attr('target', '_blank')
.attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing');
linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#')
.text('dev')
.on('click.editor', function() {
d3.event.preventDefault();
if (d3.select(this).classed('live')) {
map.flush().connection()
.url('http://api06.dev.openstreetmap.org');
d3.select(this).text('dev').classed('live', false);
} else {
map.flush().connection()
.url('http://www.openstreetmap.org');
d3.select(this).text('live').classed('live', true);
}
});
var contributors = linkList.append('li')
.attr('id', 'user-list');
contributors.append('span')
.attr('class', 'icon nearby icon-pre-text');
contributors.append('span')
.text('Viewing contributions by ');
contributors.append('span')
.attr('class', 'contributor-list');
contributors.append('span')
.attr('class', 'contributor-count');
history.on('change.editor', function() {
window.onbeforeunload = history.hasChanges() ? function() {
return 'You have unsaved changes.';
} : null;
var undo = history.undoAnnotation(),
redo = history.redoAnnotation();
function refreshTooltip(selection) {
if (selection.property('disabled')) {
selection.call(undo_tooltip.hide);
} else if (selection.property('tooltipVisible')) {
selection.call(undo_tooltip.show);
}
}
limiter.select('#undo')
.property('disabled', !undo)
.attr('data-original-title', hintprefix('⌘ + Z', undo))
.call(refreshTooltip);
limiter.select('#redo')
.property('disabled', !redo)
.attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo))
.call(refreshTooltip);
});
d3.select(window).on('resize.editor', function() {
map.size(m.size());
});
var keybinding = d3.keybinding('main')
.on('⌘+Z', function() { history.undo(); })
.on('⌃+Z', function() { history.undo(); })
.on('⌘+⇧+Z', function() { history.redo(); })
.on('⌃+⇧+Z', function() { history.redo(); })
.on('⌫', function() { d3.event.preventDefault(); });
[iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()].forEach(function(m) {
keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); });
});
d3.select(document)
.call(keybinding);
var hash = iD.behavior.Hash(controller, map);
hash();
if (!hash.hadHash) {
map.centerZoom([-77.02271, 38.90085], 20);
}
d3.select('.user-container').call(iD.ui.userpanel(connection)
.on('logout.editor', connection.logout)
.on('login.editor', connection.authenticate));
controller.enter(iD.modes.Browse());
if (!localStorage.sawSplash) {
iD.ui.splash();
localStorage.sawSplash = true;
}
var q = iD.util.stringQs(location.hash.substring(1)), detected = false;
if (q.layer) {
context.background()
.source(_.find(iD.layers, function(l) {
if (l.data.sourcetag === q.layer) {
return (detected = true);
}
}));
}
editor.connection = function(_) {
if (!arguments.length) return connection;
connection = _;
return editor;
};
editor.map = function() {
return map;
};
editor.controller = function() {
return controller;
};
if (arguments.length) {
d3.select(container).call(editor);
if (!detected) {
context.background()
.source(_.find(iD.layers, function(l) {
return l.data.name === 'Bing aerial imagery';
}));
}
return editor;
return d3.rebind(context, dispatch, 'on');
};
iD.supported = function() {
if (navigator.appName !== 'Microsoft Internet Explorer') {
return true;
iD.version = '0.0.0-alpha1';
iD.detect = function() {
var browser = {};
var ua = navigator.userAgent,
msie = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
if (msie.exec(ua) !== null) {
var rv = parseFloat(RegExp.$1);
browser.support = !(rv && rv < 9);
} else {
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
if (re.exec(ua) !== null) {
rv = parseFloat( RegExp.$1 );
}
if (rv && rv < 9) return false;
else return true;
browser.support = true;
}
// Added due to incomplete svg style support. See #715
browser.opera = ua.indexOf('Opera') >= 0;
browser.locale = navigator.language;
function nav(x) {
return navigator.userAgent.indexOf(x) !== -1;
}
if (nav('Win')) browser.os = 'win';
else if (nav('Mac')) browser.os = 'mac';
else if (nav('X11')) browser.os = 'linux';
else if (nav('Linux')) browser.os = 'linux';
else browser.os = 'win';
return browser;
};
+50 -71
View File
@@ -1,87 +1,66 @@
iD.modes.AddArea = function() {
iD.modes.AddArea = function(context) {
var mode = {
id: 'add-area',
button: 'area',
title: t('modes.add_area.title'),
description: t('modes.add_area.description'),
key: t('modes.add_area.key')
key: '4'
};
var behavior,
defaultTags = {area: 'yes'};
mode.enter = function() {
var map = mode.map,
history = mode.history,
controller = mode.controller;
function start(loc) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function startFromWay(other, loc, index) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function startFromNode(node) {
var graph = history.graph(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
function startFromMidpoint(midpoint) {
var graph = history.graph(),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawArea(way.id, graph));
}
behavior = iD.behavior.AddWay(mode)
var behavior = iD.behavior.AddWay(context)
.on('start', start)
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode)
.on('startFromMidpoint', startFromMidpoint);
.on('startFromNode', startFromNode),
defaultTags = {area: 'yes'};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.add_area.tail'));
function start(loc) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawArea(context, way.id, graph));
}
function startFromWay(other, loc, index) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
context.enter(iD.modes.DrawArea(context, way.id, graph));
}
function startFromNode(node) {
var graph = context.graph(),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawArea(context, way.id, graph));
}
mode.enter = function() {
context.install(behavior);
context.tail(t('modes.add_area.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+58 -78
View File
@@ -1,94 +1,74 @@
iD.modes.AddLine = function() {
iD.modes.AddLine = function(context) {
var mode = {
id: 'add-line',
button: 'line',
title: t('modes.add_line.title'),
description: t('modes.add_line.description'),
key: t('modes.add_line.key')
key: '3'
};
var behavior,
defaultTags = {highway: 'residential'};
mode.enter = function() {
var map = mode.map,
history = mode.history,
controller = mode.controller;
function start(loc) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
function startFromWay(other, loc, index) {
var graph = history.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
function startFromNode(node) {
var graph = history.graph(),
parent = graph.parentWays(node)[0],
isLine = parent && parent.geometry(graph) === 'line';
if (isLine && parent.first() === node.id) {
controller.enter(iD.modes.DrawLine(parent.id, 'backward', graph));
} else if (isLine && parent.last() === node.id) {
controller.enter(iD.modes.DrawLine(parent.id, 'forward', graph));
} else {
var way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
}
function startFromMidpoint(midpoint) {
var graph = history.graph(),
node = iD.Node(),
way = iD.Way({tags: defaultTags});
history.perform(
iD.actions.AddMidpoint(midpoint, node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
behavior = iD.behavior.AddWay(mode)
var behavior = iD.behavior.AddWay(context)
.on('start', start)
.on('startFromWay', startFromWay)
.on('startFromNode', startFromNode)
.on('startFromMidpoint', startFromMidpoint);
.on('startFromNode', startFromNode),
defaultTags = {highway: 'residential'};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.add_line.tail'));
function start(loc) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph));
}
function startFromWay(other, loc, index) {
var graph = context.graph(),
node = iD.Node({loc: loc}),
way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(node),
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id),
iD.actions.AddVertex(other.id, node.id, index));
context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph));
}
function startFromNode(node) {
var graph = context.graph(),
parent = graph.parentWays(node)[0],
isLine = parent && parent.geometry(graph) === 'line';
if (isLine && parent.first() === node.id) {
context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph));
} else if (isLine && parent.last() === node.id) {
context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph));
} else {
var way = iD.Way({tags: defaultTags});
context.perform(
iD.actions.AddEntity(way),
iD.actions.AddVertex(way.id, node.id));
context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph));
}
}
mode.enter = function() {
context.install(behavior);
context.tail(t('modes.add_line.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+34 -44
View File
@@ -1,58 +1,48 @@
iD.modes.AddPoint = function() {
iD.modes.AddPoint = function(context) {
var mode = {
id: 'add-point',
title: t('modes.add_point.title'),
description: t('modes.add_point.description'),
key: t('modes.add_point.key')
key: '2'
};
var behavior;
var behavior = iD.behavior.Draw(context)
.on('click', add)
.on('clickWay', addWay)
.on('clickNode', addNode)
.on('cancel', cancel)
.on('finish', cancel);
function add(loc) {
var node = iD.Node({loc: loc});
context.perform(
iD.actions.AddEntity(node),
t('operations.add.annotation.point'));
context.enter(iD.modes.Select(context, [node.id], true));
}
function addWay(way, loc, index) {
add(loc);
}
function addNode(node) {
add(node.loc);
}
function cancel() {
context.enter(iD.modes.Browse(context));
}
mode.enter = function() {
var map = mode.map,
history = mode.history,
controller = mode.controller;
function add(loc) {
var node = iD.Node({loc: loc});
history.perform(
iD.actions.AddEntity(node),
t('operations.add.annotation.point'));
controller.enter(iD.modes.Select([node.id], true));
}
function addWay(way, loc, index) {
add(loc);
}
function addNode(node) {
add(node.loc);
}
function cancel() {
controller.enter(iD.modes.Browse());
}
behavior = iD.behavior.Draw(map)
.on('click', add)
.on('clickWay', addWay)
.on('clickNode', addNode)
.on('clickMidpoint', addNode)
.on('cancel', cancel)
.on('finish', cancel);
mode.map.surface.call(behavior);
mode.map.tail(t('modes.add_point.tail'));
context.install(behavior);
context.tail(t('modes.add_point.tail'));
};
mode.exit = function() {
var map = mode.map,
surface = map.surface;
map.tail(false);
behavior.off(surface);
context.uninstall(behavior);
context.tail(false);
};
return mode;
+9 -15
View File
@@ -1,33 +1,27 @@
iD.modes.Browse = function() {
iD.modes.Browse = function(context) {
var mode = {
button: 'browse',
id: 'browse',
title: t('modes.browse.title'),
description: t('modes.browse.description'),
key: t('modes.browse.key')
key: '1'
};
var behaviors;
var behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(context),
iD.behavior.Lasso(context),
iD.behavior.DragNode(context)];
mode.enter = function() {
var surface = mode.map.surface;
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(mode),
iD.behavior.DragNode(mode),
iD.behavior.DragMidpoint(mode)];
behaviors.forEach(function(behavior) {
behavior(surface);
context.install(behavior);
});
};
mode.exit = function() {
var surface = mode.map.surface;
behaviors.forEach(function(behavior) {
behavior.off(surface);
context.uninstall(behavior);
});
};
+6 -6
View File
@@ -1,4 +1,4 @@
iD.modes.DrawArea = function(wayId, baseGraph) {
iD.modes.DrawArea = function(context, wayId, baseGraph) {
var mode = {
button: 'area',
id: 'draw-area'
@@ -7,11 +7,11 @@ iD.modes.DrawArea = function(wayId, baseGraph) {
var behavior;
mode.enter = function() {
var way = mode.history.graph().entity(wayId),
var way = context.entity(wayId),
headId = way.nodes[way.nodes.length - 2],
tailId = way.first();
behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph);
behavior = iD.behavior.DrawWay(context, wayId, -1, mode, baseGraph);
var addNode = behavior.addNode;
@@ -23,12 +23,12 @@ iD.modes.DrawArea = function(wayId, baseGraph) {
}
};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.draw_area.tail'));
context.install(behavior);
context.tail(t('modes.draw_area.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+6 -6
View File
@@ -1,4 +1,4 @@
iD.modes.DrawLine = function(wayId, direction, baseGraph) {
iD.modes.DrawLine = function(context, wayId, direction, baseGraph) {
var mode = {
button: 'line',
id: 'draw-line'
@@ -7,11 +7,11 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) {
var behavior;
mode.enter = function() {
var way = mode.history.graph().entity(wayId),
var way = context.entity(wayId),
index = (direction === 'forward') ? undefined : 0,
headId = (direction === 'forward') ? way.last() : way.first();
behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph);
behavior = iD.behavior.DrawWay(context, wayId, index, mode, baseGraph);
var addNode = behavior.addNode;
@@ -23,12 +23,12 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) {
}
};
mode.map.surface.call(behavior);
mode.map.tail(t('modes.draw_line.tail'));
context.install(behavior);
context.tail(t('modes.draw_line.tail'));
};
mode.exit = function() {
mode.map.surface.call(behavior.off);
context.uninstall(behavior);
};
return mode;
+55 -31
View File
@@ -1,60 +1,87 @@
iD.modes.MoveWay = function(wayId) {
iD.modes.MoveWay = function(context, wayId) {
var mode = {
id: 'move-way'
id: 'move-way',
button: 'browse'
};
var keybinding = d3.keybinding('move-way');
mode.enter = function() {
var map = mode.map,
history = mode.history,
graph = history.graph(),
selection = map.surface,
controller = mode.controller,
projection = map.projection,
way = graph.entity(wayId),
origin = d3.mouse(selection.node()),
annotation = t('operations.move.annotation.' + way.geometry(graph));
var origin = context.map().mouseCoordinates(),
nudgeInterval,
annotation = t('operations.move.annotation.' + context.geometry(wayId));
// If intiated via keyboard
if (!origin[0] && !origin[1]) origin = null;
history.perform(
context.perform(
iD.actions.Noop(),
annotation);
function edge(point, size) {
var pad = [30, 100, 30, 100];
if (point[0] > size[0] - pad[0]) return [-10, 0];
else if (point[0] < pad[2]) return [10, 0];
else if (point[1] > size[1] - pad[1]) return [0, -10];
else if (point[1] < pad[3]) return [0, 10];
return null;
}
function startNudge(nudge) {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = window.setInterval(function() {
context.pan(nudge);
}, 50);
}
function stopNudge() {
if (nudgeInterval) window.clearInterval(nudgeInterval);
nudgeInterval = null;
}
function point() {
return d3.mouse(context.map().surface.node());
}
function move() {
var p = d3.mouse(selection.node()),
delta = origin ?
[p[0] - origin[0], p[1] - origin[1]] :
[0, 0];
var p = point();
origin = p;
var delta = origin ?
[p[0] - context.projection(origin)[0],
p[1] - context.projection(origin)[1]] :
[0, 0];
history.replace(
iD.actions.MoveWay(wayId, delta, projection),
var nudge = edge(p, context.map().size());
if (nudge) startNudge(nudge);
else stopNudge();
origin = context.map().mouseCoordinates();
context.replace(
iD.actions.MoveWay(wayId, delta, context.projection),
annotation);
}
function finish() {
d3.event.stopPropagation();
controller.enter(iD.modes.Select([way.id], true));
context.enter(iD.modes.Select(context, [wayId], true));
}
function cancel() {
history.pop();
controller.enter(iD.modes.Select([way.id], true));
context.pop();
context.enter(iD.modes.Select(context, [wayId], true));
}
function undone() {
controller.enter(iD.modes.Browse());
context.enter(iD.modes.Browse(context));
}
selection
context.surface()
.on('mousemove.move-way', move)
.on('click.move-way', finish);
history.on('undone.move-way', undone);
context.history()
.on('undone.move-way', undone);
keybinding
.on('⎋', cancel)
@@ -65,15 +92,12 @@ iD.modes.MoveWay = function(wayId) {
};
mode.exit = function() {
var map = mode.map,
history = mode.history,
selection = map.surface;
selection
context.surface()
.on('mousemove.move-way', null)
.on('click.move-way', null);
history.on('undone.move-way', null);
context.history()
.on('undone.move-way', null);
keybinding.off();
};
+126 -71
View File
@@ -1,17 +1,22 @@
iD.modes.Select = function(selection, initial) {
iD.modes.Select = function(context, selection, initial) {
var mode = {
id: 'select',
button: 'browse'
};
var inspector = iD.ui.inspector().initial(!!initial),
var inspector = iD.ui.Inspector().initial(!!initial),
keybinding = d3.keybinding('select'),
behaviors,
timeout = null,
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(context),
iD.behavior.Lasso(context),
iD.behavior.DragNode(context)],
radialMenu;
function changeTags(d, tags) {
if (!_.isEqual(singular().tags, tags)) {
mode.history.perform(
context.perform(
iD.actions.ChangeTags(d.id, tags),
t('operations.change_tags.annotation'));
}
@@ -19,41 +24,53 @@ iD.modes.Select = function(selection, initial) {
function singular() {
if (selection.length === 1) {
return mode.map.history().graph().entity(selection[0]);
return context.entity(selection[0]);
}
}
function positionMenu() {
var entity = singular();
if (entity && entity.type === 'node') {
radialMenu.center(context.projection(entity.loc));
} else {
radialMenu.center(d3.mouse(context.surface().node()));
}
}
function showMenu() {
context.surface()
.call(radialMenu.close)
.call(radialMenu);
}
mode.selection = function() {
return selection;
};
mode.reselect = function() {
positionMenu();
showMenu();
};
mode.enter = function() {
var map = mode.map,
history = map.history(),
graph = history.graph(),
surface = map.surface,
entity = singular();
inspector
.graph(graph)
.presetData(map.connection().presetData());
behaviors = [
iD.behavior.Hover(),
iD.behavior.Select(mode),
iD.behavior.DragNode(mode),
iD.behavior.DragMidpoint(mode)];
var entity = singular();
behaviors.forEach(function(behavior) {
behavior(surface);
context.install(behavior);
});
var operations = d3.values(iD.operations)
.map(function (o) { return o(selection, mode); })
.filter(function (o) { return o.available(); });
var operations = _.without(d3.values(iD.operations), iD.operations.Delete)
.map(function(o) { return o(selection, context); })
.filter(function(o) { return o.available(); });
operations.unshift(iD.operations.Delete(selection, context));
keybinding.on('⎋', function() {
context.enter(iD.modes.Browse(context));
});
operations.forEach(function(operation) {
keybinding.on(operation.key, function () {
keybinding.on(operation.key, function() {
if (operation.enabled()) {
operation();
}
@@ -66,9 +83,12 @@ iD.modes.Select = function(selection, initial) {
}), true));
if (entity) {
inspector.graph(graph);
inspector
.context(context)
.presetData(context.connection().presetData());
d3.select('.inspector-wrap')
context.container()
.select('.inspector-wrap')
.style('display', 'block')
.style('opacity', 1)
.datum(entity)
@@ -77,38 +97,39 @@ iD.modes.Select = function(selection, initial) {
if (d3.event) {
// Pan the map if the clicked feature intersects with the position
// of the inspector
var inspector_size = d3.select('.inspector-wrap').size(),
map_size = mode.map.size(),
var inspector_size = context.container().select('.inspector-wrap').size(),
map_size = context.map().size(),
offset = 50,
shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset,
shift_left = d3.event.clientX - map_size[0] + inspector_size[0] + offset,
center = (map_size[0] / 2) + shift_left + offset;
if (shift_left > 0 && inspector_size[1] > d3.event.y) {
mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2]));
if (shift_left > 0 && inspector_size[1] > d3.event.clientY) {
context.map().centerEase(context.projection.invert([center, map_size[1]/2]));
}
}
inspector
.on('changeTags', changeTags)
.on('close', function() { mode.controller.enter(iD.modes.Browse()); });
history.on('change.select', function() {
// Exit mode if selected entity gets undone
var oldEntity = entity,
newEntity = history.graph().entity(selection[0]);
if (!newEntity) {
mode.controller.enter(iD.modes.Browse());
} else if (!_.isEqual(oldEntity.tags, newEntity.tags)) {
inspector.tags(newEntity.tags);
}
surface.call(radialMenu.close);
});
.on('close', function() { context.enter(iD.modes.Browse(context)); });
}
map.on('move.select', function() {
surface.call(radialMenu.close);
context.history().on('change.select', function() {
context.surface().call(radialMenu.close);
if (_.any(selection, function(id) { return !context.entity(id); })) {
// Exit mode if selected entity gets undone
context.enter(iD.modes.Browse(context));
} else if (entity) {
var newEntity = context.entity(selection[0]);
if (!_.isEqual(entity.tags, newEntity.tags)) {
inspector.tags(newEntity.tags);
}
}
});
context.map().on('move.select', function() {
context.surface().call(radialMenu.close);
});
function dblclick() {
@@ -117,12 +138,32 @@ iD.modes.Select = function(selection, initial) {
if (datum instanceof iD.Way && !target.classed('fill')) {
var choice = iD.geo.chooseIndex(datum,
d3.mouse(mode.map.surface.node()), mode.map),
d3.mouse(context.surface().node()), context),
node = iD.Node({ loc: choice.loc });
history.perform(
iD.actions.AddEntity(node),
iD.actions.AddVertex(datum.id, node.id, choice.index),
var prev = datum.nodes[choice.index - 1],
next = datum.nodes[choice.index],
prevParents = context.graph().parentWays({ id: prev }),
ways = [];
for (var i = 0; i < prevParents.length; i++) {
var p = prevParents[i];
for (var k = 0; k < p.nodes.length; k++) {
if (p.nodes[k] === prev) {
if (p.nodes[k-1] === next) {
ways.push({ id: p.id, index: k});
break;
} else if (p.nodes[k+1] === next) {
ways.push({ id: p.id, index: k+1});
break;
}
}
}
}
context.perform(iD.actions.AddEntity(node),
iD.actions.AddMidpoint({ ways: ways, loc: node.loc }, node),
t('operations.add.annotation.vertex'));
d3.event.preventDefault();
@@ -130,36 +171,49 @@ iD.modes.Select = function(selection, initial) {
}
}
function selected(entity) {
if (!entity) return false;
if (selection.indexOf(entity.id) >= 0) return true;
return d3.select(this).classed('stroke') &&
_.any(context.graph().parentRelations(entity), function(parent) {
return selection.indexOf(parent.id) >= 0;
});
}
d3.select(document)
.call(keybinding);
surface.on('dblclick.select', dblclick)
context.surface()
.selectAll("*")
.filter(function (d) { return d && selection.indexOf(d.id) >= 0; })
.filter(selected)
.classed('selected', true);
radialMenu = iD.ui.RadialMenu(operations);
var show = d3.event && !initial;
if (d3.event && !initial) {
var loc = map.mouseCoordinates();
if (show) {
positionMenu();
}
if (entity && entity.type === 'node') {
loc = entity.loc;
timeout = window.setTimeout(function() {
if (show) {
showMenu();
}
surface.call(radialMenu, map.projection(loc));
}
context.surface()
.on('dblclick.select', dblclick);
}, 200);
};
mode.exit = function () {
var surface = mode.map.surface,
history = mode.history;
mode.exit = function() {
if (singular()) {
changeTags(singular(), inspector.tags());
}
d3.select('.inspector-wrap')
if (timeout) window.clearTimeout(timeout);
context.container()
.select('.inspector-wrap')
.style('display', 'none')
.html('');
@@ -168,7 +222,7 @@ iD.modes.Select = function(selection, initial) {
d3.selectAll('div.typeahead').remove();
behaviors.forEach(function(behavior) {
behavior.off(surface);
context.uninstall(behavior);
});
var q = iD.util.stringQs(location.hash.substring(1));
@@ -176,13 +230,14 @@ iD.modes.Select = function(selection, initial) {
keybinding.off();
history.on('change.select', null);
context.history()
.on('change.select', null);
surface.on('dblclick.select', null)
context.surface()
.call(radialMenu.close)
.on('dblclick.select', null)
.selectAll(".selected")
.classed('selected', false);
surface.call(radialMenu.close);
};
return mode;
+9 -12
View File
@@ -1,4 +1,4 @@
iD.OAuth = function() {
iD.OAuth = function(context) {
var baseurl = 'http://www.openstreetmap.org',
o = {},
keys,
@@ -6,10 +6,6 @@ iD.OAuth = function() {
function keyclean(x) { return x.replace(/\W/g, ''); }
if (token('oauth_token')) {
o.oauth_token = token('oauth_token');
}
function timenonce(o) {
o.oauth_timestamp = ohauth.timestamp();
o.oauth_nonce = ohauth.nonce();
@@ -17,11 +13,12 @@ iD.OAuth = function() {
}
// token getter/setter, namespaced to the current `apibase` value.
function token(k, x) {
if (arguments.length == 2) {
localStorage[keyclean(baseurl) + k] = x;
}
return localStorage[keyclean(baseurl) + k];
function token() {
return context.storage.apply(context, arguments);
}
if (token('oauth_token')) {
o.oauth_token = token('oauth_token');
}
oauth.authenticated = function() {
@@ -63,7 +60,7 @@ iD.OAuth = function() {
o.oauth_signature = ohauth.signature(oauth_secret, '',
ohauth.baseString('POST', url, o));
var l = iD.ui.loading('contacting openstreetmap...');
var l = iD.ui.loading(context.container(), 'contacting openstreetmap...');
// it would make more sense to have this code within the callback
// to oauth.xhr below. however, it needs to be directly within a
@@ -112,7 +109,7 @@ iD.OAuth = function() {
var request_token_secret = token('oauth_request_token_secret');
o.oauth_signature = ohauth.signature(oauth_secret, request_token_secret,
ohauth.baseString('POST', url, o));
var l = iD.ui.loading('contacting openstreetmap...');
var l = iD.ui.loading(context.container(), 'contacting openstreetmap...');
function accessTokenDone(err, xhr) {
if (err) callback(err);
+1 -1
View File
@@ -1 +1 @@
iD.operations = {}
iD.operations = {};
+7 -13
View File
@@ -1,25 +1,19 @@
iD.operations.Circularize = function(selection, mode) {
iD.operations.Circularize = function(selection, context) {
var entityId = selection[0],
history = mode.map.history(),
action = iD.actions.Circularize(entityId, mode.map);
action = iD.actions.Circularize(entityId, context.projection);
var operation = function() {
var graph = history.graph(),
entity = graph.entity(entityId),
annotation = t('operations.circularize.annotation.' + entity.geometry(graph));
history.perform(action, annotation);
var annotation = t('operations.circularize.annotation.' + context.geometry(entityId));
context.perform(action, annotation);
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 && entity.type === 'way';
return selection.length === 1 &&
context.entity(entityId).type === 'way';
};
operation.enabled = function() {
var graph = history.graph();
return action.enabled(graph);
return action.enabled(context.graph());
};
operation.id = "circularize";
+11 -14
View File
@@ -1,23 +1,20 @@
iD.operations.Delete = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history();
iD.operations.Delete = function(selection, context) {
var operation = function() {
var graph = history.graph(),
entity = graph.entity(entityId),
action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type],
annotation = t('operations.delete.annotation.' + entity.geometry(graph));
var annotation;
history.perform(
action(entityId),
if (selection.length === 1) {
annotation = t('operations.delete.annotation.' + context.geometry(selection[0]));
} else {
annotation = t('operations.delete.annotation.multiple', {n: selection.length});
}
context.perform(
iD.actions.DeleteMultiple(selection),
annotation);
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
(entity.type === 'way' || entity.type === 'node');
return true;
};
operation.enabled = function() {
+24
View File
@@ -0,0 +1,24 @@
iD.operations.Disconnect = function(selection, context) {
var entityId = selection[0],
action = iD.actions.Disconnect(entityId);
var operation = function() {
context.perform(action, t('operations.disconnect.annotation'));
};
operation.available = function() {
return selection.length === 1 &&
context.geometry(entityId) === 'vertex';
};
operation.enabled = function() {
return action.enabled(context.graph());
};
operation.id = "disconnect";
operation.key = t('operations.disconnect.key');
operation.title = t('operations.disconnect.title');
operation.description = t('operations.disconnect.description');
return operation;
};
+34
View File
@@ -0,0 +1,34 @@
iD.operations.Merge = function(selection, context) {
var join = iD.actions.Join(selection),
merge = iD.actions.Merge(selection);
var operation = function() {
var annotation = t('operations.merge.annotation', {n: selection.length}),
action;
if (join.enabled(context.graph())) {
action = join;
} else {
action = merge;
}
var difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
return selection.length >= 2;
};
operation.enabled = function() {
return join.enabled(context.graph()) ||
merge.enabled(context.graph());
};
operation.id = "merge";
operation.key = t('operations.merge.key');
operation.title = t('operations.merge.title');
operation.description = t('operations.merge.description');
return operation;
};
+4 -6
View File
@@ -1,15 +1,13 @@
iD.operations.Move = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history();
iD.operations.Move = function(selection, context) {
var entityId = selection[0];
var operation = function() {
mode.controller.enter(iD.modes.MoveWay(entityId));
context.enter(iD.modes.MoveWay(context, entityId));
};
operation.available = function() {
var graph = history.graph();
return selection.length === 1 &&
graph.entity(entityId).type === 'way';
context.entity(entityId).type === 'way';
};
operation.enabled = function() {
+25
View File
@@ -0,0 +1,25 @@
iD.operations.Orthogonalize = function(selection, context) {
var entityId = selection[0],
action = iD.actions.Orthogonalize(entityId, context.projection);
var operation = function() {
var annotation = t('operations.orthogonalize.annotation.' + context.geometry(entityId));
context.perform(action, annotation);
};
operation.available = function() {
return selection.length === 1 &&
context.entity(entityId).type === 'way';
};
operation.enabled = function() {
return action.enabled(context.graph());
};
operation.id = "orthogonalize";
operation.key = t('operations.orthogonalize.key');
operation.title = t('operations.orthogonalize.title');
operation.description = t('operations.orthogonalize.description');
return operation;
};
+5 -8
View File
@@ -1,18 +1,15 @@
iD.operations.Reverse = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history();
iD.operations.Reverse = function(selection, context) {
var entityId = selection[0];
var operation = function() {
history.perform(
iD.actions.ReverseWay(entityId),
context.perform(
iD.actions.Reverse(entityId),
t('operations.reverse.annotation'));
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
entity.geometry(graph) === 'line';
context.geometry(entityId) === 'line';
};
operation.enabled = function() {
+7 -9
View File
@@ -1,22 +1,20 @@
iD.operations.Split = function(selection, mode) {
iD.operations.Split = function(selection, context) {
var entityId = selection[0],
history = mode.map.history(),
action = iD.actions.SplitWay(entityId);
action = iD.actions.Split(entityId);
var operation = function() {
history.perform(action, t('operations.split.annotation'));
var annotation = t('operations.split.annotation'),
difference = context.perform(action, annotation);
context.enter(iD.modes.Select(context, difference.extantIDs()));
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
entity.geometry(graph) === 'vertex';
context.geometry(entityId) === 'vertex';
};
operation.enabled = function() {
var graph = history.graph();
return action.enabled(graph);
return action.enabled(context.graph());
};
operation.id = "split";
-28
View File
@@ -1,28 +0,0 @@
iD.operations.Unjoin = function(selection, mode) {
var entityId = selection[0],
history = mode.map.history(),
action = iD.actions.UnjoinNode(entityId);
var operation = function() {
history.perform(action, 'Unjoined lines.');
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
return selection.length === 1 &&
entity.geometry(graph) === 'vertex';
};
operation.enabled = function() {
var graph = history.graph();
return action.enabled(graph);
};
operation.id = "unjoin";
operation.key = t('operations.unjoin.key');
operation.title = t('operations.unjoin.title');
operation.description = t('operations.unjoin.description');
return operation;
};
+26 -19
View File
@@ -12,16 +12,6 @@ iD.Background = function() {
transformProp = iD.util.prefixCSSProperty('Transform'),
source = d3.functor('');
var imgstyle = 'position:absolute;transform-origin:0 0;' +
'-ms-transform-origin:0 0;' +
'-webkit-transform-origin:0 0;' +
'-moz-transform-origin:0 0;' +
'-o-transform-origin:0 0;' +
'-webkit-user-select: none;' +
'-webkit-user-drag: none;' +
'-moz-user-drag: none;' +
'opacity:0;';
function tileSizeAtZoom(d, z) {
return Math.ceil(tileSize[0] * Math.pow(2, z - d[2])) / tileSize[0];
}
@@ -63,7 +53,7 @@ iD.Background = function() {
var sel = this,
tiles = tile
.scale(projection.scale())
.scaleExtent(source.scaleExtent || [1, 17])
.scaleExtent((source.data && source.data.scaleExtent) || [1, 17])
.translate(projection.translate())(),
requests = [],
scaleExtent = tile.scaleExtent(),
@@ -92,8 +82,7 @@ iD.Background = function() {
cache[d[3]] = true;
d3.select(this)
.on('load', null)
.transition()
.style('opacity', 1);
.classed('tile-loaded', true);
background.apply(sel);
}
@@ -119,19 +108,24 @@ iD.Background = function() {
image.exit()
.style(transformProp, imageTransform)
.transition()
.style('opacity', 0)
.remove();
.classed('tile-loaded', false)
.each(function() {
var tile = this;
window.setTimeout(function() {
// this tile may already be removed
if (tile.parentNode) {
tile.parentNode.removeChild(tile);
}
}, 300);
});
image.enter().append('img')
.attr('style', imgstyle)
.attr('class', 'tile')
.attr('src', function(d) { return d[3]; })
.on('error', error)
.on('load', load);
image.style(transformProp, imageTransform);
if (Object.keys(cache).length > 100) cache = {};
}
background.offset = function(_) {
@@ -158,9 +152,22 @@ iD.Background = function() {
return background;
};
function setPermalink(source) {
var tag = source.data.sourcetag;
var q = iD.util.stringQs(location.hash.substring(1));
if (tag) {
location.replace('#' + iD.util.qsString(_.assign(q, {
layer: tag
}), true));
} else {
location.replace('#' + iD.util.qsString(_.omit(q, 'layer'), true));
}
}
background.source = function(_) {
if (!arguments.length) return source;
source = _;
setPermalink(source);
return background;
};
+11 -25
View File
@@ -1,8 +1,7 @@
iD.BackgroundSource = {};
// derive the url of a 'quadkey' style tile from a coordinate object
iD.BackgroundSource.template = function(template, subdomains, scaleExtent) {
scaleExtent = scaleExtent || [0, 18];
iD.BackgroundSource.template = function(data) {
var generator = function(coord) {
var u = '';
for (var zoom = coord[2]; zoom > 0; zoom--) {
@@ -12,18 +11,17 @@ iD.BackgroundSource.template = function(template, subdomains, scaleExtent) {
if ((coord[1] & mask) !== 0) byte += 2;
u += byte.toString();
}
// distribute requests against multiple domains
var t = subdomains ?
subdomains[coord[2] % subdomains.length] : '';
return template
.replace('{t}', t)
return data.template
.replace('{t}', data.subdomains ?
data.subdomains[coord[2] % data.subdomains.length] : '')
.replace('{u}', u)
.replace('{x}', coord[0])
.replace('{y}', coord[1])
.replace('{z}', coord[2]);
};
generator.scaleExtent = scaleExtent;
generator.data = data;
return generator;
};
@@ -31,21 +29,9 @@ iD.BackgroundSource.template = function(template, subdomains, scaleExtent) {
iD.BackgroundSource.Custom = function() {
var template = window.prompt('Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.');
if (!template) return null;
return iD.BackgroundSource.template(template, null, [0, 20]);
return iD.BackgroundSource.template({
template: template,
name: 'Custom (customized)'
});
};
iD.BackgroundSource.Bing = iD.BackgroundSource.template(
'http://ecn.t{t}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&mkt=en-gb&n=z',
[0, 1, 2, 3], [0, 20]);
iD.BackgroundSource.Tiger2012 = iD.BackgroundSource.template(
'http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png',
['a', 'b', 'c'], [0, 17]);
iD.BackgroundSource.OSM = iD.BackgroundSource.template(
'http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png',
['a', 'b', 'c'], [0, 18]);
iD.BackgroundSource.MapBox = iD.BackgroundSource.template(
'http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.jpg70',
['a', 'b', 'c'], [0, 16]);
iD.BackgroundSource.Custom.data = { 'name': 'Custom' };
+15
View File
@@ -0,0 +1,15 @@
iD.layers = iD.data.imagery.map(iD.BackgroundSource.template);
iD.layers.push((function() {
function custom() {
var template = window.prompt('Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.');
if (!template) return null;
if (template.match(/google/g)) return null;
return iD.BackgroundSource.template({
template: template,
name: 'Custom (customized)'
});
}
custom.data = { name: 'Custom' };
return custom;
})());
+57 -80
View File
@@ -1,6 +1,5 @@
iD.Map = function() {
var connection, history,
dimensions = [],
iD.Map = function(context) {
var dimensions = [],
dispatch = d3.dispatch('move', 'drawn'),
projection = d3.geo.mercator().scale(1024),
roundedProjection = iD.svg.RoundProjection(projection),
@@ -20,13 +19,15 @@ iD.Map = function() {
vertices = iD.svg.Vertices(roundedProjection),
lines = iD.svg.Lines(roundedProjection),
areas = iD.svg.Areas(roundedProjection),
multipolygons = iD.svg.Multipolygons(roundedProjection),
midpoints = iD.svg.Midpoints(roundedProjection),
labels = iD.svg.Labels(roundedProjection),
tail = d3.tail(),
surface, tilegroup;
function map(selection) {
context.history()
.on('change.map', redraw);
selection.call(zoom);
tilegroup = selection.append('div')
@@ -41,12 +42,16 @@ iD.Map = function() {
d3.event.stopPropagation();
}
}, true)
.on('mouseup.zoom', function() {
if (resetTransform()) redraw();
})
.attr('id', 'surface')
.call(iD.svg.Surface());
map.size(selection.size());
map.surface = surface;
map.tilesurface = tilegroup;
supersurface
.call(tail);
@@ -57,46 +62,21 @@ iD.Map = function() {
function drawVector(difference) {
var filter, all,
extent = map.extent(),
graph = history.graph();
function addParents(parents) {
for (var i = 0; i < parents.length; i++) {
var parent = parents[i];
if (only[parent.id] === undefined) {
only[parent.id] = parent;
addParents(graph.parentRelations(parent));
}
}
}
graph = context.graph();
if (!difference) {
all = graph.intersects(extent);
filter = d3.functor(true);
} else {
var only = {};
for (var j = 0; j < difference.length; j++) {
var id = difference[j],
entity = graph.entity(id);
// Even if the entity is false (deleted), it needs to be
// removed from the surface
only[id] = entity;
if (entity && entity.intersects(extent, graph)) {
addParents(graph.parentWays(only[id]));
addParents(graph.parentRelations(only[id]));
}
}
all = _.compact(_.values(only));
var complete = difference.complete(extent);
all = _.compact(_.values(complete));
filter = function(d) {
if (d.type === 'midpoint') {
for (var i = 0; i < d.ways.length; i++) {
if (d.ways[i].id in only) return true;
if (d.ways[i].id in complete) return true;
}
} else {
return d.id in only;
return d.id in complete;
}
};
}
@@ -109,7 +89,6 @@ iD.Map = function() {
.call(vertices, graph, all, filter)
.call(lines, graph, all, filter)
.call(areas, graph, all, filter)
.call(multipolygons, graph, all, filter)
.call(midpoints, graph, all, filter)
.call(labels, graph, all, filter, dimensions, !difference);
}
@@ -120,11 +99,6 @@ iD.Map = function() {
surface.selectAll('.layer *').remove();
}
function connectionLoad(err, result) {
history.merge(result);
redraw(Object.keys(result));
}
function zoomPan() {
if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
if (!dblclickEnabled) {
@@ -135,10 +109,10 @@ iD.Map = function() {
}
if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) {
iD.ui.flash()
iD.ui.flash(context.container())
.select('.content')
.text('Cannot zoom out further in current mode.');
return map.zoom(16);
return setZoom(16, true);
}
projection
@@ -172,17 +146,26 @@ iD.Map = function() {
}
function redraw(difference) {
clearTimeout(timeoutId);
// If we are in the middle of a zoom/pan, we can't do differenced redraws.
// It would result in artifacts where differenced entities are redrawn with
// one transform and unchanged entities with another.
if (resetTransform())
if (resetTransform()) {
difference = undefined;
}
surface.attr('data-zoom', ~~map.zoom());
tilegroup.call(background);
var zoom = String(~~map.zoom());
if (surface.attr('data-zoom') !== zoom) {
surface.attr('data-zoom', zoom);
}
if (!difference) {
tilegroup.call(background);
}
if (map.editable()) {
connection.loadTiles(projection, dimensions);
context.connection().loadTiles(projection, dimensions);
drawVector(difference);
} else {
editOff();
@@ -195,7 +178,11 @@ iD.Map = function() {
return map;
}
var queueRedraw = _.debounce(redraw, 200);
var timeoutId;
function queueRedraw() {
clearTimeout(timeoutId);
timeoutId = setTimeout(function() { redraw(); }, 300);
}
function pointLocation(p) {
var translate = projection.translate(),
@@ -230,8 +217,8 @@ iD.Map = function() {
return map;
};
function setZoom(z) {
if (z === map.zoom())
function setZoom(z, force) {
if (z === map.zoom() && !force)
return false;
var scale = 256 * Math.pow(2, z),
center = pxCenter(),
@@ -258,7 +245,6 @@ iD.Map = function() {
t[0] - ll[0] + c[0],
t[1] - ll[1] + c[1]]);
zoom.translate(projection.translate());
dispatch.move(map);
return true;
}
@@ -268,7 +254,7 @@ iD.Map = function() {
t[1] += d[1];
projection.translate(t);
zoom.translate(projection.translate());
return map;
return redraw();
};
map.size = function(_) {
@@ -328,39 +314,37 @@ iD.Map = function() {
map.extent = function(_) {
if (!arguments.length) {
return iD.geo.Extent(projection.invert([0, dimensions[1]]),
return new iD.geo.Extent(projection.invert([0, dimensions[1]]),
projection.invert([dimensions[0], 0]));
} else {
var extent = iD.geo.Extent(_),
tl = projection([extent[0][0], extent[1][1]]),
br = projection([extent[1][0], extent[0][1]]);
// Calculate maximum zoom that fits extent
var hFactor = (br[0] - tl[0]) / dimensions[0],
vFactor = (br[1] - tl[1]) / dimensions[1],
hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2,
vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2,
newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff);
map.centerZoom(extent.center(), newZoom);
var extent = iD.geo.Extent(_);
map.centerZoom(extent.center(), map.extentZoom(extent));
}
};
map.flush = function () {
connection.flush();
history.reset();
return map;
map.extentZoom = function(_) {
var extent = iD.geo.Extent(_),
tl = projection([extent[0][0], extent[1][1]]),
br = projection([extent[1][0], extent[0][1]]);
// Calculate maximum zoom that fits extent
var hFactor = (br[0] - tl[0]) / dimensions[0],
vFactor = (br[1] - tl[1]) / dimensions[1],
hZoomDiff = Math.log(Math.abs(hFactor)) / Math.LN2,
vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2,
newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff);
return newZoom;
};
map.connection = function(_) {
if (!arguments.length) return connection;
connection = _;
connection.on('load.tile', connectionLoad);
map.flush = function() {
context.connection().flush();
context.history().reset();
return map;
};
var usedTails = {};
map.tail = function (_) {
map.tail = function(_) {
if (!_ || usedTails[_] === undefined) {
tail.text(_);
usedTails[_] = true;
@@ -378,13 +362,6 @@ iD.Map = function() {
return map;
};
map.history = function (_) {
if (!arguments.length) return history;
history = _;
history.on('change.map', redraw);
return map;
};
map.background = background;
map.projection = projection;
map.redraw = redraw;
+2 -2
View File
@@ -40,7 +40,7 @@ iD.taginfo = function() {
}
function popularValues(parameters) {
return function(d) { return parseFloat(d['fraction']) > 0.01; };
return function(d) { return parseFloat(d.fraction) > 0.01; };
}
function valKey(d) { return { value: d.key }; }
@@ -76,7 +76,7 @@ iD.taginfo = function() {
page: 1
}, parameters)), function(err, d) {
if (err) return callback(err);
callback(null, d.data.filter(popularValues()).map(valKeyDescription));
callback(null, d.data.filter(popularValues()).map(valKeyDescription), parameters);
});
};
+26 -9
View File
@@ -1,19 +1,21 @@
iD.svg = {
RoundProjection: function (projection) {
return function (d) {
RoundProjection: function(projection) {
return function(d) {
return iD.geo.roundCoords(projection(d));
};
},
PointTransform: function (projection) {
return function (entity) {
return 'translate(' + projection(entity.loc) + ')';
PointTransform: function(projection) {
return function(entity) {
// http://jsperf.com/short-array-join
var pt = projection(entity.loc);
return 'translate(' + pt[0] + ',' + pt[1] + ')';
};
},
LineString: function (projection, graph) {
LineString: function(projection, graph) {
var cache = {};
return function (entity) {
return function(entity) {
if (cache[entity.id] !== undefined) {
return cache[entity.id];
}
@@ -23,7 +25,22 @@ iD.svg = {
}
return (cache[entity.id] =
'M' + graph.childNodes(entity).map(function (n) { return projection(n.loc); }).join('L'));
}
'M' + graph.childNodes(entity).map(function(n) {
var pt = projection(n.loc);
return pt[0] + ',' + pt[1];
}).join('L'));
};
},
MultipolygonMemberTags: function(graph) {
return function(entity) {
var tags = entity.tags;
graph.parentRelations(entity).forEach(function(relation) {
if (relation.isMultipolygon()) {
tags = _.extend({}, relation.tags, tags);
}
});
return tags;
};
}
};
+20 -14
View File
@@ -1,38 +1,39 @@
iD.svg.Areas = function(projection) {
return function drawAreas(surface, graph, entities, filter) {
var areas = [];
var path = d3.geo.path().projection(projection),
areas = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (entity.geometry(graph) === 'area') {
var points = graph.childNodes(entity).map(function(n) {
return projection(n.loc);
});
areas.push({
entity: entity,
area: entity.isDegenerate() ? 0 : Math.abs(d3.geom.polygon(points).area())
area: Math.abs(path.area(entity.asGeoJSON(graph)))
});
}
}
areas.sort(function(a, b) { return b.area - a.area; });
var lineString = iD.svg.LineString(projection, graph);
function drawPaths(group, areas, filter, klass) {
var tagClasses = iD.svg.TagClasses();
if (klass === 'stroke') {
tagClasses.tags(iD.svg.MultipolygonMemberTags(graph));
}
function drawPaths(group, areas, filter, classes) {
var paths = group.selectAll('path.area')
.filter(filter)
.data(areas, iD.Entity.key);
paths.enter()
.append('path')
.attr('class', classes);
.attr('class', function(d) { return d.type + ' area ' + klass; });
paths
.order()
.attr('d', lineString)
.call(iD.svg.TagClasses())
.attr('d', function(entity) { return path(entity.asGeoJSON(graph)); })
.call(tagClasses)
.call(iD.svg.MemberClasses(graph));
paths.exit()
@@ -43,9 +44,14 @@ iD.svg.Areas = function(projection) {
areas = _.pluck(areas, 'entity');
var strokes = areas.filter(function(area) {
return area.type === 'way';
});
var fill = surface.select('.layer-fill'),
stroke = surface.select('.layer-stroke'),
fills = drawPaths(fill, areas, filter, 'way area fill'),
strokes = drawPaths(stroke, areas, filter, 'way area stroke');
stroke = surface.select('.layer-stroke');
drawPaths(fill, areas, filter, 'fill');
drawPaths(stroke, strokes, filter, 'stroke');
};
};
+44 -20
View File
@@ -33,8 +33,11 @@ iD.svg.Labels = function(projection) {
var font_sizes = label_stack.map(function(d) {
var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]);
var m = style && style.cssText.match("font-size: ([0-9]{1,2})px;");
if (!m) return default_size;
return parseInt(m[1], 10);
if (m) return parseInt(m[1], 10);
style = iD.util.getStyle('text.' + d[0]);
m = style && style.cssText.match("font-size: ([0-9]{1,2})px;");
if (m) return parseInt(m[1], 10);
return default_size;
});
var pointOffsets = [
@@ -79,7 +82,7 @@ iD.svg.Labels = function(projection) {
'startOffset': '50%',
'xlink:href': function(d, i) { return '#halo-' + d.id; }
})
.text(function(d, i) { return d.tags.name; });
.text(function(d, i) { return name(d); });
texts.exit().remove();
@@ -116,14 +119,14 @@ iD.svg.Labels = function(projection) {
'x': function(d, i) {
var x = labels[i].x - 2;
if (labels[i].textAnchor === 'middle') {
x -= textWidth(d.tags.name, labels[i].height) / 2;
x -= textWidth(name(d), labels[i].height) / 2;
}
return x;
},
'y': function(d, i) { return labels[i].y - labels[i].height + 1 - 2; },
'rx': 3,
'ry': 3,
'width': function(d, i) { return textWidth(d.tags.name, labels[i].height) + 4; },
'width': function(d, i) { return textWidth(name(d), labels[i].height) + 4; },
'height': function(d, i) { return labels[i].height + 4; },
'fill': 'white'
});
@@ -146,8 +149,8 @@ iD.svg.Labels = function(projection) {
.attr('y', get(labels, 'y'))
.attr('transform', get(labels, 'transform'))
.style('text-anchor', get(labels, 'textAnchor'))
.text(function(d) { return d.tags.name; })
.each(function(d, i) { textWidth(d.tags.name, labels[i].height, this); });
.text(function(d) { return name(d); })
.each(function(d, i) { textWidth(name(d), labels[i].height, this); });
texts.exit().remove();
return texts;
@@ -200,33 +203,56 @@ iD.svg.Labels = function(projection) {
}
function hideOnMouseover() {
var mouse = d3.mouse(this),
var mouse = mousePosition(d3.event),
pad = 50,
rect = new RTree.Rectangle(mouse[0] - pad, mouse[1] - pad, 2*pad, 2*pad),
labels = _.pluck(rtree.search(rect, this), 'leaf'),
containsLabel = iD.util.trueObj(labels),
selection = d3.select(this);
// ensures that simply resetting opacity
// does not force style recalculation
function resetOpacity() {
if (this._opacity !== '') {
this.style.opacity = '';
this._opacity = '';
}
}
selection.selectAll('.layer-label text, .layer-halo path, .layer-halo rect')
.style('opacity', '');
.each(resetOpacity);
if (!labels.length) return;
selection.selectAll('.layer-label text, .layer-halo path, .layer-halo rect')
.filter(function(d) {
return _.contains(labels, d.id);
return containsLabel[d.id];
})
.style('opacity', 0);
.style('opacity', 0)
.property('_opacity', 0);
}
function name(d) {
return d.tags[lang] || d.tags.name;
}
var rtree = new RTree(),
rectangles = {};
rectangles = {},
lang = 'name:' + iD.detect().locale.toLowerCase().split('-')[0],
mousePosition, cacheDimensions;
return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) {
if (!mousePosition || dimensions.join(',') !== cacheDimensions) {
mousePosition = iD.util.fastMouse(surface.node().parentNode);
cacheDimensions = dimensions.join(',');
}
d3.select(surface.node().parentNode)
.on('mousemove.hidelabels', hideOnMouseover);
var hidePoints = !d3.select('.node.point').node();
var hidePoints = !surface.select('.node.point').node();
var labelable = [], i, k, entity;
for (i = 0; i < label_stack.length; i++) labelable.push([]);
@@ -243,7 +269,7 @@ iD.svg.Labels = function(projection) {
// Split entities into groups specified by label_stack
for (i = 0; i < entities.length; i++) {
entity = entities[i];
if (!entity.tags.name) continue;
if (!name(entity)) continue;
if (hidePoints && entity.geometry(graph) === 'point') continue;
for (k = 0; k < label_stack.length; k ++) {
if (entity.geometry(graph) === label_stack[k][0] &&
@@ -254,7 +280,6 @@ iD.svg.Labels = function(projection) {
}
}
var positions = {
point: [],
line: [],
@@ -272,7 +297,7 @@ iD.svg.Labels = function(projection) {
var font_size = font_sizes[k];
for (i = 0; i < labelable[k].length; i ++) {
entity = labelable[k][i];
var width = textWidth(entity.tags.name, font_size),
var width = textWidth(name(entity), font_size),
p;
if (entity.geometry(graph) === 'point') {
p = getPointLabel(entity, width, font_size);
@@ -282,7 +307,7 @@ iD.svg.Labels = function(projection) {
p = getAreaLabel(entity, width, font_size);
}
if (p) {
p.classes = entity.geometry(graph) + ' tag-' + label_stack[k].slice(1).join('-');
p.classes = entity.geometry(graph) + ' tag-' + label_stack[k][1];
positions[entity.geometry(graph)].push(p);
labelled[entity.geometry(graph)].push(entity);
}
@@ -333,9 +358,8 @@ iD.svg.Labels = function(projection) {
}
function getAreaLabel(entity, width, height) {
var nodes = _.pluck(graph.childNodes(entity), 'loc')
.map(iD.svg.RoundProjection(projection)),
centroid = d3.geom.polygon(nodes).centroid(),
var path = d3.geo.path().projection(projection),
centroid = path.centroid(entity.asGeoJSON(graph)),
extent = entity.extent(graph),
entitywidth = projection(extent[1])[0] - projection(extent[0])[0];
+21 -12
View File
@@ -1,6 +1,6 @@
iD.svg.Lines = function(projection) {
var arrowtext = '►\u3000\u3000',
var arrowtext = '►\u3000\u3000\u3000',
alength;
var highway_stack = {
@@ -34,19 +34,25 @@ iD.svg.Lines = function(projection) {
}
return function drawLines(surface, graph, entities, filter) {
function drawPaths(group, lines, filter, classes, lineString) {
function drawPaths(group, lines, filter, klass, lineString) {
var tagClasses = iD.svg.TagClasses();
if (klass === 'stroke') {
tagClasses.tags(iD.svg.MultipolygonMemberTags(graph));
}
var paths = group.selectAll('path.line')
.filter(filter)
.data(lines, iD.Entity.key);
paths.enter()
.append('path')
.attr('class', classes);
.attr('class', 'way line ' + klass);
paths
.order()
.attr('d', lineString)
.call(iD.svg.TagClasses())
.call(tagClasses)
.call(iD.svg.MemberClasses(graph));
paths.exit()
@@ -56,13 +62,16 @@ iD.svg.Lines = function(projection) {
}
if (!alength) {
var arrow = surface.append('text').text(arrowtext);
var container = surface.append('g')
.attr('class', 'oneway'),
arrow = container.append('text')
.attr('class', 'textpath')
.text(arrowtext);
alength = arrow.node().getComputedTextLength();
arrow.remove();
container.remove();
}
var lines = [],
lineStrings = {};
var lines = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
@@ -80,13 +89,13 @@ iD.svg.Lines = function(projection) {
stroke = surface.select('.layer-stroke'),
defs = surface.select('defs'),
text = surface.select('.layer-text'),
shadows = drawPaths(shadow, lines, filter, 'way line shadow', lineString),
casings = drawPaths(casing, lines, filter, 'way line casing', lineString),
strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString);
shadows = drawPaths(shadow, lines, filter, 'shadow', lineString),
casings = drawPaths(casing, lines, filter, 'casing', lineString),
strokes = drawPaths(stroke, lines, filter, 'stroke', lineString);
// Determine the lengths of oneway paths
var lengths = {},
oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) {
oneways = strokes.filter(function(d) { return d.isOneWay(); }).each(function(d) {
lengths[d.id] = Math.floor(this.getTotalLength() / alength);
}).data();
+1 -1
View File
@@ -17,7 +17,7 @@ iD.svg.MemberClasses = function(graph) {
classes += ' member';
}
relations.forEach(function (relation) {
relations.forEach(function(relation) {
classes += ' member-type-' + relation.tags.type;
classes += ' member-role-' + relation.memberById(d.id).role;
});
+9 -3
View File
@@ -2,6 +2,10 @@ iD.svg.Midpoints = function(projection) {
return function drawMidpoints(surface, graph, entities, filter) {
var midpoints = {};
if (!surface.select('.layer-hit g.vertex').node()) {
return surface.selectAll('.layer-hit g.midpoint').remove();
}
for (var i = 0; i < entities.length; i++) {
if (entities[i].type !== 'way') continue;
@@ -31,14 +35,14 @@ iD.svg.Midpoints = function(projection) {
var groups = surface.select('.layer-hit').selectAll('g.midpoint')
.filter(filter)
.data(_.values(midpoints), function (d) { return d.id; });
.data(_.values(midpoints), function(d) { return d.id; });
var group = groups.enter()
.insert('g', ':first-child')
.attr('class', 'midpoint');
group.append('circle')
.attr('r', 7)
.attr('r', 8)
.attr('class', 'shadow');
group.append('circle')
@@ -47,7 +51,9 @@ iD.svg.Midpoints = function(projection) {
groups.attr('transform', iD.svg.PointTransform(projection));
groups.select('circle');
// Propagate data bindings.
groups.select('circle.shadow');
groups.select('circle.fill');
groups.exit()
.remove();
-55
View File
@@ -1,55 +0,0 @@
iD.svg.Multipolygons = function(projection) {
return function(surface, graph, entities, filter) {
var multipolygons = [];
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (entity.geometry(graph) === 'relation' && entity.tags.type === 'multipolygon') {
multipolygons.push(entity);
}
}
var lineStrings = {};
function lineString(entity) {
if (lineStrings[entity.id] !== undefined) {
return lineStrings[entity.id];
}
var multipolygon = entity.multipolygon(graph);
if (entity.members.length === 0 || !multipolygon) {
return (lineStrings[entity.id] = null);
}
multipolygon = _.flatten(multipolygon, true);
return (lineStrings[entity.id] =
multipolygon.map(function (ring) {
return 'M' + ring.map(function (node) { return projection(node.loc); }).join('L');
}).join(""));
}
function drawPaths(group, multipolygons, filter, classes) {
var paths = group.selectAll('path.multipolygon')
.filter(filter)
.data(multipolygons, iD.Entity.key);
paths.enter()
.append('path')
.attr('class', classes);
paths
.order()
.attr('d', lineString)
.call(iD.svg.TagClasses())
.call(iD.svg.MemberClasses(graph));
paths.exit()
.remove();
return paths;
}
var fill = surface.select('.layer-fill'),
paths = drawPaths(fill, multipolygons, filter, 'relation multipolygon');
};
};
+2 -1
View File
@@ -52,6 +52,7 @@ iD.svg.Points = function(projection) {
// sets the data (point entity) on the element
groups.select('image')
.attr('xlink:href', imageHref);
groups.select('.shadow, .stroke');
groups.exit()
.remove();
@@ -79,7 +80,7 @@ iD.svg.Points.imageIndex = [
},
{
tags: { man_made: 'lighthouse' },
icon: 'lighthouselevel_crossing'
icon: 'lighthouse'
},
{
tags: { natural: 'peak' },
+1 -1
View File
@@ -3,7 +3,7 @@ iD.svg.Surface = function() {
selection.append('defs');
var layers = selection.selectAll('.layer')
.data(['shadow', 'fill', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']);
.data(['fill', 'shadow', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']);
layers.enter().append('g')
.attr('class', function(d) { return 'layer layer-' + d; });
+15 -7
View File
@@ -3,10 +3,11 @@ iD.svg.TagClasses = function() {
'highway', 'railway', 'waterway', 'power', 'motorway', 'amenity',
'natural', 'landuse', 'building', 'oneway', 'bridge', 'boundary',
'leisure', 'construction'
]), tagClassRe = /^tag-/;
]), tagClassRe = /^tag-/,
tags = function(entity) { return entity.tags; };
return function tagClassesSelection(selection) {
selection.each(function tagClassesEach(d, i) {
var tagClasses = function(selection) {
selection.each(function tagClassesEach(entity) {
var classes, value = this.className;
if (value.baseVal !== undefined) value = value.baseVal;
@@ -15,11 +16,10 @@ iD.svg.TagClasses = function() {
return name.length && !tagClassRe.test(name);
}).join(' ');
var tags = d.tags;
for (var k in tags) {
var t = tags(entity);
for (var k in t) {
if (!keys[k]) continue;
classes += ' tag-' + k + ' ' +
'tag-' + k + '-' + tags[k];
classes += ' tag-' + k + ' ' + 'tag-' + k + '-' + t[k];
}
classes = classes.trim();
@@ -29,4 +29,12 @@ iD.svg.TagClasses = function() {
}
});
};
tagClasses.tags = function(_) {
if (!arguments.length) return tags;
tags = _;
return tagClasses;
};
return tagClasses;
};
+5 -6
View File
@@ -23,15 +23,15 @@ iD.svg.Vertices = function(projection) {
group.append('circle')
.attr('r', 10)
.attr('class', 'shadow');
.attr('class', 'node vertex shadow');
group.append('circle')
.attr('r', 6)
.attr('class', 'stroke');
.attr('r', 4)
.attr('class', 'node vertex stroke');
group.append('circle')
.attr('r', 3)
.attr('class', 'fill');
.attr('class', 'node vertex fill');
groups.attr('transform', iD.svg.PointTransform(projection))
.call(iD.svg.TagClasses())
@@ -40,8 +40,7 @@ iD.svg.Vertices = function(projection) {
// Selecting the following implicitly
// sets the data (vertix entity) on the elements
groups.select('circle.fill');
groups.select('circle.stroke');
groups.select('circle.fill, circle.stroke, circle.shadow');
groups.exit()
.remove();
+144 -1
View File
@@ -1 +1,144 @@
iD.ui = {};
iD.ui = function(context) {
return function(container) {
context.container(container);
var history = context.history(),
map = context.map();
if (!iD.detect().support) {
container
.text(t('browser_notice'))
.style({
'text-align': 'center',
'font-style': 'italic'
});
return;
}
if (iD.detect().opera) container.classed('opera', true);
var m = container.append('div')
.attr('id', 'map')
.call(map);
var bar = container.append('div')
.attr('id', 'bar')
.attr('class','pad1 fillD');
var limiter = bar.append('div')
.attr('class', 'limiter');
limiter.append('div')
.attr('class', 'button-wrap joined col4')
.call(iD.ui.Modes(context), limiter);
limiter.append('div')
.attr('class', 'button-wrap joined col1')
.call(iD.ui.UndoRedo(context));
limiter.append('div')
.attr('class', 'button-wrap col1')
.call(iD.ui.Save(context));
container.append('div')
.attr('class', 'map-control zoombuttons')
.call(iD.ui.Zoom(context));
container.append('div')
.attr('class', 'map-control geocode-control')
.call(iD.ui.Geocoder(context));
container.append('div')
.attr('class', 'map-control layerswitcher-control')
.call(iD.ui.LayerSwitcher(context));
container.append('div')
.attr('class', 'map-control geolocate-control')
.call(iD.ui.Geolocate(map));
container.append('div')
.style('display', 'none')
.attr('class', 'inspector-wrap fr col5');
var about = container.append('div')
.attr('class','col12 about-block fillD pad1');
about.append('div')
.attr('class', 'account')
.call(iD.ui.Account(context));
var linkList = about.append('ul')
.attr('id', 'about')
.attr('class', 'pad1 fillD about-block link-list');
linkList.append('li')
.append('a')
.attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD')
.text(iD.version);
linkList.append('li')
.append('a')
.attr('target', '_blank')
.attr('href', 'http://github.com/systemed/iD/issues')
.text(t('report_a_bug'));
linkList.append('li')
.attr('class', 'attribution')
.call(iD.ui.Attribution(context));
linkList.append('li')
.attr('class', 'source-switch')
.call(iD.ui.SourceSwitch(context));
linkList.append('li')
.attr('class', 'user-list')
.call(iD.ui.Contributors(context));
window.onbeforeunload = function() {
history.save();
if (history.hasChanges()) return t('save.unsaved_changes');
};
d3.select(window).on('resize.editor', function() {
map.size(m.size());
});
function pan(d) {
return function() {
context.pan(d);
};
}
// pan amount
var pa = 5;
var keybinding = d3.keybinding('main')
.on('⌫', function() { d3.event.preventDefault(); })
.on('←', pan([pa, 0]))
.on('↑', pan([0, pa]))
.on('→', pan([-pa, 0]))
.on('↓', pan([0, -pa]));
d3.select(document)
.call(keybinding);
var hash = iD.behavior.Hash(context);
hash();
if (!hash.hadHash) {
map.centerZoom([-77.02271, 38.90085], 20);
}
context.enter(iD.modes.Browse(context));
context.container()
.call(iD.ui.Splash(context))
.call(iD.ui.Restore(context));
};
};
iD.ui.tooltipHtml = function(text, key) {
return '<span>' + text + '</span>' + '<div class="keyhint-wrap"><span class="keyhint"> ' + key + '</span></div>';
};
+53
View File
@@ -0,0 +1,53 @@
iD.ui.Account = function(context) {
var connection = context.connection();
function update(selection) {
if (!connection.authenticated()) {
selection.html('')
.style('display', 'none');
return;
}
selection.style('display', 'block');
connection.userDetails(function(err, details) {
selection.html('');
if (err) return;
// Link
var userLink = selection.append('a')
.attr('href', connection.url() + '/user/' + details.display_name)
.attr('target', '_blank');
// Add thumbnail or dont
if (details.image_url) {
userLink.append('img')
.attr('class', 'icon icon-pre-text user-icon')
.attr('src', details.image_url);
} else {
userLink.append('span')
.attr('class', 'icon avatar icon-pre-text');
}
// Add user name
userLink.append('span')
.attr('class', 'label')
.text(details.display_name);
selection.append('a')
.attr('class', 'logout')
.attr('href', '#')
.text(t('logout'))
.on('click.logout', function() {
d3.event.preventDefault();
connection.logout();
});
});
}
return function(selection) {
connection.on('auth', function() { update(selection); });
update(selection);
};
};
+10
View File
@@ -0,0 +1,10 @@
iD.ui.Attribution = function(context) {
return function(selection) {
selection.append('span')
.text('imagery');
selection
.append('span')
.attr('class', 'provided-by');
}
};
+22
View File
@@ -0,0 +1,22 @@
// Translate a MacOS key command into the appropriate Windows/Linux equivalent.
// For example, ⌘Z -> Ctrl+Z
iD.ui.cmd = function(code) {
if (iD.detect().os === 'mac')
return code;
var modifiers = {
'⌘': 'Ctrl',
'⇧': 'Shift',
'⌥': 'Alt'
}, keys = [];
for (var i = 0; i < code.length; i++) {
if (code[i] in modifiers) {
keys.push(modifiers[code[i]]);
} else {
keys.push(code[i]);
}
}
return keys.join('+');
};
+56 -49
View File
@@ -1,4 +1,4 @@
iD.ui.commit = function(map) {
iD.ui.Commit = function(context) {
var event = d3.dispatch('cancel', 'save', 'fix');
function zipSame(d) {
@@ -31,110 +31,117 @@ iD.ui.commit = function(map) {
header = selection.append('div').attr('class', 'header modal-section fillL'),
body = selection.append('div').attr('class', 'body');
header.append('h2').text('Save Changes');
header.append('h2')
.text(t('commit.title'));
// Comment Box
var comment_section = body.append('div').attr('class','modal-section fillD');
comment_section.append('textarea')
var commentSection = body.append('div')
.attr('class', 'modal-section fillD');
var commentField = commentSection.append('textarea')
.attr('class', 'changeset-comment')
.attr('placeholder', 'Brief Description of your contributions')
.property('value', localStorage.comment || '')
.node().select();
.attr('placeholder', t('commit.description_placeholder'))
.property('value', context.storage('comment') || '');
var commit_info =
comment_section
.append('p')
.attr('class','commit-info');
commentField.node().select();
commit_info.append('span').text('The changes you upload as ');
var userLink = d3.select(document.createElement('div'));
var user_link = commit_info.append('a')
.attr('class','user-info')
.text(user.display_name)
.attr('href', connection.url() + '/user/' + user.display_name)
.attr('target', '_blank');
commit_info.append('span').text(' will be visible on all maps that use OpenStreetMap data:');
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) {
user_link
.append('img')
.attr('src', user.image_url)
.attr('class', 'icon icon-pre-text user-icon');
userLink.append('img')
.attr('src', user.image_url)
.attr('class', 'icon icon-pre-text user-icon');
}
// Confirm / Cancel Buttons
var buttonwrap = comment_section.append('div')
.attr('class', 'buttons cf')
.append('div')
.attr('class', 'button-wrap joined col4');
commentSection.append('p')
.attr('class', 'commit-info')
.html(t('commit.upload_explanation', {user: userLink.html()}));
var savebutton = buttonwrap
.append('button')
// Confirm / Cancel Buttons
var buttonWrap = commentSection.append('div')
.attr('class', 'buttons cf')
.append('div')
.attr('class', 'button-wrap joined col4');
var saveButton = buttonWrap.append('button')
.attr('class', 'save action col6 button')
.on('click.save', function() {
var comment = d3.select('textarea.changeset-comment').node().value;
var comment = commentField.node().value;
localStorage.comment = comment;
event.save({
comment: comment
});
});
savebutton.append('span').attr('class','label').text('Save');
var cancelbutton = buttonwrap.append('button')
saveButton.append('span')
.attr('class', 'label')
.text(t('commit.save'));
var cancelButton = buttonWrap.append('button')
.attr('class', 'cancel col6 button')
.on('click.cancel', function() {
event.cancel();
});
cancelbutton.append('span').attr('class','label').text('Cancel');
cancelButton.append('span')
.attr('class', 'label')
.text(t('commit.cancel'));
var warnings = body.selectAll('div.warning-section')
.data(iD.validate(changes, map.history().graph()))
.data(iD.validate(changes, context.graph()))
.enter()
.append('div').attr('class', 'modal-section warning-section fillL');
.append('div')
.attr('class', 'modal-section warning-section fillL');
warnings.append('h3')
.text('Warnings');
.text(t('commit.warnings'));
var warning_li = warnings.append('ul')
var warningLi = warnings.append('ul')
.attr('class', 'changeset-list')
.selectAll('li')
.data(function(d) { return d; })
.enter()
.append('li');
warning_li.append('button')
warningLi.append('button')
.attr('class', 'minor')
.on('click', event.fix)
.append('span')
.attr('class', 'icon warning');
warning_li.append('strong').text(function(d) {
warningLi.append('strong').text(function(d) {
return d.message;
});
var section = body.selectAll('div.commit-section')
.data(['modified', 'deleted', 'created'].filter(changesLength))
.enter()
.append('div').attr('class', 'commit-section modal-section fillL2');
.append('div')
.attr('class', 'commit-section modal-section fillL2');
section.append('h3').text(function(d) {
return d.charAt(0).toUpperCase() + d.slice(1);
})
section.append('h3')
.text(function(d) { return t('commit.' + d); })
.append('small')
.attr('class', 'count')
.text(changesLength);
var li = section.append('ul')
.attr('class','changeset-list')
.attr('class', 'changeset-list')
.selectAll('li')
.data(function(d) { return zipSame(changes[d]); })
.enter()
.append('li');
li.append('strong').text(function(d) {
return (d.count > 1) ? d.type + 's ' : d.type + ' ';
});
li.append('strong')
.text(function(d) {
return (d.count > 1) ? d.type + 's ' : d.type + ' ';
});
li.append('span')
.text(function(d) { return d.name; })
.attr('title', function(d) { return d.tagText; });
+2 -2
View File
@@ -1,5 +1,5 @@
iD.ui.confirm = function() {
var modal = iD.ui.modal();
iD.ui.confirm = function(selection) {
var modal = iD.ui.modal(selection);
modal.select('.modal').classed('modal-alert', true);
modal.select('.content')
.attr('class','modal-section fillD')

Some files were not shown because too many files have changed in this diff Show More