]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/iD/iD.js
Update to iD v1.5.0
[rails.git] / vendor / assets / iD / iD.js
index 2ecfaf5255d49a26f4206910d7b50f3f84eeea81..a28af85618ad5d1fe07483674f51189c5f0f164d 100644 (file)
@@ -7050,7 +7050,7 @@ var JXON = new (function () {
 /**
  * @license
  * Lo-Dash 2.3.0 (Custom Build) <http://lodash.com/>
- * Build: `lodash --debug --output js/lib/lodash.js include="any,assign,bind,clone,compact,contains,debounce,difference,each,every,extend,filter,find,first,forEach,groupBy,indexOf,intersection,isEmpty,isEqual,isFunction,keys,last,map,omit,pairs,pluck,reject,some,throttle,union,uniq,unique,values,without,flatten,value,chain,cloneDeep,merge,pick" exports="global,node"`
+ * Build: `lodash --debug --output js/lib/lodash.js include="any,assign,bind,clone,compact,contains,debounce,difference,each,every,extend,filter,find,first,forEach,groupBy,indexOf,intersection,isEmpty,isEqual,isFunction,keys,last,map,omit,pairs,pluck,reject,some,throttle,union,uniq,unique,values,without,flatten,value,chain,cloneDeep,merge,pick,reduce" exports="global,node"`
  * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
  * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
  * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
@@ -9833,6 +9833,60 @@ var JXON = new (function () {
    */
   var pluck = map;
 
+  /**
+   * Reduces a collection to a value which is the accumulated result of running
+   * each element in the collection through the callback, where each successive
+   * callback execution consumes the return value of the previous execution. If
+   * `accumulator` is not provided the first element of the collection will be
+   * used as the initial `accumulator` value. The callback is bound to `thisArg`
+   * and invoked with four arguments; (accumulator, value, index|key, collection).
+   *
+   * @static
+   * @memberOf _
+   * @alias foldl, inject
+   * @category Collections
+   * @param {Array|Object|string} collection The collection to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {*} [accumulator] Initial value of the accumulator.
+   * @param {*} [thisArg] The `this` binding of `callback`.
+   * @returns {*} Returns the accumulated value.
+   * @example
+   *
+   * var sum = _.reduce([1, 2, 3], function(sum, num) {
+   *   return sum + num;
+   * });
+   * // => 6
+   *
+   * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+   *   result[key] = num * 3;
+   *   return result;
+   * }, {});
+   * // => { 'a': 3, 'b': 6, 'c': 9 }
+   */
+  function reduce(collection, callback, accumulator, thisArg) {
+    var noaccum = arguments.length < 3;
+    callback = lodash.createCallback(callback, thisArg, 4);
+
+    if (isArray(collection)) {
+      var index = -1,
+          length = collection.length;
+
+      if (noaccum) {
+        accumulator = collection[++index];
+      }
+      while (++index < length) {
+        accumulator = callback(accumulator, collection[index], index, collection);
+      }
+    } else {
+      baseEach(collection, function(value, index, collection) {
+        accumulator = noaccum
+          ? (noaccum = false, value)
+          : callback(accumulator, value, index, collection)
+      });
+    }
+    return accumulator;
+  }
+
   /**
    * The opposite of `_.filter` this method returns the elements of a
    * collection that the callback does **not** return truey for.
@@ -10970,6 +11024,7 @@ var JXON = new (function () {
   lodash.isString = isString;
   lodash.mixin = mixin;
   lodash.noop = noop;
+  lodash.reduce = reduce;
   lodash.some = some;
   lodash.sortedIndex = sortedIndex;
 
@@ -10978,7 +11033,9 @@ var JXON = new (function () {
   lodash.any = some;
   lodash.detect = find;
   lodash.findWhere = find;
+  lodash.foldl = reduce;
   lodash.include = contains;
+  lodash.inject = reduce;
 
   forOwn(lodash, function(func, methodName) {
     if (!lodash.prototype[methodName]) {
@@ -14658,7 +14715,79 @@ if (typeof define === 'function' && define.amd) {
 }
 
 })();
-toGeoJSON = (function() {
+(function(e){if("function"==typeof bootstrap)bootstrap("sexagesimal",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeSexagesimal=e}else"undefined"!=typeof window?window.sexagesimal=e():global.sexagesimal=e()})(function(){var define,ses,bootstrap,module,exports;
+return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+module.exports = element;
+module.exports.pair = pair;
+module.exports.format = format;
+module.exports.formatPair = formatPair;
+
+function element(x, dims) {
+    return search(x, dims).val;
+}
+
+function formatPair(x) {
+    return format(x.lat, 'lat') + ' ' + format(x.lon, 'lon');
+}
+
+// Is 0 North or South?
+function format(x, dim) {
+    var dirs = {
+            lat: ['N', 'S'],
+            lon: ['E', 'W']
+        }[dim] || '',
+        dir = dirs[x >= 0 ? 0 : 1],
+        abs = Math.abs(x),
+        whole = Math.floor(abs),
+        fraction = abs - whole,
+        fractionMinutes = fraction * 60,
+        minutes = Math.floor(fractionMinutes),
+        seconds = Math.floor((fractionMinutes - minutes) * 60);
+
+    return whole + '° ' +
+        (minutes ? minutes + "' " : '') +
+        (seconds ? seconds + '" ' : '') + dir;
+}
+
+function search(x, dims, r) {
+    if (!dims) dims = 'NSEW';
+    if (typeof x !== 'string') return { val: null, regex: r };
+    r = r || /[\s\,]*([\-|\—|\―]?[0-9.]+)°? *(?:([0-9.]+)['’′‘] *)?(?:([0-9.]+)(?:''|"|”|″) *)?([NSEW])?/gi;
+    var m = r.exec(x);
+    if (!m) return { val: null, regex: r };
+    else if (m[4] && dims.indexOf(m[4]) === -1) return { val: null, regex: r };
+    else return {
+        val: (((m[1]) ? parseFloat(m[1]) : 0) +
+            ((m[2] ? parseFloat(m[2]) / 60 : 0)) +
+            ((m[3] ? parseFloat(m[3]) / 3600 : 0))) *
+            ((m[4] && m[4] === 'S' || m[4] === 'W') ? -1 : 1),
+        regex: r,
+        raw: m[0],
+        dim: m[4]
+    };
+}
+
+function pair(x, dims) {
+    x = x.trim();
+    var one = search(x, dims);
+    if (one.val === null) return null;
+    var two = search(x, dims, one.regex);
+    if (two.val === null) return null;
+    // null if one/two are not contiguous.
+    if (one.raw + two.raw !== x) return null;
+    if (one.dim) return swapdim(one.val, two.val, one.dim);
+    else return [one.val, two.val];
+}
+
+function swapdim(a, b, dim) {
+    if (dim == 'N' || dim == 'S') return [a, b];
+    if (dim == 'W' || dim == 'E') return [b, a];
+}
+
+},{}]},{},[1])
+(1)
+});
+;toGeoJSON = (function() {
     'use strict';
 
     var removeSpace = (/\s*/g),
@@ -16278,7 +16407,7 @@ window.iD = function () {
     return d3.rebind(context, dispatch, 'on');
 };
 
-iD.version = '1.4.0';
+iD.version = '1.5.0';
 
 (function() {
     var detected = {};
@@ -16312,6 +16441,44 @@ iD.version = '1.4.0';
 
     iD.detect = function() { return detected; };
 })();
+iD.countryCode  = function() {
+    var countryCode = {},
+        endpoint = 'http://nominatim.openstreetmap.org/reverse?';
+
+    if (!iD.countryCode.cache) {
+        iD.countryCode.cache = rbush();
+    }
+
+    var cache = iD.countryCode.cache;
+
+    countryCode.search = function(location, callback) {
+        var countryCodes = cache.search([location[0], location[1], location[0], location[1]]);
+
+        if (countryCodes.length > 0)
+            return callback(null, countryCodes[0][4]);
+
+        d3.json(endpoint +
+            iD.util.qsString({
+                format: 'json',
+                addressdetails: 1,
+                lat: location[1],
+                lon: location[0]
+            }), function(err, result) {
+                if (err)
+                    return callback(err);
+                else if (result && result.error)
+                    return callback(result.error);
+
+                var extent = iD.geo.Extent(location).padByMeters(1000);
+
+                cache.insert([extent[0][0], extent[0][1], extent[1][0], extent[1][1], result.address.country_code]);
+
+                callback(null, result.address.country_code);
+            });
+    };
+
+    return countryCode;
+};
 iD.taginfo = function() {
     var taginfo = {},
         endpoint = 'https://taginfo.openstreetmap.org/api/4/',
@@ -16761,11 +16928,38 @@ iD.geo.euclideanDistance = function(a, b) {
     var x = a[0] - b[0], y = a[1] - b[1];
     return Math.sqrt((x * x) + (y * y));
 };
+
+// using WGS84 polar radius (6356752.314245179 m)
+// const = 2 * PI * r / 360
+iD.geo.latToMeters = function(dLat) {
+    return dLat * 110946.257617;
+};
+
+// using WGS84 equatorial radius (6378137.0 m)
+// const = 2 * PI * r / 360
+iD.geo.lonToMeters = function(dLon, atLat) {
+    return Math.abs(atLat) >= 90 ? 0 :
+        dLon * 111319.490793 * Math.abs(Math.cos(atLat * (Math.PI/180)));
+};
+
+// using WGS84 polar radius (6356752.314245179 m)
+// const = 2 * PI * r / 360
+iD.geo.metersToLat = function(m) {
+    return m / 110946.257617;
+};
+
+// using WGS84 equatorial radius (6378137.0 m)
+// const = 2 * PI * r / 360
+iD.geo.metersToLon = function(m, atLat) {
+    return Math.abs(atLat) >= 90 ? 0 :
+        m / 111319.490793 / Math.abs(Math.cos(atLat * (Math.PI/180)));
+};
+
 // Equirectangular approximation of spherical distances on Earth
 iD.geo.sphericalDistance = function(a, b) {
-    var x = Math.cos(a[1]*Math.PI/180) * (a[0] - b[0]),
-        y = a[1] - b[1];
-    return 6.3710E6 * Math.sqrt((x * x) + (y * y)) * Math.PI/180;
+    var x = iD.geo.lonToMeters(a[0] - b[0], (a[1] + b[1]) / 2),
+        y = iD.geo.latToMeters(a[1] - b[1]);
+    return Math.sqrt((x * x) + (y * y));
 };
 
 iD.geo.edgeEqual = function(a, b) {
@@ -16827,6 +17021,39 @@ iD.geo.chooseEdge = function(nodes, point, projection) {
     };
 };
 
+// Return the intersection point of 2 line segments.
+// From https://github.com/pgkelley4/line-segments-intersect
+// This uses the vector cross product approach described below:
+//  http://stackoverflow.com/a/565282/786339
+iD.geo.lineIntersection = function(a, b) {
+    function subtractPoints(point1, point2) {
+        return [point1[0] - point2[0], point1[1] - point2[1]];
+    }
+    function crossProduct(point1, point2) {
+        return point1[0] * point2[1] - point1[1] * point2[0];
+    }
+
+    var p = [a[0][0], a[0][1]],
+        p2 = [a[1][0], a[1][1]],
+        q = [b[0][0], b[0][1]],
+        q2 = [b[1][0], b[1][1]],
+        r = subtractPoints(p2, p),
+        s = subtractPoints(q2, q),
+        uNumerator = crossProduct(subtractPoints(q, p), r),
+        denominator = crossProduct(r, s);
+
+    if (uNumerator && denominator) {
+        var u = uNumerator / denominator,
+            t = crossProduct(subtractPoints(q, p), s) / denominator;
+
+        if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
+            return iD.geo.interp(p, p2, t);
+        }
+    }
+
+    return null;
+};
+
 // Return whether point is contained in polygon.
 //
 // `point` should be a 2-item array of coordinates.
@@ -16935,8 +17162,8 @@ _.extend(iD.geo.Extent.prototype, {
     },
 
     padByMeters: function(meters) {
-        var dLat = meters / 111200,
-            dLon = meters / 111200 / Math.abs(Math.cos(this.center()[1]));
+        var dLat = iD.geo.metersToLat(meters),
+            dLon = iD.geo.metersToLon(meters, this.center()[1]);
         return iD.geo.Extent(
                 [this[0][0] - dLon, this[0][1] - dLat],
                 [this[1][0] + dLon, this[1][1] + dLat]);
@@ -17585,7 +17812,15 @@ iD.actions.Connect = function(nodeIds) {
 };
 iD.actions.DeleteMember = function(relationId, memberIndex) {
     return function(graph) {
-        return graph.replace(graph.entity(relationId).removeMember(memberIndex));
+        var relation = graph.entity(relationId)
+            .removeMember(memberIndex);
+
+        graph = graph.replace(relation);
+
+        if (relation.isDegenerate())
+            graph = iD.actions.DeleteRelation(relation.id)(graph);
+
+        return graph;
     };
 };
 iD.actions.DeleteMultiple = function(ids) {
@@ -19407,15 +19642,29 @@ iD.behavior.Hash = function(context) {
     };
 
     var formatter = function(map) {
-        var center = map.center(),
+        var mode = context.mode(),
+            center = map.center(),
             zoom = map.zoom(),
-            precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
-        var q = iD.util.stringQs(location.hash.substring(1));
-        return '#' + iD.util.qsString(_.assign(q, {
-                map: zoom.toFixed(2) +
-                    '/' + center[0].toFixed(precision) +
-                    '/' + center[1].toFixed(precision)
-            }), true);
+            precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)),
+            q = iD.util.stringQs(location.hash.substring(1)),
+            newParams = {};
+
+        if (mode && mode.id === 'browse') {
+            delete q.id;
+        } else {
+            var selected = context.selectedIDs().filter(function(id) {
+                return !context.entity(id).isNew();
+            });
+            if (selected.length) {
+                newParams.id = selected.join(',');
+            }
+        }
+
+        newParams.map = zoom.toFixed(2) +
+                '/' + center[0].toFixed(precision) +
+                '/' + center[1].toFixed(precision);
+
+        return '#' + iD.util.qsString(_.assign(q, newParams), true);
     };
 
     function update() {
@@ -19423,7 +19672,7 @@ iD.behavior.Hash = function(context) {
         if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map!
     }
 
-    var move = _.throttle(update, 500);
+    var throttledUpdate = _.throttle(update, 500);
 
     function hashchange() {
         if (location.hash === s0) return; // ignore spurious hashchange events
@@ -19434,14 +19683,17 @@ iD.behavior.Hash = function(context) {
 
     function hash() {
         context.map()
-            .on('move.hash', move);
+            .on('move.hash', throttledUpdate);
+
+        context
+            .on('enter.hash', throttledUpdate);
 
         d3.select(window)
             .on('hashchange.hash', hashchange);
 
         if (location.hash) {
             var q = iD.util.stringQs(location.hash.substring(1));
-            if (q.id) context.loadEntity(q.id, !q.map);
+            if (q.id) context.loadEntity(q.id.split(',')[0], !q.map);
             hashchange();
             if (q.map) hash.hadHash = true;
         }
@@ -19451,6 +19703,9 @@ iD.behavior.Hash = function(context) {
         context.map()
             .on('move.hash', null);
 
+        context
+            .on('enter.hash', null);
+
         d3.select(window)
             .on('hashchange.hash', null);
 
@@ -19986,8 +20241,7 @@ iD.modes.Browse = function(context) {
         button: 'browse',
         id: 'browse',
         title: t('modes.browse.title'),
-        description: t('modes.browse.description'),
-        key: '1'
+        description: t('modes.browse.description')
     }, sidebar;
 
     var behaviors = [
@@ -20698,17 +20952,6 @@ iD.modes.Select = function(context, selectedIDs) {
             });
         });
 
-        var notNew = selectedIDs.filter(function(id) {
-            return !context.entity(id).isNew();
-        });
-
-        if (notNew.length) {
-            var q = iD.util.stringQs(location.hash.substring(1));
-            location.replace('#' + iD.util.qsString(_.assign(q, {
-                id: notNew.join(',')
-            }), true));
-        }
-
         context.ui().sidebar
             .select(singular() ? singular().id : null, newFeature);
 
@@ -20792,9 +21035,6 @@ iD.modes.Select = function(context, selectedIDs) {
             context.uninstall(behavior);
         });
 
-        var q = iD.util.stringQs(location.hash.substring(1));
-        location.replace('#' + iD.util.qsString(_.omit(q, 'id'), true));
-
         keybinding.off();
 
         context.history()
@@ -22776,8 +23016,7 @@ iD.oneWayTags = {
         'roundabout': true
     },
     'man_made': {
-        'piste:halfpipe': true,
-        'pipeline': true
+        'piste:halfpipe': true
     },
     'piste:type': {
         'downhill': true,
@@ -23177,6 +23416,29 @@ _.extend(iD.Way.prototype, {
         if (this.nodes[this.nodes.length - 1] === node) return 'suffix';
     },
 
+    layer: function() {
+        // explicit layer tag, clamp between -10, 10..
+        if (this.tags.layer !== undefined) {
+            return Math.max(-10, Math.min(+(this.tags.layer), 10));
+        }
+
+        // implied layer tag..
+        if (this.tags.location === 'overground') return 1;
+        if (this.tags.location === 'underground') return -1;
+        if (this.tags.location === 'underwater') return -10;
+
+        if (this.tags.power === 'line') return 10;
+        if (this.tags.power === 'minor_line') return 10;
+        if (this.tags.aerialway) return 10;
+        if (this.tags.bridge) return 1;
+        if (this.tags.cutting) return -1;
+        if (this.tags.tunnel) return -1;
+        if (this.tags.waterway) return -1;
+        if (this.tags.man_made === 'pipeline') return -10;
+        if (this.tags.boundary) return -10;
+        return 0;
+    },
+
     isOneWay: function() {
         // explicit oneway tag..
         if (['yes', '1', '-1'].indexOf(this.tags.oneway) !== -1) { return true; }
@@ -23779,7 +24041,7 @@ iD.GpxLayer = function(context) {
                 .append('text')
                 .attr('class', 'gpx')
                 .text(function(d) {
-                    return d.properties.name;
+                    return d.properties.desc || d.properties.name;
                 })
                 .attr('x', function(d) {
                     var centroid = path.centroid(d);
@@ -23916,10 +24178,9 @@ iD.Map = function(context) {
             if (map.editable() && !transformed) {
                 var all = context.intersects(map.extent()),
                     filter = d3.functor(true),
-                    extent = map.extent(),
                     graph = context.graph();
-                surface.call(vertices, graph, all, filter, extent, map.zoom());
-                surface.call(midpoints, graph, all, filter, extent);
+                surface.call(vertices, graph, all, filter, map.extent(), map.zoom());
+                surface.call(midpoints, graph, all, filter, map.trimmedExtent());
                 dispatch.drawn({full: false});
             }
         });
@@ -23954,7 +24215,7 @@ iD.Map = function(context) {
             .call(vertices, graph, all, filter, map.extent(), map.zoom())
             .call(lines, graph, all, filter)
             .call(areas, graph, all, filter)
-            .call(midpoints, graph, all, filter, map.extent())
+            .call(midpoints, graph, all, filter, map.trimmedExtent())
             .call(labels, graph, all, filter, dimensions, !difference && !extent);
 
         if (points.points(context.intersects(map.extent()), 100).length >= 100) {
@@ -23967,8 +24228,12 @@ iD.Map = function(context) {
     }
 
     function editOff() {
+        var mode = context.mode();
         surface.selectAll('.layer *').remove();
         dispatch.drawn({full: true});
+        if (!(mode && mode.id === 'browse')) {
+            context.enter(iD.modes.Browse(context));
+        }
     }
 
     function zoomPan() {
@@ -24206,6 +24471,12 @@ iD.Map = function(context) {
         }
     };
 
+    map.trimmedExtent = function() {
+        var headerY = 60, footerY = 30, pad = 10;
+        return new iD.geo.Extent(projection.invert([pad, dimensions[1] - footerY - pad]),
+                projection.invert([dimensions[0] - pad, headerY + pad]));
+    };
+
     map.extentZoom = function(_) {
         var extent = iD.geo.Extent(_),
             tl = projection([extent[0][0], extent[1][1]]),
@@ -24442,6 +24713,7 @@ iD.svg = {
                 i = 0,
                 offset = dt,
                 segments = [],
+                viewport = iD.geo.Extent(projection.clipExtent()),
                 coordinates = graph.childNodes(entity).map(function(n) {
                     return n.loc;
                 });
@@ -24460,9 +24732,10 @@ iD.svg = {
                     b = [x, y];
 
                     if (a) {
-                        var span = iD.geo.euclideanDistance(a, b) - offset;
+                        var extent = iD.geo.Extent(a).extend(b),
+                            span = iD.geo.euclideanDistance(a, b) - offset;
 
-                        if (span >= 0) {
+                        if (extent.intersects(viewport) && span >= 0) {
                             var angle = Math.atan2(b[1] - a[1], b[0] - a[0]),
                                 dx = dt * Math.cos(angle),
                                 dy = dt * Math.sin(angle),
@@ -24572,8 +24845,17 @@ iD.svg.Areas = function(projection) {
             fill: areas
         };
 
-        var paths = surface.selectAll('.layer-shadow, .layer-stroke, .layer-fill')
-            .selectAll('path.area')
+        var areagroup = surface
+            .select('.layer-areas')
+            .selectAll('g.areagroup')
+            .data(['fill', 'shadow', 'stroke']);
+
+        areagroup.enter()
+            .append('g')
+            .attr('class', function(d) { return 'layer areagroup area-' + d; });
+
+        var paths = areagroup
+            .selectAll('path')
             .filter(filter)
             .data(function(layer) { return data[layer]; }, iD.Entity.key);
 
@@ -24582,7 +24864,7 @@ iD.svg.Areas = function(projection) {
         paths.exit()
             .remove();
 
-        var fills = surface.selectAll('.layer-fill path.area')[0];
+        var fills = surface.selectAll('.area-fill path.area')[0];
 
         var bisect = d3.bisector(function(node) {
             return -node.__data__.area(graph);
@@ -25199,80 +25481,98 @@ iD.svg.Lines = function(projection) {
     };
 
     function waystack(a, b) {
-        if (!a || !b || !a.tags || !b.tags) return 0;
-        if (a.tags.layer !== undefined && b.tags.layer !== undefined) {
-            return a.tags.layer - b.tags.layer;
-        }
-        if (a.tags.bridge) return 1;
-        if (b.tags.bridge) return -1;
-        if (a.tags.tunnel) return -1;
-        if (b.tags.tunnel) return 1;
         var as = 0, bs = 0;
-        if (a.tags.highway && b.tags.highway) {
-            as -= highway_stack[a.tags.highway];
-            bs -= highway_stack[b.tags.highway];
-        }
+
+        if (a.tags.highway) { as -= highway_stack[a.tags.highway]; }
+        if (b.tags.highway) { bs -= highway_stack[b.tags.highway]; }
         return as - bs;
     }
 
     return function drawLines(surface, graph, entities, filter) {
-        var lines = [],
-            path = iD.svg.Path(projection, graph);
+        var ways = [], pathdata = {}, onewaydata = {},
+            getPath = iD.svg.Path(projection, graph);
 
         for (var i = 0; i < entities.length; i++) {
             var entity = entities[i],
                 outer = iD.geo.simpleMultipolygonOuterMember(entity, graph);
             if (outer) {
-                lines.push(entity.mergeTags(outer.tags));
+                ways.push(entity.mergeTags(outer.tags));
             } else if (entity.geometry(graph) === 'line') {
-                lines.push(entity);
+                ways.push(entity);
             }
         }
 
-        lines = lines.filter(path);
-        lines.sort(waystack);
+        ways = ways.filter(getPath);
 
-        function drawPaths(klass) {
-            var paths = surface.select('.layer-' + klass)
-                .selectAll('path.line')
-                .filter(filter)
-                .data(lines, iD.Entity.key);
+        pathdata = _.groupBy(ways, function(way) { return way.layer(); });
 
-            var enter = paths.enter()
-                .append('path')
-                .attr('class', function(d) { return 'way line ' + klass + ' ' + d.id; });
+        _.forOwn(pathdata, function(v, k) {
+            onewaydata[k] = _(v)
+                .filter(function(d) { return d.isOneWay(); })
+                .map(iD.svg.OneWaySegments(projection, graph, 35))
+                .flatten()
+                .valueOf();
+        });
 
-            // Optimization: call simple TagClasses only on enter selection. This
-            // works because iD.Entity.key is defined to include the entity v attribute.
-            if (klass !== 'stroke') {
-                enter.call(iD.svg.TagClasses());
-            } else {
-                paths.call(iD.svg.TagClasses()
-                    .tags(iD.svg.MultipolygonMemberTags(graph)));
-            }
+        var layergroup = surface
+            .select('.layer-lines')
+            .selectAll('g.layergroup')
+            .data(d3.range(-10, 11));
 
-            paths
-                .order()
-                .attr('d', path);
+        layergroup.enter()
+            .append('g')
+            .attr('class', function(d) { return 'layer layergroup layer' + String(d); });
 
-            paths.exit()
-                .remove();
-        }
 
-        drawPaths('shadow');
-        drawPaths('casing');
-        drawPaths('stroke');
+        var linegroup = layergroup
+            .selectAll('g.linegroup')
+            .data(['shadow', 'casing', 'stroke']);
 
-        var segments = _(lines)
-            .filter(function(d) { return d.isOneWay(); })
-            .map(iD.svg.OneWaySegments(projection, graph, 35))
-            .flatten()
-            .valueOf();
+        linegroup.enter()
+            .append('g')
+            .attr('class', function(d) { return 'layer linegroup line-' + d; });
+
+
+        var lines = linegroup
+            .selectAll('path')
+            .filter(filter)
+            .data(
+                function() { return pathdata[this.parentNode.parentNode.__data__] || []; },
+                iD.Entity.key
+            );
+
+        // Optimization: call simple TagClasses only on enter selection. This
+        // works because iD.Entity.key is defined to include the entity v attribute.
+        lines.enter()
+            .append('path')
+            .attr('class', function(d) { return 'way line ' + this.parentNode.__data__ + ' ' + d.id; })
+            .call(iD.svg.TagClasses());
+
+        lines
+            .sort(waystack)
+            .attr('d', getPath)
+            .call(iD.svg.TagClasses().tags(iD.svg.MultipolygonMemberTags(graph)));
+
+        lines.exit()
+            .remove();
 
-        var oneways = surface.select('.layer-oneway')
-            .selectAll('path.oneway')
+
+        var onewaygroup = layergroup
+            .selectAll('g.onewaygroup')
+            .data(['oneway']);
+
+        onewaygroup.enter()
+            .append('g')
+            .attr('class', 'layer onewaygroup');
+
+
+        var oneways = onewaygroup
+            .selectAll('path')
             .filter(filter)
-            .data(segments, function(d) { return [d.id, d.index]; });
+            .data(
+                function() { return onewaydata[this.parentNode.parentNode.__data__] || []; },
+                function(d) { return [d.id, d.index]; }
+            );
 
         oneways.enter()
             .append('path')
@@ -25280,16 +25580,17 @@ iD.svg.Lines = function(projection) {
             .attr('marker-mid', 'url(#oneway-marker)');
 
         oneways
-            .order()
             .attr('d', function(d) { return d.d; });
 
         oneways.exit()
             .remove();
+
     };
 };
 iD.svg.Midpoints = function(projection, context) {
     return function drawMidpoints(surface, graph, entities, filter, extent) {
-        var midpoints = {};
+        var poly = extent.polygon(),
+            midpoints = {};
 
         for (var i = 0; i < entities.length; i++) {
             var entity = entities[i];
@@ -25311,15 +25612,34 @@ iD.svg.Midpoints = function(projection, context) {
                 if (midpoints[id]) {
                     midpoints[id].parents.push(entity);
                 } else {
-                    var loc = iD.geo.interp(a.loc, b.loc, 0.5);
-                    if (extent.intersects(loc) && iD.geo.euclideanDistance(projection(a.loc), projection(b.loc)) > 40) {
-                        midpoints[id] = {
-                            type: 'midpoint',
-                            id: id,
-                            loc: loc,
-                            edge: [a.id, b.id],
-                            parents: [entity]
-                        };
+                    if (iD.geo.euclideanDistance(projection(a.loc), projection(b.loc)) > 40) {
+                        var point = iD.geo.interp(a.loc, b.loc, 0.5),
+                            loc = null;
+
+                        if (extent.intersects(point)) {
+                            loc = point;
+                        } else {
+                            for (var k = 0; k < 4; k++) {
+                                point = iD.geo.lineIntersection([a.loc, b.loc], [poly[k], poly[k+1]]);
+                                if (point &&
+                                    iD.geo.euclideanDistance(projection(a.loc), projection(point)) > 20 &&
+                                    iD.geo.euclideanDistance(projection(b.loc), projection(point)) > 20)
+                                {
+                                    loc = point;
+                                    break;
+                                }
+                            }
+                        }
+
+                        if (loc) {
+                            midpoints[id] = {
+                                type: 'midpoint',
+                                id: id,
+                                loc: loc,
+                                edge: [a.id, b.id],
+                                parents: [entity]
+                            };
+                        }
                     }
                 }
             }
@@ -25434,7 +25754,7 @@ iD.svg.Points = function(projection, context) {
 iD.svg.Surface = function() {
     return function (selection) {
         var layers = selection.selectAll('.layer')
-            .data(['fill', 'shadow', 'casing', 'stroke', 'oneway', 'hit', 'halo', 'label']);
+            .data(['areas', 'lines', 'hit', 'halo', 'label']);
 
         layers.enter().append('g')
             .attr('class', function(d) { return 'layer layer-' + d; });
@@ -25612,49 +25932,74 @@ iD.svg.Vertices = function(projection, context) {
         return vertices;
     }
 
-    function draw(groups, vertices, klass, graph, zoom) {
-        groups = groups.data(vertices, function(entity) {
-            return iD.Entity.key(entity) + ',' + zoom;
-        });
+    function draw(selection, vertices, klass, graph, zoom) {
+        var icons = {},
+            z;
 
         if (zoom < 17) {
-            zoom = 0;
+            z = 0;
         } else if (zoom < 18) {
-            zoom = 1;
+            z = 1;
         } else {
-            zoom = 2;
+            z = 2;
         }
 
-        var icons = {};
+        var groups = selection.data(vertices, function(entity) {
+            return iD.Entity.key(entity);
+        });
+
         function icon(entity) {
             if (entity.id in icons) return icons[entity.id];
-            icons[entity.id] = zoom !== 0 &&
+            icons[entity.id] =
                 entity.hasInterestingTags() &&
                 context.presets().match(entity, graph).icon;
             return icons[entity.id];
         }
 
-        function circle(klass) {
-            var rads = radiuses[klass];
+        function classCircle(klass) {
             return function(entity) {
-                var i = icon(entity),
-                    c = i ? 0.5 : 0,
-                    r = rads[i ? 3 : zoom];
                 this.setAttribute('class', 'node vertex ' + klass + ' ' + entity.id);
-                this.setAttribute('cx', c);
-                this.setAttribute('cy', -c);
-                this.setAttribute('r', r);
             };
         }
 
-        var enter = groups.enter().append('g')
+        function setAttributes(selection) {
+            ['shadow','stroke','fill'].forEach(function(klass) {
+                var rads = radiuses[klass];
+                selection.selectAll('.' + klass)
+                    .each(function(entity) {
+                        var i = z && icon(entity),
+                            c = i ? 0.5 : 0,
+                            r = rads[i ? 3 : z];
+                        this.setAttribute('cx', c);
+                        this.setAttribute('cy', -c);
+                        this.setAttribute('r', r);
+                        if (i && klass === 'fill') {
+                            this.setAttribute('visibility', 'hidden');
+                        } else {
+                            this.removeAttribute('visibility');
+                        }
+                    });
+            });
+
+            selection.selectAll('use')
+                .each(function() {
+                    if (z) {
+                        this.removeAttribute('visibility');
+                    } else {
+                        this.setAttribute('visibility', 'hidden');
+                    }
+                });
+        }
+
+        var enter = groups.enter()
+            .append('g')
             .attr('class', function(d) { return 'node vertex ' + klass + ' ' + d.id; });
 
         enter.append('circle')
-            .each(circle('shadow'));
+            .each(classCircle('shadow'));
 
         enter.append('circle')
-            .each(circle('stroke'));
+            .each(classCircle('stroke'));
 
         // Vertices with icons get a `use`.
         enter.filter(function(d) { return icon(d); })
@@ -25663,14 +26008,15 @@ iD.svg.Vertices = function(projection, context) {
             .attr('clip-path', 'url(#clip-square-12)')
             .attr('xlink:href', function(d) { return '#maki-' + icon(d) + '-12'; });
 
-        // Vertices with tags get a `circle`.
-        enter.filter(function(d) { return !icon(d) && d.hasInterestingTags(); })
+        // Vertices with tags get a fill.
+        enter.filter(function(d) { return d.hasInterestingTags(); })
             .append('circle')
-            .each(circle('fill'));
+            .each(classCircle('fill'));
 
         groups
             .attr('transform', iD.svg.PointTransform(projection))
-            .classed('shared', function(entity) { return graph.isShared(entity); });
+            .classed('shared', function(entity) { return graph.isShared(entity); })
+            .call(setAttributes);
 
         groups.exit()
             .remove();
@@ -25798,23 +26144,24 @@ iD.ui = function(context) {
             .attr('class', 'map-control help-control')
             .call(iD.ui.Help(context));
 
-        var about = content.append('div')
-            .attr('class','col12 about-block fillD');
+        var footer = content.append('div')
+            .attr('id', 'footer')
+            .attr('class', 'fillD');
+
+        footer.append('div')
+            .attr('id', 'scale-block')
+            .call(iD.ui.Scale(context));
 
-        about.append('div')
-            .attr('class', 'api-status')
-            .call(iD.ui.Status(context));
+        var linkList = footer.append('div')
+            .attr('id', 'info-block')
+            .append('ul')
+            .attr('id', 'about-list')
+            .attr('class', 'link-list');
 
         if (!context.embed()) {
-            about.append('div')
-                .attr('class', 'account')
-                .call(iD.ui.Account(context));
+            linkList.call(iD.ui.Account(context));
         }
 
-        var linkList = about.append('ul')
-            .attr('id', 'about')
-            .attr('class', 'link-list');
-
         linkList.append('li')
             .append('a')
             .attr('target', '_blank')
@@ -25841,6 +26188,10 @@ iD.ui = function(context) {
             .attr('tabindex', -1)
             .call(iD.ui.Contributors(context));
 
+        footer.append('div')
+            .attr('class', 'api-status')
+            .call(iD.ui.Status(context));
+
         window.onbeforeunload = function() {
             return context.save();
         };
@@ -25911,20 +26262,25 @@ iD.ui.Account = function(context) {
 
     function update(selection) {
         if (!connection.authenticated()) {
-            selection.html('')
+            selection.selectAll('#userLink, #logoutLink')
                 .style('display', 'none');
             return;
         }
 
-        selection.style('display', 'block');
-
         connection.userDetails(function(err, details) {
-            selection.html('');
+            var userLink = selection.select('#userLink'),
+                logoutLink = selection.select('#logoutLink');
+
+            userLink.html('');
+            logoutLink.html('');
 
             if (err) return;
 
+            selection.selectAll('#userLink, #logoutLink')
+                .style('display', 'list-item');
+
             // Link
-            var userLink = selection.append('a')
+            userLink.append('a')
                 .attr('href', connection.userURL(details.display_name))
                 .attr('target', '_blank');
 
@@ -25943,7 +26299,7 @@ iD.ui.Account = function(context) {
                 .attr('class', 'label')
                 .text(details.display_name);
 
-            selection.append('a')
+            logoutLink.append('a')
                 .attr('class', 'logout')
                 .attr('href', '#')
                 .text(t('logout'))
@@ -25955,7 +26311,15 @@ iD.ui.Account = function(context) {
     }
 
     return function(selection) {
-        connection.on('auth', function() { update(selection); });
+        selection.append('li')
+            .attr('id', 'logoutLink')
+            .style('display', 'none');
+
+        selection.append('li')
+            .attr('id', 'userLink')
+            .style('display', 'none');
+
+        connection.on('auth.account', function() { update(selection); });
         update(selection);
     };
 };
@@ -27040,15 +27404,16 @@ iD.ui.FeatureList = function(context) {
                 });
             }
 
-            var locationMatch = q.match(/^(-?\d+\.?\d*)\s+(-?\d+\.?\d*)$/);
+            var locationMatch = sexagesimal.pair(q.toUpperCase()) || q.match(/^(-?\d+\.?\d*)\s+(-?\d+\.?\d*)$/);
 
             if (locationMatch) {
+                var loc = [parseFloat(locationMatch[0]), parseFloat(locationMatch[1])];
                 result.push({
                     id: -1,
                     geometry: 'point',
                     type: t('inspector.location'),
-                    name: locationMatch[0],
-                    location: [parseFloat(locationMatch[1]), parseFloat(locationMatch[2])]
+                    name: loc[0].toFixed(6) + ', ' + loc[1].toFixed(6),
+                    location: loc
                 });
             }
 
@@ -28091,7 +28456,7 @@ iD.ui.preset = function(context) {
 
         function show(field) {
             field.show = true;
-            context.presets()(selection);
+            presets(selection);
             field.input.focus();
         }
 
@@ -28589,6 +28954,10 @@ iD.ui.RawMemberEditor = function(context) {
         context.perform(
             iD.actions.DeleteMember(d.relation.id, d.index),
             t('operations.delete_member.annotation'));
+
+        if (!context.hasEntity(d.relation.id)) {
+            context.enter(iD.modes.Browse(context));
+        }
     }
 
     function rawMemberEditor(selection) {
@@ -29236,6 +29605,88 @@ iD.ui.Save = function(context) {
         });
     };
 };
+iD.ui.Scale = function(context) {
+    var projection = context.projection,
+        imperial = (iD.detect().locale === 'en-us'),
+        maxLength = 180,
+        tickHeight = 8;
+
+    function scaleDefs(loc1, loc2) {
+        var lat = (loc2[1] + loc1[1]) / 2,
+            conversion = (imperial ? 3.28084 : 1),
+            dist = iD.geo.lonToMeters(loc2[0] - loc1[0], lat) * conversion,
+            scale = { dist: 0, px: 0, text: '' },
+            buckets, i, val, dLon;
+
+        if (imperial) {
+            buckets = [5280000, 528000, 52800, 5280, 500, 50, 5, 1];
+        } else {
+            buckets = [5000000, 500000, 50000, 5000, 500, 50, 5, 1];
+        }
+
+        // determine a user-friendly endpoint for the scale
+        for (i = 0; i < buckets.length; i++) {
+            val = buckets[i];
+            if (dist >= val) {
+                scale.dist = Math.floor(dist / val) * val;
+                break;
+            }
+        }
+
+        dLon = iD.geo.metersToLon(scale.dist / conversion, lat);
+        scale.px = Math.round(projection([loc1[0] + dLon, loc1[1]])[0]);
+
+        if (imperial) {
+            if (scale.dist >= 5280) {
+                scale.dist /= 5280;
+                scale.text = String(scale.dist) + ' mi';
+            } else {
+                scale.text = String(scale.dist) + ' ft';
+            }
+        } else {
+            if (scale.dist >= 1000) {
+                scale.dist /= 1000;
+                scale.text = String(scale.dist) + ' km';
+            } else {
+                scale.text = String(scale.dist) + ' m';
+            }
+        }
+
+        return scale;
+    }
+
+    function update(selection) {
+        // choose loc1, loc2 along bottom of viewport (near where the scale will be drawn)
+        var dims = context.map().dimensions(),
+            loc1 = projection.invert([0, dims[1]]),
+            loc2 = projection.invert([maxLength, dims[1]]),
+            scale = scaleDefs(loc1, loc2);
+
+        selection.select('#scalepath')
+            .attr('d', 'M0.5,0.5v' + tickHeight + 'h' + scale.px + 'v-' + tickHeight);
+
+        selection.select('#scaletext')
+            .attr('x', scale.px + 8)
+            .attr('y', tickHeight)
+            .text(scale.text);
+    }
+
+    return function(selection) {
+        var g = selection.append('svg')
+            .attr('id', 'scale')
+            .append('g')
+            .attr('transform', 'translate(10,11)');
+
+        g.append('path').attr('id', 'scalepath');
+        g.append('text').attr('id', 'scaletext');
+
+        update(selection);
+
+        context.map().on('move.scale', function() {
+            update(selection);
+        });
+    };
+};
 iD.ui.SelectionList = function(context, selectedIDs) {
 
     function selectionList(selection) {
@@ -30078,12 +30529,17 @@ iD.ui.preset.access = function(field) {
     return d3.rebind(access, event, 'on');
 };
 iD.ui.preset.address = function(field, context) {
-    var event = d3.dispatch('change'),
-        housenumber,
-        street,
-        city,
-        postcode,
-        entity;
+    var event = d3.dispatch('init', 'change'),
+        wrap,
+        entity,
+        isInitialized;
+
+    var widths = {
+        housenumber: 1/3,
+        street: 2/3,
+        city: 2/3,
+        postcode: 1/3
+    };
 
     function getStreets() {
         var extent = entity.extent(context.graph()),
@@ -30168,71 +30624,95 @@ iD.ui.preset.address = function(field, context) {
     }
 
     function address(selection) {
-        var wrap = selection.selectAll('.preset-input-wrap')
-            .data([0]);
+        selection.selectAll('.preset-input-wrap')
+            .remove();
+
+        var center = entity.extent(context.graph()).center(),
+            addressFormat;
 
         // Enter
 
-        var enter = wrap.enter().append('div')
+        wrap = selection.append('div')
             .attr('class', 'preset-input-wrap');
 
-        enter.append('input')
-            .property('type', 'text')
-            .attr('placeholder', field.t('placeholders.number'))
-            .attr('class', 'addr-number');
+        iD.countryCode().search(center, function (err, countryCode) {
+            addressFormat = _.find(iD.data.addressFormats, function (a) {
+                return a && a.countryCodes && _.contains(a.countryCodes, countryCode);
+            }) || _.first(iD.data.addressFormats);
 
-        enter.append('input')
-            .property('type', 'text')
-            .attr('placeholder', field.t('placeholders.street'))
-            .attr('class', 'addr-street');
+            function row(r) {
+                // Normalize widths.
+                var total = _.reduce(r, function(sum, field) {
+                    return sum + (widths[field] || 0.5);
+                }, 0);
 
-        enter.append('input')
-            .property('type', 'text')
-            .attr('placeholder', field.t('placeholders.city'))
-            .attr('class', 'addr-city');
+                return r.map(function (field) {
+                    return {
+                        id: field,
+                        width: (widths[field] || 0.5) / total
+                    };
+                });
+            }
 
-        enter.append('input')
-            .property('type', 'text')
-            .attr('placeholder', field.t('placeholders.postcode'))
-            .attr('class', 'addr-postcode');
+            wrap.selectAll('div')
+                .data(addressFormat.format)
+                .enter()
+                .append('div')
+                .attr('class', 'addr-row')
+                .selectAll('input')
+                .data(row)
+                .enter()
+                .append('input')
+                .property('type', 'text')
+                .attr('placeholder', function (d) { return field.t('placeholders.' + d.id); })
+                .attr('class', function (d) { return 'addr-' + d.id; })
+                .style('width', function (d) { return d.width * 100 + '%'; });
 
-        // Update
+            // Update
 
-        housenumber = wrap.select('.addr-number');
-        street = wrap.select('.addr-street');
-        city = wrap.select('.addr-city');
-        postcode = wrap.select('.addr-postcode');
+            wrap.selectAll('.addr-street')
+                .call(d3.combobox()
+                    .fetcher(function(value, callback) {
+                        callback(getStreets());
+                    }));
 
-        street
-            .call(d3.combobox()
-                .fetcher(function(value, callback) {
-                    callback(getStreets());
-                }));
+            wrap.selectAll('.addr-city')
+                .call(d3.combobox()
+                    .fetcher(function(value, callback) {
+                        callback(getCities());
+                    }));
 
-        city
-            .call(d3.combobox()
-                .fetcher(function(value, callback) {
-                    callback(getCities());
-                }));
+            wrap.selectAll('.addr-postcode')
+                .call(d3.combobox()
+                    .fetcher(function(value, callback) {
+                        callback(getPostCodes());
+                    }));
 
-        postcode
-            .call(d3.combobox()
-                .fetcher(function(value, callback) {
-                    callback(getPostCodes());
-                }));
+            wrap.selectAll('input')
+                .on('blur', change)
+                .on('change', change);
 
-        wrap.selectAll('input')
-            .on('blur', change)
-            .on('change', change);
+            event.init();
+            isInitialized = true;
+        });
     }
 
     function change() {
-        event.change({
-            'addr:housenumber': housenumber.value() || undefined,
-            'addr:street': street.value() || undefined,
-            'addr:city': city.value() || undefined,
-            'addr:postcode': postcode.value() || undefined
-        });
+        var tags = {};
+
+        wrap.selectAll('input')
+            .each(function (field) {
+                tags['addr:' + field.id] = this.value || undefined;
+            });
+
+        event.change(tags);
+    }
+
+    function updateTags(tags) {
+        wrap.selectAll('input')
+            .value(function (field) {
+                return tags['addr:' + field.id] || '';
+            });
     }
 
     address.entity = function(_) {
@@ -30242,14 +30722,17 @@ iD.ui.preset.address = function(field, context) {
     };
 
     address.tags = function(tags) {
-        housenumber.value(tags['addr:housenumber'] || '');
-        street.value(tags['addr:street'] || '');
-        city.value(tags['addr:city'] || '');
-        postcode.value(tags['addr:postcode'] || '');
+        if (isInitialized) {
+            updateTags(tags);
+        } else {
+            event.on('init', function () {
+                updateTags(tags);
+            });
+        }
     };
 
     address.focus = function() {
-        housenumber.node().focus();
+        wrap.selectAll('input').node().focus();
     };
 
     return d3.rebind(address, event, 'on');
@@ -32137,7 +32620,7 @@ iD.validate = function(changes, graph) {
         if ((geometry === 'point' || geometry === 'line' || geometry === 'area') && !change.isUsed(graph)) {
             warnings.push({
                 message: t('validations.untagged_' + geometry),
-                tooltip: t('validations.untagged_tooltip', {geometry: geometry}),
+                tooltip: t('validations.untagged_' + geometry + '_tooltip'),
                 entity: change
             });
         }
@@ -42470,7 +42953,7 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
             "name": "Locator Overlay",
             "type": "tms",
             "description": "Shows major features to help orient you.",
-            "template": "http://{switch:a,b,c}.tiles.mapbox.com/v3/openstreetmap.map-btyhiati/{zoom}/{x}/{y}.png",
+            "template": "http://{switch:a,b,c}.tiles.mapbox.com/v4/openstreetmap.map-inh76ba2/{zoom}/{x}/{y}.png?access_token=pk.eyJ1Ijoib3BlbnN0cmVldG1hcCIsImEiOiJhNVlHd29ZIn0.ti6wATGDWOmCnCYen-Ip7Q",
             "scaleExtent": [
                 0,
                 16
@@ -42490,7 +42973,7 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
             "name": "Mapbox Satellite",
             "type": "tms",
             "description": "Satellite and aerial imagery.",
-            "template": "http://{switch:a,b,c}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{zoom}/{x}/{y}.png",
+            "template": "http://{switch:a,b,c}.tiles.mapbox.com/v4/openstreetmap.map-inh7ifmo/{zoom}/{x}/{y}.png?access_token=pk.eyJ1Ijoib3BlbnN0cmVldG1hcCIsImEiOiJhNVlHd29ZIn0.ti6wATGDWOmCnCYen-Ip7Q",
             "scaleExtent": [
                 0,
                 19
@@ -66840,7 +67323,8 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                     "address",
                     "operator",
                     "opening_hours"
-                ]
+                ],
+                "searchable": false
             },
             "craft/tiler": {
                 "name": "Tiler",
@@ -67745,6 +68229,27 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                 ],
                 "name": "Stop Sign"
             },
+            "highway/street_lamp": {
+                "geometry": [
+                    "point",
+                    "vertex"
+                ],
+                "tags": {
+                    "highway": "street_lamp"
+                },
+                "fields": [
+                    "lamp_type",
+                    "ref"
+                ],
+                "terms": [
+                    "streetlight",
+                    "street light",
+                    "lamp",
+                    "light",
+                    "gaslight"
+                ],
+                "name": "Street Lamp"
+            },
             "highway/tertiary": {
                 "icon": "highway-tertiary",
                 "fields": [
@@ -71058,6 +71563,27 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                 },
                 "name": "Supermarket"
             },
+            "shop/tailor": {
+                "name": "Tailor",
+                "geometry": [
+                    "point",
+                    "area"
+                ],
+                "terms": [
+                    "tailor",
+                    "clothes"
+                ],
+                "tags": {
+                    "shop": "tailor"
+                },
+                "icon": "clothing-store",
+                "fields": [
+                    "building_area",
+                    "address",
+                    "operator",
+                    "opening_hours"
+                ]
+            },
             "shop/toys": {
                 "icon": "shop",
                 "fields": [
@@ -100820,6 +101346,7 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
             "address": {
                 "type": "address",
                 "keys": [
+                    "addr:housename",
                     "addr:housenumber",
                     "addr:street",
                     "addr:city",
@@ -100833,10 +101360,19 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                 "label": "Address",
                 "strings": {
                     "placeholders": {
-                        "number": "123",
+                        "housename": "Housename",
+                        "housenumber": "123",
                         "street": "Street",
                         "city": "City",
-                        "postcode": "Postal code"
+                        "postcode": "Postcode",
+                        "place": "Place",
+                        "hamlet": "Hamlet",
+                        "suburb": "Suburb",
+                        "subdistrict": "Subdistrict",
+                        "district": "District",
+                        "province": "Province",
+                        "state": "State",
+                        "country": "Country"
                     }
                 }
             },
@@ -101248,6 +101784,11 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                     }
                 }
             },
+            "lamp_type": {
+                "key": "lamp_type",
+                "type": "combo",
+                "label": "Type"
+            },
             "landuse": {
                 "key": "landuse",
                 "type": "typeCombo",
@@ -114129,7 +114670,9 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
             "untagged_area": "Untagged area",
             "many_deletions": "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
             "tag_suggests_area": "The tag {tag} suggests line should be area, but it is not an area",
-            "untagged_tooltip": "Select a feature type that describes what this {geometry} is.",
+            "untagged_point_tooltip": "Select a feature type that describes what this point is.",
+            "untagged_line_tooltip": "Select a feature type that describes what this line is.",
+            "untagged_area_tooltip": "Select a feature type that describes what this area is.",
             "deprecated_tags": "Deprecated tags: {tags}"
         },
         "zoom": {
@@ -114281,10 +114824,19 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                 "address": {
                     "label": "Address",
                     "placeholders": {
-                        "number": "123",
+                        "housename": "Housename",
+                        "housenumber": "123",
                         "street": "Street",
                         "city": "City",
-                        "postcode": "Postal code"
+                        "postcode": "Postcode",
+                        "place": "Place",
+                        "hamlet": "Hamlet",
+                        "suburb": "Suburb",
+                        "subdistrict": "Subdistrict",
+                        "district": "District",
+                        "province": "Province",
+                        "state": "State",
+                        "country": "Country"
                     }
                 },
                 "admin_level": {
@@ -114504,6 +115056,9 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                         "terminal": "Terminal"
                     }
                 },
+                "lamp_type": {
+                    "label": "Type"
+                },
                 "landuse": {
                     "label": "Type"
                 },
@@ -115698,6 +116253,10 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                     "name": "Stop Sign",
                     "terms": "stop sign"
                 },
+                "highway/street_lamp": {
+                    "name": "Street Lamp",
+                    "terms": "streetlight,street light,lamp,light,gaslight"
+                },
                 "highway/tertiary": {
                     "name": "Tertiary Road",
                     "terms": ""
@@ -116558,6 +117117,10 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                     "name": "Supermarket",
                     "terms": "bazaar,boutique,chain,co-op,cut-rate store,discount store,five-and-dime,flea market,galleria,grocery store,mall,mart,outlet,outlet store,shop,shopping center,shopping centre,shopping plaza,stand,store,supermarket,thrift shop"
                 },
+                "shop/tailor": {
+                    "name": "Tailor",
+                    "terms": "tailor,clothes"
+                },
                 "shop/toys": {
                     "name": "Toy Store",
                     "terms": ""
@@ -121628,5 +122191,151 @@ iD.introGraph = '{"n185954700":{"id":"n185954700","loc":[-85.642244,41.939081],"
                 }
             }
         }
-    }
+    },
+    "addressFormats": [
+        {
+            "format": [
+                [
+                    "housenumber",
+                    "street"
+                ],
+                [
+                    "city",
+                    "postcode"
+                ]
+            ]
+        },
+        {
+            "countryCodes": [
+                "gb"
+            ],
+            "format": [
+                [
+                    "housename"
+                ],
+                [
+                    "housenumber",
+                    "street"
+                ],
+                [
+                    "city",
+                    "postcode"
+                ]
+            ]
+        },
+        {
+            "countryCodes": [
+                "ie"
+            ],
+            "format": [
+                [
+                    "housename"
+                ],
+                [
+                    "housenumber",
+                    "street"
+                ],
+                [
+                    "city"
+                ]
+            ]
+        },
+        {
+            "countryCodes": [
+                "ad",
+                "at",
+                "ba",
+                "be",
+                "ch",
+                "cz",
+                "de",
+                "dk",
+                "es",
+                "fi",
+                "gr",
+                "hr",
+                "is",
+                "it",
+                "li",
+                "nl",
+                "no",
+                "pl",
+                "pt",
+                "se",
+                "si",
+                "sk",
+                "sm",
+                "va"
+            ],
+            "format": [
+                [
+                    "street",
+                    "housenumber"
+                ],
+                [
+                    "postcode",
+                    "city"
+                ]
+            ]
+        },
+        {
+            "countryCodes": [
+                "fr",
+                "lu",
+                "mo"
+            ],
+            "format": [
+                [
+                    "housenumber",
+                    "street"
+                ],
+                [
+                    "postcode",
+                    "city"
+                ]
+            ]
+        },
+        {
+            "countryCodes": [
+                "br"
+            ],
+            "format": [
+                [
+                    "street"
+                ],
+                [
+                    "housenumber",
+                    "suburb"
+                ],
+                [
+                    "city",
+                    "postcode"
+                ]
+            ]
+        },
+        {
+            "countryCodes": [
+                "vn"
+            ],
+            "format": [
+                [
+                    "housenumber",
+                    "street"
+                ],
+                [
+                    "subdistrict"
+                ],
+                [
+                    "district"
+                ],
+                [
+                    "city"
+                ],
+                [
+                    "province",
+                    "postcode"
+                ]
+            ]
+        }
+    ]
 };
\ No newline at end of file