diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..09dbab7ba
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -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
+
+```
+
+## 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.
diff --git a/README.md b/README.md
index 711cb5418..08c6764c0 100644
--- a/README.md
+++ b/README.md
@@ -16,8 +16,7 @@
## 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.
+* [Read up on Contributing and the code style of iD](CONTRIBUTING.md)
* See [open issues in the issue tracker if you're looking for something to do](https://github.com/systemed/iD/issues?state=open)
To run the code locally, just fork this project and run it from a local webserver.
diff --git a/index.html b/index.html
index f9b627c16..83ae066eb 100644
--- a/index.html
+++ b/index.html
@@ -85,6 +85,7 @@
+
@@ -112,6 +113,7 @@
+
diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js
new file mode 100644
index 000000000..068db55bd
--- /dev/null
+++ b/js/id/actions/orthogonalize.js
@@ -0,0 +1,132 @@
+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 = [], 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) {
+ return graph;
+ }
+ score = newScore;
+ if (score < 1.0e-8) {
+ break;
+ }
+ }
+
+ for (i = 0; i < points.length - 1; i++) {
+ quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) }));
+ }
+
+ for (i = 0; i < nodes.length; i++) {
+ if (graph.parentWays(nodes[i]).length > 1) {
+ var closest, closest_dist = Infinity, dist;
+ for (j = 0; j < quad_nodes.length; j++) {
+ dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc);
+ if (dist < closest_dist) {
+ closest_dist = dist;
+ closest = j;
+ }
+ }
+ quad_nodes.splice(closest, 1, nodes[i]);
+ }
+ }
+
+ for (i = 0; i < quad_nodes.length; i++) {
+ graph = graph.replace(quad_nodes[i]);
+ }
+
+ var ids = _.pluck(quad_nodes, 'id'),
+ difference = _.difference(_.uniq(way.nodes), ids);
+
+ ids.push(ids[0]);
+
+ graph = graph.replace(way.update({nodes: ids}));
+
+ for (i = 0; i < difference.length; i++) {
+ graph = iD.actions.DeleteNode(difference[i])(graph);
+ }
+
+ 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 = p.length + q.length;
+ 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;
+};
diff --git a/js/id/actions/split.js b/js/id/actions/split.js
index 19a71c619..24f2b4318 100644
--- a/js/id/actions/split.js
+++ b/js/id/actions/split.js
@@ -17,7 +17,7 @@ iD.actions.Split = function(nodeId, newWayId) {
return parents.filter(function (parent) {
return parent.first() !== nodeId &&
parent.last() !== nodeId;
- })
+ });
}
var action = function(graph) {
diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js
index 42099963c..4e39be2c6 100644
--- a/js/id/behavior/drag_midpoint.js
+++ b/js/id/behavior/drag_midpoint.js
@@ -9,7 +9,7 @@ iD.behavior.DragMidpoint = function(context) {
context.perform(iD.actions.AddMidpoint(d, node));
- var vertex = d3.selectAll('.vertex')
+ var vertex = context.surface().selectAll('.vertex')
.filter(function(data) { return data.id === node.id; });
behavior.target(vertex.node(), vertex.datum());
diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js
index 3089bd6ee..1cc52ab1f 100644
--- a/js/id/behavior/draw_way.js
+++ b/js/id/behavior/draw_way.js
@@ -82,7 +82,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) {
return graph
.replace(way.removeNode(nodeId).addNode(newNode.id, index))
.remove(node);
- }
+ };
}
// Accept the current position of the temporary node and continue drawing.
diff --git a/js/id/graph/history.js b/js/id/graph/history.js
index 3cc1ace01..7d1098027 100644
--- a/js/id/graph/history.js
+++ b/js/id/graph/history.js
@@ -127,7 +127,7 @@ iD.History = function() {
modified: difference.modified(),
created: difference.created(),
deleted: difference.deleted()
- }
+ };
},
hasChanges: function() {
diff --git a/js/id/graph/node.js b/js/id/graph/node.js
index b9a171752..56fc14eab 100644
--- a/js/id/graph/node.js
+++ b/js/id/graph/node.js
@@ -47,6 +47,6 @@ _.extend(iD.Node.prototype, {
type: 'Point',
coordinates: this.loc
}
- }
+ };
}
});
diff --git a/js/id/operations.js b/js/id/operations.js
index 2786d046f..a72fe1d82 100644
--- a/js/id/operations.js
+++ b/js/id/operations.js
@@ -1 +1 @@
-iD.operations = {}
+iD.operations = {};
diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js
new file mode 100644
index 000000000..c59ed13e4
--- /dev/null
+++ b/js/id/operations/orthogonalize.js
@@ -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;
+};
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index b2561f21f..19354d7f3 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -49,6 +49,7 @@ iD.Map = function(context) {
map.size(selection.size());
map.surface = surface;
+ map.tilesurface = tilegroup;
supersurface
.call(tail);
diff --git a/js/id/services/taginfo.js b/js/id/services/taginfo.js
index aa89a37fa..fc4e72542 100644
--- a/js/id/services/taginfo.js
+++ b/js/id/services/taginfo.js
@@ -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 }; }
diff --git a/js/id/svg.js b/js/id/svg.js
index 5b07b3a53..aadb6b830 100644
--- a/js/id/svg.js
+++ b/js/id/svg.js
@@ -1,19 +1,19 @@
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) {
+ PointTransform: function(projection) {
+ return function(entity) {
return 'translate(' + projection(entity.loc) + ')';
};
},
- 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 +23,9 @@ 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) {
+ return projection(n.loc);
+ }).join('L'));
+ };
}
};
diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js
index eb4297bca..551c29e81 100644
--- a/js/id/svg/labels.js
+++ b/js/id/svg/labels.js
@@ -226,7 +226,7 @@ iD.svg.Labels = function(projection) {
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([]);
@@ -254,7 +254,6 @@ iD.svg.Labels = function(projection) {
}
}
-
var positions = {
point: [],
line: [],
diff --git a/js/id/ui.js b/js/id/ui.js
index 87cc3745a..69f20c2fd 100644
--- a/js/id/ui.js
+++ b/js/id/ui.js
@@ -140,8 +140,10 @@ iD.ui = function (context) {
var about = container.append('div')
.attr('class','col12 about-block fillD pad1');
- about.append('div')
- .attr('class', 'user-container')
+ var userContainer = about.append('div')
+ .attr('class', 'user-container');
+
+ userContainer
.append('div')
.attr('class', 'hello');
@@ -243,7 +245,7 @@ iD.ui = function (context) {
map.centerZoom([-77.02271, 38.90085], 20);
}
- d3.select('.user-container').call(iD.ui.userpanel(connection)
+ userContainer.call(iD.ui.userpanel(connection)
.on('logout.editor', connection.logout)
.on('login.editor', connection.authenticate));
diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js
index 09bb99404..8d724a732 100644
--- a/js/id/ui/commit.js
+++ b/js/id/ui/commit.js
@@ -35,11 +35,12 @@ iD.ui.commit = function(context) {
// Comment Box
var comment_section = body.append('div').attr('class','modal-section fillD');
- comment_section.append('textarea')
+ var commentField = comment_section.append('textarea')
.attr('class', 'changeset-comment')
.attr('placeholder', 'Brief Description of your contributions')
- .property('value', context.storage('comment') || '')
- .node().select();
+ .property('value', context.storage('comment') || '');
+
+ commentField.node().select();
var commit_info =
comment_section
@@ -73,7 +74,7 @@ iD.ui.commit = function(context) {
.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
diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js
index 4aac10bf2..063e8b517 100644
--- a/js/id/ui/geocoder.js
+++ b/js/id/ui/geocoder.js
@@ -38,9 +38,8 @@ iD.ui.geocoder = function() {
function setVisible(show) {
button.classed('active', show);
gcForm.classed('hide', !show);
- var input_node = d3.select('.map-overlay input').node();
- if (show) input_node.focus();
- else input_node.blur();
+ if (show) inputNode.node().focus();
+ else inputNode.node().blur();
}
var button = selection.append('button')
@@ -51,7 +50,7 @@ iD.ui.geocoder = function() {
var gcForm = selection.append('form');
- gcForm.attr('class','content fillD map-overlay hide')
+ var inputNode = gcForm.attr('class','content fillD map-overlay hide')
.append('input')
.attr({ type: 'text', placeholder: t('geocoder.find_a_place') })
.on('keydown', keydown);
diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js
index 5c4835757..9e5e4dd4f 100644
--- a/js/id/ui/layerswitcher.js
+++ b/js/id/ui/layerswitcher.js
@@ -61,9 +61,10 @@ iD.ui.layerswitcher = function(context) {
opa.append('h4').text(t('layerswitcher.layers'));
- opa.append('ul')
- .attr('class', 'opacity-options')
- .selectAll('div.opacity')
+ var opacityList = opa.append('ul')
+ .attr('class', 'opacity-options');
+
+ opacityList.selectAll('div.opacity')
.data(opacities)
.enter()
.append('li')
@@ -71,11 +72,11 @@ iD.ui.layerswitcher = function(context) {
return t('layerswitcher.percent_brightness', { opacity: (d * 100) });
})
.on('click.set-opacity', function(d) {
- d3.select('#tile-g')
+ context.map().tilesurface
.transition()
.style('opacity', d)
.attr('data-opacity', d);
- d3.selectAll('.opacity-options li')
+ opacityList.selectAll('li')
.classed('selected', false);
d3.select(this)
.classed('selected', true);
diff --git a/js/id/ui/save.js b/js/id/ui/save.js
index ba217adcf..773ae4d50 100644
--- a/js/id/ui/save.js
+++ b/js/id/ui/save.js
@@ -14,6 +14,7 @@ iD.ui.save = function(context) {
.on('click', function() {
function commit(e) {
+
d3.select('.shaded').remove();
var l = iD.ui.loading(t('uploading_changes'), true);
connection.putChangeset(history.changes(), e.comment, history.imagery_used(), function(err, changeset_id) {
@@ -40,6 +41,7 @@ iD.ui.save = function(context) {
}));
}
});
+
}
if (history.hasChanges()) {
diff --git a/js/id/ui/splash.js b/js/id/ui/splash.js
index 43489fab1..a4d2a915e 100644
--- a/js/id/ui/splash.js
+++ b/js/id/ui/splash.js
@@ -2,15 +2,20 @@ iD.ui.splash = function() {
var modal = iD.ui.modal();
modal.select('.modal')
- .attr('class', 'modal-splash modal')
+ .attr('class', 'modal-splash modal');
var introModal = modal.select('.content')
.append('div')
.attr('class', 'modal-section fillL');
- introModal.append('div').attr('class','logo');
+ introModal.append('div')
+ .attr('class','logo');
- introModal.append('div').html("Welcome to the iD OpenStreetMap editor
This is development version 0.0.0-alpha1. For more information see ideditor.com and report bugs at github.com.systemed/iD.
");
+ introModal.append('div')
+ .html("Welcome to the iD OpenStreetMap editor
" +
+ "This is development version 0.0.0-alpha1. " +
+ "For more information see ideditor.com" +
+ " and report bugs at github.com.systemed/iD.
");
return modal;
-};
\ No newline at end of file
+};
diff --git a/locale/en.js b/locale/en.js
index 866c7eca3..31d6a13de 100644
--- a/locale/en.js
+++ b/locale/en.js
@@ -65,6 +65,15 @@ locale.en = {
area: "Made an area circular."
}
},
+ orthogonalize: {
+ title: "Orthogonalize",
+ description: "Square these corners.",
+ key: "Q",
+ annotation: {
+ line: "Squared the corners of a line.",
+ area: "Squared the corners of an area."
+ }
+ },
delete: {
title: "Delete",
description: "Remove this from the map.",
diff --git a/test/index.html b/test/index.html
index b6d925923..551a87621 100644
--- a/test/index.html
+++ b/test/index.html
@@ -73,6 +73,7 @@
+
@@ -108,6 +109,7 @@
+