<commit_before>iD.modes.Select = function(context, selection, initial) {
    var mode = {
        id: 'select',
        button: 'browse'
    };

    var inspector = iD.ui.Inspector(context, singular()),
        keybinding = d3.keybinding('select'),
        timeout = null,
        behaviors = [
            iD.behavior.Hover(),
            iD.behavior.Select(context),
            iD.behavior.Lasso(context),
            iD.modes.DragNode(context).behavior],
        radialMenu;

    var wrap = context.container()
        .select('.inspector-wrap');

    function singular() {
        if (selection.length === 1) {
            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() {
        behaviors.forEach(function(behavior) {
            context.install(behavior);
        });

        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));
        }, true);

        operations.forEach(function(operation) {
            operation.keys.forEach(function(key) {
                keybinding.on(key, function() {
                    if (!operation.disabled()) {
                        operation();
                    }
                });
            });
        });

        var q = iD.util.stringQs(location.hash.substring(1));
        location.replace('#' + iD.util.qsString(_.assign(q, {
            id: selection.join(',')
        }), true));

        if (singular()) {
            wrap.call(inspector);
        }

        context.history()
            .on('undone.select', update)
            .on('redone.select', update);

        function update() {
            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));
            }
        }

        context.map().on('move.select', function() {
            context.surface().call(radialMenu.close);
        });

        function dblclick() {
            var target = d3.select(d3.event.target),
                datum = target.datum();

            if (datum instanceof iD.Way && !target.classed('fill')) {
                var choice = iD.geo.chooseIndex(datum,
                        d3.mouse(context.surface().node()), context),
                    node = iD.Node();

                var prev = datum.nodes[choice.index - 1],
                    next = datum.nodes[choice.index];

                context.perform(
                    iD.actions.AddMidpoint({loc: choice.loc, edge: [prev, next]}, node),
                    t('operations.add.annotation.vertex'));

                d3.event.preventDefault();
                d3.event.stopPropagation();
            }
        }

        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);

        context.surface()
            .selectAll("*")
            .filter(selected)
            .classed('selected', true);

        radialMenu = iD.ui.RadialMenu(operations);
        var show = d3.event && !initial;

        if (show) {
            positionMenu();
        }

        timeout = window.setTimeout(function() {
            if (show) {
                showMenu();
            }

            context.surface()
                .on('dblclick.select', dblclick);
        }, 200);
    };

    mode.exit = function() {
        if (timeout) window.clearTimeout(timeout);

        wrap.call(inspector.close);

        behaviors.forEach(function(behavior) {
            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()
            .on('undone.select', null)
            .on('redone.select', null);

        context.surface()
            .call(radialMenu.close)
            .on('dblclick.select', null)
            .selectAll(".selected")
            .classed('selected', false);
    };

    return mode;
};
<commit_msg>re-add 'selected' class after each redraw<commit_after>iD.modes.Select = function(context, selection, initial) {
    var mode = {
        id: 'select',
        button: 'browse'
    };

    var inspector = iD.ui.Inspector(context, singular()),
        keybinding = d3.keybinding('select'),
        timeout = null,
        behaviors = [
            iD.behavior.Hover(),
            iD.behavior.Select(context),
            iD.behavior.Lasso(context),
            iD.modes.DragNode(context).behavior],
        radialMenu;

    var wrap = context.container()
        .select('.inspector-wrap');

    function singular() {
        if (selection.length === 1) {
            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() {
        behaviors.forEach(function(behavior) {
            context.install(behavior);
        });

        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));
        }, true);

        operations.forEach(function(operation) {
            operation.keys.forEach(function(key) {
                keybinding.on(key, function() {
                    if (!operation.disabled()) {
                        operation();
                    }
                });
            });
        });

        var q = iD.util.stringQs(location.hash.substring(1));
        location.replace('#' + iD.util.qsString(_.assign(q, {
            id: selection.join(',')
        }), true));

        if (singular()) {
            wrap.call(inspector);
        }

        context.history()
            .on('undone.select', update)
            .on('redone.select', update);

        function update() {
            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));
            }
        }

        context.map().on('move.select', function() {
            context.surface().call(radialMenu.close);
        });

        function dblclick() {
            var target = d3.select(d3.event.target),
                datum = target.datum();

            if (datum instanceof iD.Way && !target.classed('fill')) {
                var choice = iD.geo.chooseIndex(datum,
                        d3.mouse(context.surface().node()), context),
                    node = iD.Node();

                var prev = datum.nodes[choice.index - 1],
                    next = datum.nodes[choice.index];

                context.perform(
                    iD.actions.AddMidpoint({loc: choice.loc, edge: [prev, next]}, node),
                    t('operations.add.annotation.vertex'));

                d3.event.preventDefault();
                d3.event.stopPropagation();
            }
        }

        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);

        function selectElements() {
            context.surface()
                .selectAll("*")
                .filter(selected)
                .classed('selected', true);
        }

        context.map().on('drawn.select', selectElements);
        selectElements();

        radialMenu = iD.ui.RadialMenu(operations);
        var show = d3.event && !initial;

        if (show) {
            positionMenu();
        }

        timeout = window.setTimeout(function() {
            if (show) {
                showMenu();
            }

            context.surface()
                .on('dblclick.select', dblclick);
        }, 200);
    };

    mode.exit = function() {
        if (timeout) window.clearTimeout(timeout);

        wrap.call(inspector.close);

        behaviors.forEach(function(behavior) {
            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()
            .on('undone.select', null)
            .on('redone.select', null);

        context.surface()
            .call(radialMenu.close)
            .on('dblclick.select', null)
            .selectAll(".selected")
            .classed('selected', false);

        context.map().on('drawn.select', null);
    };

    return mode;
};
<|endoftext|>


<commit_before>iD.Map = function(context) {
    var dimensions = [1, 1],
        dispatch = d3.dispatch('move', 'drawn'),
        projection = context.projection,
        roundedProjection = iD.svg.RoundProjection(projection),
        zoom = d3.behavior.zoom()
            .translate(projection.translate())
            .scale(projection.scale() * 2 * Math.PI)
            .scaleExtent([1024, 256 * Math.pow(2, 24)])
            .on('zoom', zoomPan),
        dblclickEnabled = true,
        transformStart,
        transformed = false,
        minzoom = 0,
        transformProp = iD.util.prefixCSSProperty('Transform'),
        points = iD.svg.Points(roundedProjection, context),
        vertices = iD.svg.Vertices(roundedProjection, context),
        lines = iD.svg.Lines(projection),
        areas = iD.svg.Areas(projection),
        midpoints = iD.svg.Midpoints(roundedProjection, context),
        labels = iD.svg.Labels(projection, context),
        supersurface, surface,
        mouse,
        mousemove;

    function map(selection) {
        context.history()
            .on('change.map', redraw);
        context.background()
            .on('change.map', redraw);

        selection.call(zoom);

        supersurface = selection.append('div')
            .attr('id', 'supersurface');

        supersurface.call(context.background());

        // Need a wrapper div because Opera can't cope with an absolutely positioned
        // SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16
        var dataLayer = supersurface.append('div')
            .attr('class', 'layer-layer layer-data');

        map.surface = surface = dataLayer.append('svg')
            .on('mousedown.zoom', function() {
                if (d3.event.button == 2) {
                    d3.event.stopPropagation();
                }
            }, true)
            .on('mouseup.zoom', function() {
                if (resetTransform()) redraw();
            })
            .attr('id', 'surface')
            .call(iD.svg.Surface(context));

        surface.on('mousemove.map', function() {
            mousemove = d3.event;
        });

        surface.on('mouseover.vertices', function() {
            if (map.editable() && !transformed) {
                var hover = d3.event.target.__data__;
                surface.call(vertices.drawHover, context.graph(), hover, map.extent(), map.zoom());
                dispatch.drawn({full: false});
            }
        });

        surface.on('mouseout.vertices', function() {
            if (map.editable() && !transformed) {
                var hover = d3.event.relatedTarget && d3.event.relatedTarget.__data__;
                surface.call(vertices.drawHover, context.graph(), hover, map.extent(), map.zoom());
                dispatch.drawn({full: false});
            }
        });

        context.on('enter.map', function() {
            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);
                dispatch.drawn({full: false});
            }
        });

        map.dimensions(selection.dimensions());

        labels.supersurface(supersurface);
    }

    function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }

    function drawVector(difference, extent) {
        var filter, all,
            graph = context.graph();

        if (difference) {
            var complete = difference.complete(map.extent());
            all = _.compact(_.values(complete));
            filter = function(d) {
                if (d.type === 'midpoint') {

                    var a = d.edge[0],
                        b = d.edge[1];

                    // redraw a midpoint if it needs to be
                    // - moved (either edge node moved)
                    // - deleted (edge nodes not consecutive in any parent way)
                    if (a in complete || b in complete) return true;

                    var parentsWays = graph.parentWays({ id: a });
                    for (var i = 0; i < parentsWays.length; i++) {
                        var nodes = parentsWays[i].nodes;
                        for (var n = 0; n < nodes.length; n++) {
                            if (nodes[n] === a && (nodes[n - 1] === b || nodes[n + 1] === b)) return false;
                        }
                    }
                    return true;

                } else {
                    return d.id in complete;
                }
            };

        } else if (extent) {
            all = context.intersects(map.extent().intersection(extent));
            var set = d3.set(_.pluck(all, 'id'));
            filter = function(d) { return set.has(d.id); };

        } else {
            all = context.intersects(map.extent());
            filter = d3.functor(true);
        }

        surface
            .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(labels, graph, all, filter, dimensions, !difference && !extent);

        if (points.points(context.intersects(map.extent()), 100).length >= 100) {
            surface.select('.layer-hit').selectAll('g.point').remove();
        } else {
            surface.call(points, points.points(all), filter);
        }

        dispatch.drawn({full: true});
    }

    function editOff() {
        surface.selectAll('.layer *').remove();
        dispatch.drawn({full: true});
    }

    function zoomPan() {
        if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
            if (!dblclickEnabled) {
                zoom.scale(projection.scale() * 2 * Math.PI)
                    .translate(projection.translate());
                return d3.event.sourceEvent.preventDefault();
            }
        }

        if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) {
            iD.ui.flash(context.container())
                .select('.content')
                .text(t('cannot_zoom'));
            return setZoom(16, true);
        }

        projection
            .translate(d3.event.translate)
            .scale(d3.event.scale / (2 * Math.PI));

        var scale = d3.event.scale / transformStart[0],
            tX = Math.round(d3.event.translate[0] / scale - transformStart[1][0]),
            tY = Math.round(d3.event.translate[1] / scale - transformStart[1][1]);

        var transform =
            'scale(' + scale + ')' +
            (iD.detect().opera ?
                'translate(' + tX + 'px,' + tY + 'px)' :
                'translate3d(' + tX + 'px,' + tY + 'px, 0)');

        transformed = true;
        supersurface.style(transformProp, transform);
        queueRedraw();

        dispatch.move(map);
    }

    function resetTransform() {
        if (!transformed) return false;
        supersurface.style(transformProp, '');
        transformed = false;
        return true;
    }

    function redraw(difference, extent) {

        if (!surface) return;

        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()) {
            difference = extent = undefined;
        }

        var zoom = String(~~map.zoom());
        if (surface.attr('data-zoom') !== zoom) {
            surface.attr('data-zoom', zoom);
        }

        if (!difference) {
            supersurface.call(context.background());
        }

        if (map.editable()) {
            context.connection().loadTiles(projection, dimensions);
            drawVector(difference, extent);
        } else {
            editOff();
        }

        transformStart = [
            projection.scale() * 2 * Math.PI,
            projection.translate().slice()];

        return map;
    }

    var timeoutId;
    function queueRedraw() {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(function() { redraw(); }, 300);
    }

    function pointLocation(p) {
        var translate = projection.translate(),
            scale = projection.scale() * 2 * Math.PI;
        return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
    }

    function locationPoint(l) {
        var translate = projection.translate(),
            scale = projection.scale() * 2 * Math.PI;
        return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
    }

    map.mouse = function() {
        var e = mousemove || d3.event, s;
        while (s = e.sourceEvent) e = s;
        return mouse(e);
    };

    map.mouseCoordinates = function() {
        return projection.invert(map.mouse());
    };

    map.dblclickEnable = function(_) {
        if (!arguments.length) return dblclickEnabled;
        dblclickEnabled = _;
        return map;
    };

    function setZoom(z, force) {
        if (z === map.zoom() && !force)
            return false;
        var scale = 256 * Math.pow(2, z),
            center = pxCenter(),
            l = pointLocation(center);
        scale = Math.max(1024, Math.min(256 * Math.pow(2, 24), scale));
        projection.scale(scale / (2 * Math.PI));
        zoom.scale(scale);
        var t = projection.translate();
        l = locationPoint(l);
        t[0] += center[0] - l[0];
        t[1] += center[1] - l[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        return true;
    }

    function setCenter(loc) {
        var t = projection.translate(),
            c = pxCenter(),
            ll = projection(loc);
        if (ll[0] === c[0] && ll[1] === c[1])
            return false;
        projection.translate([
            t[0] - ll[0] + c[0],
            t[1] - ll[1] + c[1]]);
        zoom.translate(projection.translate());
        return true;
    }

    map.pan = function(d) {
        var t = projection.translate();
        t[0] += d[0];
        t[1] += d[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        dispatch.move(map);
        return redraw();
    };

    map.dimensions = function(_) {
        if (!arguments.length) return dimensions;
        var center = map.center();
        dimensions = _;
        surface.dimensions(dimensions);
        context.background().dimensions(dimensions);
//        projection.clipExtent([[0, 0], dimensions]);
        mouse = iD.util.fastMouse(supersurface.node());
        setCenter(center);
        return redraw();
    };

    map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); };
    map.zoomOut = function() { return map.zoom(Math.floor(map.zoom() - 1)); };

    map.center = function(loc) {
        if (!arguments.length) {
            return projection.invert(pxCenter());
        }

        if (setCenter(loc)) {
            dispatch.move(map);
        }

        return redraw();
    };

    map.zoom = function(z) {
        if (!arguments.length) {
            return Math.max(Math.log(projection.scale() * 2 * Math.PI) / Math.LN2 - 8, 0);
        }

        if (setZoom(z)) {
            dispatch.move(map);
        }

        return redraw();
    };

    map.zoomTo = function(entity, zoomLimits) {
        var extent = entity.extent(context.graph()),
            zoom = map.extentZoom(extent);
        zoomLimits = zoomLimits || [16, 20];
        map.centerZoom(extent.center(), Math.min(Math.max(zoom, zoomLimits[0]), zoomLimits[1]));
    };

    map.centerZoom = function(loc, z) {
        var centered = setCenter(loc),
            zoomed   = setZoom(z);

        if (centered || zoomed) {
            dispatch.move(map);
        }

        return redraw();
    };

    map.centerEase = function(loc) {
        var from = map.center().slice(),
            t = 0,
            stop;

        surface.one('mousedown.ease', function() {
            stop = true;
        });

        d3.timer(function() {
            if (stop) return true;
            map.center(iD.geo.interp(from, loc, (t += 1) / 10));
            return t == 10;
        }, 20);
        return map;
    };

    map.extent = function(_) {
        if (!arguments.length) {
            return new iD.geo.Extent(projection.invert([0, dimensions[1]]),
                                 projection.invert([dimensions[0], 0]));
        } else {
            var extent = iD.geo.Extent(_);
            map.centerZoom(extent.center(), map.extentZoom(extent));
        }
    };

    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.editable = function() {
        return map.zoom() >= 16;
    };

    map.minzoom = function(_) {
        if (!arguments.length) return minzoom;
        minzoom = _;
        return map;
    };

    return d3.rebind(map, dispatch, 'on');
};
<commit_msg>Fix test failure<commit_after>iD.Map = function(context) {
    var dimensions = [1, 1],
        dispatch = d3.dispatch('move', 'drawn'),
        projection = context.projection,
        roundedProjection = iD.svg.RoundProjection(projection),
        zoom = d3.behavior.zoom()
            .translate(projection.translate())
            .scale(projection.scale() * 2 * Math.PI)
            .scaleExtent([1024, 256 * Math.pow(2, 24)])
            .on('zoom', zoomPan),
        dblclickEnabled = true,
        transformStart,
        transformed = false,
        minzoom = 0,
        transformProp = iD.util.prefixCSSProperty('Transform'),
        points = iD.svg.Points(roundedProjection, context),
        vertices = iD.svg.Vertices(roundedProjection, context),
        lines = iD.svg.Lines(projection),
        areas = iD.svg.Areas(projection),
        midpoints = iD.svg.Midpoints(roundedProjection, context),
        labels = iD.svg.Labels(projection, context),
        supersurface, surface,
        mouse,
        mousemove;

    function map(selection) {
        context.history()
            .on('change.map', redraw);
        context.background()
            .on('change.map', redraw);

        selection.call(zoom);

        supersurface = selection.append('div')
            .attr('id', 'supersurface');

        supersurface.call(context.background());

        // Need a wrapper div because Opera can't cope with an absolutely positioned
        // SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16
        var dataLayer = supersurface.append('div')
            .attr('class', 'layer-layer layer-data');

        map.surface = surface = dataLayer.append('svg')
            .on('mousedown.zoom', function() {
                if (d3.event.button == 2) {
                    d3.event.stopPropagation();
                }
            }, true)
            .on('mouseup.zoom', function() {
                if (resetTransform()) redraw();
            })
            .attr('id', 'surface')
            .call(iD.svg.Surface(context));

        surface.on('mousemove.map', function() {
            mousemove = d3.event;
        });

        surface.on('mouseover.vertices', function() {
            if (map.editable() && !transformed) {
                var hover = d3.event.target.__data__;
                surface.call(vertices.drawHover, context.graph(), hover, map.extent(), map.zoom());
                dispatch.drawn({full: false});
            }
        });

        surface.on('mouseout.vertices', function() {
            if (map.editable() && !transformed) {
                var hover = d3.event.relatedTarget && d3.event.relatedTarget.__data__;
                surface.call(vertices.drawHover, context.graph(), hover, map.extent(), map.zoom());
                dispatch.drawn({full: false});
            }
        });

        context.on('enter.map', function() {
            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);
                dispatch.drawn({full: false});
            }
        });

        map.dimensions(selection.dimensions());

        labels.supersurface(supersurface);
    }

    function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }

    function drawVector(difference, extent) {
        var filter, all,
            graph = context.graph();

        if (difference) {
            var complete = difference.complete(map.extent());
            all = _.compact(_.values(complete));
            filter = function(d) {
                if (d.type === 'midpoint') {

                    var a = d.edge[0],
                        b = d.edge[1];

                    // redraw a midpoint if it needs to be
                    // - moved (either edge node moved)
                    // - deleted (edge nodes not consecutive in any parent way)
                    if (a in complete || b in complete) return true;

                    var parentsWays = graph.parentWays({ id: a });
                    for (var i = 0; i < parentsWays.length; i++) {
                        var nodes = parentsWays[i].nodes;
                        for (var n = 0; n < nodes.length; n++) {
                            if (nodes[n] === a && (nodes[n - 1] === b || nodes[n + 1] === b)) return false;
                        }
                    }
                    return true;

                } else {
                    return d.id in complete;
                }
            };

        } else if (extent) {
            all = context.intersects(map.extent().intersection(extent));
            var set = d3.set(_.pluck(all, 'id'));
            filter = function(d) { return set.has(d.id); };

        } else {
            all = context.intersects(map.extent());
            filter = d3.functor(true);
        }

        surface
            .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(labels, graph, all, filter, dimensions, !difference && !extent);

        if (points.points(context.intersects(map.extent()), 100).length >= 100) {
            surface.select('.layer-hit').selectAll('g.point').remove();
        } else {
            surface.call(points, points.points(all), filter);
        }

        dispatch.drawn({full: true});
    }

    function editOff() {
        surface.selectAll('.layer *').remove();
        dispatch.drawn({full: true});
    }

    function zoomPan() {
        if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
            if (!dblclickEnabled) {
                zoom.scale(projection.scale() * 2 * Math.PI)
                    .translate(projection.translate());
                return d3.event.sourceEvent.preventDefault();
            }
        }

        if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) {
            iD.ui.flash(context.container())
                .select('.content')
                .text(t('cannot_zoom'));
            return setZoom(16, true);
        }

        projection
            .translate(d3.event.translate)
            .scale(d3.event.scale / (2 * Math.PI));

        var scale = d3.event.scale / transformStart[0],
            tX = Math.round(d3.event.translate[0] / scale - transformStart[1][0]),
            tY = Math.round(d3.event.translate[1] / scale - transformStart[1][1]);

        var transform =
            'scale(' + scale + ')' +
            (iD.detect().opera ?
                'translate(' + tX + 'px,' + tY + 'px)' :
                'translate3d(' + tX + 'px,' + tY + 'px, 0)');

        transformed = true;
        supersurface.style(transformProp, transform);
        queueRedraw();

        dispatch.move(map);
    }

    function resetTransform() {
        if (!transformed) return false;
        supersurface.style(transformProp, '');
        transformed = false;
        return true;
    }

    function redraw(difference, extent) {

        if (!surface) return;

        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()) {
            difference = extent = undefined;
        }

        var zoom = String(~~map.zoom());
        if (surface.attr('data-zoom') !== zoom) {
            surface.attr('data-zoom', zoom);
        }

        if (!difference) {
            supersurface.call(context.background());
        }

        if (map.editable()) {
            context.connection().loadTiles(projection, dimensions);
            drawVector(difference, extent);
        } else {
            editOff();
        }

        transformStart = [
            projection.scale() * 2 * Math.PI,
            projection.translate().slice()];

        return map;
    }

    var timeoutId;
    function queueRedraw() {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(function() { redraw(); }, 300);
    }

    function pointLocation(p) {
        var translate = projection.translate(),
            scale = projection.scale() * 2 * Math.PI;
        return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
    }

    function locationPoint(l) {
        var translate = projection.translate(),
            scale = projection.scale() * 2 * Math.PI;
        return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
    }

    map.mouse = function() {
        var e = mousemove || d3.event, s;
        while (s = e.sourceEvent) e = s;
        return mouse(e);
    };

    map.mouseCoordinates = function() {
        return projection.invert(map.mouse());
    };

    map.dblclickEnable = function(_) {
        if (!arguments.length) return dblclickEnabled;
        dblclickEnabled = _;
        return map;
    };

    function setZoom(_, force) {
        if (_ === map.zoom() && !force)
            return false;
        var scale = 256 * Math.pow(2, _),
            center = pxCenter(),
            l = pointLocation(center);
        scale = Math.max(1024, Math.min(256 * Math.pow(2, 24), scale));
        projection.scale(scale / (2 * Math.PI));
        zoom.scale(scale);
        var t = projection.translate();
        l = locationPoint(l);
        t[0] += center[0] - l[0];
        t[1] += center[1] - l[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        return true;
    }

    function setCenter(_) {
        var c = map.center();
        if (_[0] === c[0] && _[1] === c[1])
            return false;
        var t = projection.translate(),
            pxC = pxCenter(),
            ll = projection(_);
        projection.translate([
            t[0] - ll[0] + pxC[0],
            t[1] - ll[1] + pxC[1]]);
        zoom.translate(projection.translate());
        return true;
    }

    map.pan = function(d) {
        var t = projection.translate();
        t[0] += d[0];
        t[1] += d[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        dispatch.move(map);
        return redraw();
    };

    map.dimensions = function(_) {
        if (!arguments.length) return dimensions;
        var center = map.center();
        dimensions = _;
        surface.dimensions(dimensions);
        context.background().dimensions(dimensions);
//        projection.clipExtent([[0, 0], dimensions]);
        mouse = iD.util.fastMouse(supersurface.node());
        setCenter(center);
        return redraw();
    };

    map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); };
    map.zoomOut = function() { return map.zoom(Math.floor(map.zoom() - 1)); };

    map.center = function(loc) {
        if (!arguments.length) {
            return projection.invert(pxCenter());
        }

        if (setCenter(loc)) {
            dispatch.move(map);
        }

        return redraw();
    };

    map.zoom = function(z) {
        if (!arguments.length) {
            return Math.max(Math.log(projection.scale() * 2 * Math.PI) / Math.LN2 - 8, 0);
        }

        if (setZoom(z)) {
            dispatch.move(map);
        }

        return redraw();
    };

    map.zoomTo = function(entity, zoomLimits) {
        var extent = entity.extent(context.graph()),
            zoom = map.extentZoom(extent);
        zoomLimits = zoomLimits || [16, 20];
        map.centerZoom(extent.center(), Math.min(Math.max(zoom, zoomLimits[0]), zoomLimits[1]));
    };

    map.centerZoom = function(loc, z) {
        var centered = setCenter(loc),
            zoomed   = setZoom(z);

        if (centered || zoomed) {
            dispatch.move(map);
        }

        return redraw();
    };

    map.centerEase = function(loc) {
        var from = map.center().slice(),
            t = 0,
            stop;

        surface.one('mousedown.ease', function() {
            stop = true;
        });

        d3.timer(function() {
            if (stop) return true;
            map.center(iD.geo.interp(from, loc, (t += 1) / 10));
            return t == 10;
        }, 20);
        return map;
    };

    map.extent = function(_) {
        if (!arguments.length) {
            return new iD.geo.Extent(projection.invert([0, dimensions[1]]),
                                 projection.invert([dimensions[0], 0]));
        } else {
            var extent = iD.geo.Extent(_);
            map.centerZoom(extent.center(), map.extentZoom(extent));
        }
    };

    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.editable = function() {
        return map.zoom() >= 16;
    };

    map.minzoom = function(_) {
        if (!arguments.length) return minzoom;
        minzoom = _;
        return map;
    };

    return d3.rebind(map, dispatch, 'on');
};
<|endoftext|>


<commit_before>iD.Map = function() {
    var connection, history,
        dimensions = [],
        dispatch = d3.dispatch('move'),
        translateStart,
        keybinding = d3.keybinding(),
        projection = d3.geo.mercator().scale(1024),
        zoom = d3.behavior.zoom()
            .translate(projection.translate())
            .scale(projection.scale())
            .scaleExtent([1024, 256 * Math.pow(2, 24)])
            .on('zoom', zoomPan),
        dblclickEnabled = true,
        fastEnabled = true,
        notice,
        background = iD.Background()
            .projection(projection),
        class_stroke = iD.Style.styleClasses('way line stroke'),
        class_casing = iD.Style.styleClasses('way line casing'),
        class_area = iD.Style.styleClasses('way area'),
        transformProp = iD.util.prefixCSSProperty('Transform'),
        supersurface, surface, defs, tilegroup, r, g, alength;

    function map() {
        tilegroup = this.append('div')
            .attr('id', 'tile-g');

        supersurface = this.append('div')
            .style('position', 'absolute')
            .call(zoom);

        surface = supersurface.append('svg')
            .on('mouseup.reset-transform', resetTransform)
            .on('touchend.reset-transform', resetTransform);

        defs = surface.append('defs');
        defs.append('clipPath')
                .attr('id', 'clip')
            .append('rect')
                .attr('id', 'clip-rect')
                .attr({ x: 0, y: 0 });

        r = surface.append('g')
            .attr('clip-path', 'url(#clip)');

        g = ['fill', 'casing', 'stroke', 'text', 'hit', 'temp'].reduce(function(mem, i) {
            return (mem[i] = r.append('g').attr('class', 'layer-g layer-' + i)) && mem;
        }, {});

        var arrow = surface.append('text').text('►----');
        alength = arrow.node().getComputedTextLength();
        arrow.remove();

        notice = iD.notice(supersurface
            .append('div')
            .attr('class', 'notice'));

        map.size(this.size());
        map.surface = surface;

        d3.select(document).call(keybinding);
    }

    function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
    function getline(d) { return d._line; }
    function key(d) { return d.id; }
    function nodeline(d) {
        return 'M' + _.pluck(d.nodes, 'loc').map(projection).map(iD.util.geo.roundCoords).join('L');
    }

    function drawVector(difference) {
        if (surface.style(transformProp) != 'none') return;
        var filter, all, ways = [], lines = [], areas = [], points = [], vertices = [],
            extent = map.extent(),
            graph = history.graph();

        if (!difference) {
            all = graph.intersects(extent);
            filter = d3.functor(true);
        } else {
            var only = {};
            difference.forEach(function buildDifference(id) {
                only[id] = graph.fetch(id);
                graph.parentWays(id).forEach(function buildOnly(parent) {
                    only[parent.id] = graph.fetch(parent.id);
                });
            });
            all = _.compact(_.values(only));
            filter = function(d) { return d.accuracy ? d.way in only : d.id in only; };
        }

        if (all.length > 10000) return editOff();
        else editOn();

        for (var i = 0; i < all.length; i++) {
            var a = all[i];
            if (a.type === 'way') {
                a._line = nodeline(a);
                ways.push(a);
                if (a.isArea()) areas.push(a);
                else lines.push(a);
            } else if (a._poi) {
                points.push(a);
            } else if (!a._poi && a.type === 'node' && a.intersects(extent)) {
                vertices.push(a);
            }
        }
        var parentStructure = graph.parentStructure(ways);
        var wayAccuracyHandles = [];
        for (i = 0; i < ways.length; i++) {
            accuracyHandles(ways[i], wayAccuracyHandles);
        }
        drawVertices(vertices, parentStructure, filter);
        drawAccuracyHandles(wayAccuracyHandles, filter);
        drawCasings(lines, filter);
        drawFills(areas, filter);
        drawStrokes(lines, filter);
        drawPoints(points, filter);
    }

    // updates handles by reference
    function accuracyHandles(way, handles) {
        for (var i = 0; i < way.nodes.length - 1; i++) {
            if (iD.util.geo.dist(way.nodes[i].loc, way.nodes[i + 1].loc) > 0.0001) {
                handles.push({
                    loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5),
                    way: way.id,
                    index: i + 1,
                    accuracy: true
                });
            }
        }
    }

    function pointTransform(entity) {
        return 'translate(' + iD.util.geo.roundCoords(projection(entity.loc)) + ')';
    }

    function drawVertices(vertices, parentStructure, filter) {
        function shared(d) { return parentStructure[d.id] > 1; }

        var circles = g.hit.selectAll('g.vertex')
            .filter(filter)
            .data(vertices, key);

        circles.exit().remove();

        var cg = circles.enter()
            .insert('g', ':first-child')
            .attr('class', 'node vertex');

        cg.append('circle')
            .attr('class', 'stroke')
            .attr('r', 6);

        cg.append('circle')
            .attr('class', 'fill')
            .attr('r', 4);

        circles.attr('transform', pointTransform)
            .classed('shared', shared);
    }

    function drawAccuracyHandles(waynodes, filter) {
        var handles = g.hit.selectAll('circle.accuracy-handle')
            .filter(filter)
            .data(waynodes, function (d) { return [d.way, d.index].join(","); });
        handles.exit().remove();
        handles.enter().append('circle')
            .attr({ r: 3, 'class': 'accuracy-handle' });
        handles.attr('transform', pointTransform);
    }

    function editOff() {
        notice.message('Zoom in to edit the map');
        surface.selectAll('.layer-g *').remove();
    }

    function editOn() {
        notice.message('');
    }

    function drawLines(data, filter, group, class_gen) {
        var lines = group.selectAll('path')
            .filter(filter)
            .data(data, key);
        lines.exit().remove();
        lines.enter().append('path');
        lines
            .order()
            .attr('d', getline)
            .attr('class', class_gen);
        return lines;
    }

    function drawFills(areas, filter) {
        drawLines(areas, filter, g.fill, class_area);
    }

    function drawCasings(ways, filter) {
        drawLines(ways, filter, g.casing, class_casing);
    }

    function drawPoints(points, filter) {
        var groups = g.hit.selectAll('g.point')
            .filter(filter)
            .data(points, key);

        groups.exit().remove();

        var group = groups.enter().append('g')
            .attr('class', 'node point');

        group.append('circle')
            .attr('class', 'stroke')
            .attr({ r: 10 });

        group.append('circle')
            .attr('class', 'fill')
            .attr({ r: 10 });

        group.append('image')
            .attr({ width: 16, height: 16 })
            .attr('transform', 'translate(-8, -8)');

        groups.attr('transform', pointTransform);

        groups.select('image').attr('xlink:href', iD.Style.pointImage);
    }

    function drawStrokes(ways, filter) {
        var strokes = drawLines(ways, filter, g.stroke, class_stroke);

        // Determine the lengths of oneway paths
        var lengths = {},
            oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) {
                lengths[d.id] = Math.floor(this.getTotalLength() / alength);
            }).data();

        var uses = defs.selectAll('path')
            .filter(filter)
            .data(oneways, key);
        uses.exit().remove();
        uses.enter().append('path');
        uses
            .attr('id', function(d) { return 'shadow-' + d.id; })
            .attr('d', getline);

        var labels = g.text.selectAll('text')
            .filter(filter)
            .data(oneways, key);
        labels.exit().remove();
        var tp = labels.enter()
            .append('text').attr({ 'class': 'oneway', dy: 4 })
            .append('textPath').attr('class', 'textpath');
        g.text.selectAll('.textpath')
            .filter(filter)
            .attr('xlink:href', function(d, i) { return '#shadow-' + d.id; })
            .text(function(d) {
                return (new Array(Math.floor(lengths[d.id]))).join('►----');
            });
    }

    function connectionLoad(err, result) {
        history.merge(result);
        redraw(Object.keys(result.entities));
    }

    function zoomPan() {
        if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
            if (!dblclickEnabled) {
                zoom.scale(projection.scale())
                    .translate(projection.translate());
                return d3.event.sourceEvent.preventDefault();
            }
        }
        var fast = (d3.event.scale === projection.scale() && fastEnabled);
        projection
            .translate(d3.event.translate)
            .scale(d3.event.scale);
        if (fast) {
            if (!translateStart) translateStart = d3.event.translate.slice();
            var a = d3.event.translate,
                b = translateStart,
                translate = 'translate(' + ~~(a[0] - b[0]) + 'px,' +
                    ~~(a[1] - b[1]) + 'px)';
            tilegroup.style(transformProp, translate);
            surface.style(transformProp, translate);
        } else {
            redraw();
            translateStart = null;
        }
    }

    function resetTransform() {
        if (!surface.style(transformProp)) return;
        translateStart = null;
        surface.style(transformProp, '');
        tilegroup.style(transformProp, '');
        redraw();
    }

    function redraw(difference) {
        dispatch.move(map);
        tilegroup.call(background);
        if (map.zoom() > 16) {
            connection.loadTiles(projection);
            drawVector(difference);
        } else {
            editOff();
        }
        return map;
    }

    function pointLocation(p) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
    }

    function locationPoint(l) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
    }

    map.mouseCoordinates = function() {
        return projection.invert(d3.mouse(surface.node()));
    };

    map.dblclickEnable = function(_) {
        if (!arguments.length) return dblclickEnabled;
        dblclickEnabled = _;
        return map;
    };

    map.fastEnable = function(_) {
        if (!arguments.length) return fastEnabled;
        fastEnabled = _;
        return map;
    };

    map.zoom = function(z) {
        if (!arguments.length) {
            return Math.max(Math.log(projection.scale()) / Math.LN2 - 8, 0);
        }
        var scale = 256 * Math.pow(2, z),
            center = pxCenter(),
            l = pointLocation(center);
        scale = Math.max(1024, Math.min(256 * Math.pow(2, 24), scale));
        projection.scale(scale);
        zoom.scale(projection.scale());
        var t = projection.translate();
        l = locationPoint(l);
        t[0] += center[0] - l[0];
        t[1] += center[1] - l[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        return redraw();
    };

    map.size = function(_) {
        if (!arguments.length) return dimensions;
        dimensions = _;
        surface
            .size(dimensions)
            .selectAll('#clip-rect')
            .size(dimensions);
        background.size(dimensions);
        return redraw();
    };

    map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); };
    map.zoomOut = function() { return map.zoom(Math.floor(map.zoom() - 1)); };

    map.center = function(loc) {
        if (!arguments.length) {
            return projection.invert(pxCenter());
        } else {
            var t = projection.translate(),
                c = pxCenter(),
                ll = projection(loc);
            projection.translate([
                t[0] - ll[0] + c[0],
                t[1] - ll[1] + c[1]]);
            zoom.translate(projection.translate());
            return redraw();
        }
    };

    map.centerEase = function(loc) {
        var from = map.center().slice(), t = 0;
        d3.timer(function() {
            map.center(iD.util.geo.interp(from, loc, (t += 1) / 10));
            return t == 10;
        }, 20);
    };

    map.extent = function() {
        return [projection.invert([0, 0]), projection.invert(dimensions)];
    };

    map.flush = function () {
        connection.flush();
        return map;
    };

    map.connection = function(_) {
        if (!arguments.length) return connection;
        connection = _;
        connection.on('load', connectionLoad);
        return map;
    };

    map.hint = function (_) {
        if (_ === false) {
            d3.select('div.inspector-wrap')
                .style('opacity', 0)
                .style('display', 'none');
        } else {
            d3.select('div.inspector-wrap')
                .html('')
                .style('display', 'block')
                .transition()
                .style('opacity', 1);
            d3.select('div.inspector-wrap')
                .append('div')
                .attr('class','inspector-inner')
                .text(_);
        }
    };

    map.history = function (_) {
        if (!arguments.length) return history;
        history = _;
        history.on('change.map', redraw);
        return map;
    };

    map.keybinding = function (_) {
        if (!arguments.length) return keybinding;
        keybinding = _;
        return map;
    };

    map.background = background;
    map.projection = projection;
    map.redraw = redraw;

    return d3.rebind(map, dispatch, 'on');
};
<commit_msg>Use Entity#geometry()<commit_after>iD.Map = function() {
    var connection, history,
        dimensions = [],
        dispatch = d3.dispatch('move'),
        translateStart,
        keybinding = d3.keybinding(),
        projection = d3.geo.mercator().scale(1024),
        zoom = d3.behavior.zoom()
            .translate(projection.translate())
            .scale(projection.scale())
            .scaleExtent([1024, 256 * Math.pow(2, 24)])
            .on('zoom', zoomPan),
        dblclickEnabled = true,
        fastEnabled = true,
        notice,
        background = iD.Background()
            .projection(projection),
        class_stroke = iD.Style.styleClasses('way line stroke'),
        class_casing = iD.Style.styleClasses('way line casing'),
        class_area = iD.Style.styleClasses('way area'),
        transformProp = iD.util.prefixCSSProperty('Transform'),
        supersurface, surface, defs, tilegroup, r, g, alength;

    function map() {
        tilegroup = this.append('div')
            .attr('id', 'tile-g');

        supersurface = this.append('div')
            .style('position', 'absolute')
            .call(zoom);

        surface = supersurface.append('svg')
            .on('mouseup.reset-transform', resetTransform)
            .on('touchend.reset-transform', resetTransform);

        defs = surface.append('defs');
        defs.append('clipPath')
                .attr('id', 'clip')
            .append('rect')
                .attr('id', 'clip-rect')
                .attr({ x: 0, y: 0 });

        r = surface.append('g')
            .attr('clip-path', 'url(#clip)');

        g = ['fill', 'casing', 'stroke', 'text', 'hit', 'temp'].reduce(function(mem, i) {
            return (mem[i] = r.append('g').attr('class', 'layer-g layer-' + i)) && mem;
        }, {});

        var arrow = surface.append('text').text('►----');
        alength = arrow.node().getComputedTextLength();
        arrow.remove();

        notice = iD.notice(supersurface
            .append('div')
            .attr('class', 'notice'));

        map.size(this.size());
        map.surface = surface;

        d3.select(document).call(keybinding);
    }

    function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
    function getline(d) { return d._line; }
    function key(d) { return d.id; }
    function nodeline(d) {
        return 'M' + _.pluck(d.nodes, 'loc').map(projection).map(iD.util.geo.roundCoords).join('L');
    }

    function drawVector(difference) {
        if (surface.style(transformProp) != 'none') return;
        var filter, all, ways = [], lines = [], areas = [], points = [], vertices = [],
            extent = map.extent(),
            graph = history.graph();

        if (!difference) {
            all = graph.intersects(extent);
            filter = d3.functor(true);
        } else {
            var only = {};
            difference.forEach(function buildDifference(id) {
                only[id] = graph.fetch(id);
                graph.parentWays(id).forEach(function buildOnly(parent) {
                    only[parent.id] = graph.fetch(parent.id);
                });
            });
            all = _.compact(_.values(only));
            filter = function(d) { return d.accuracy ? d.way in only : d.id in only; };
        }

        if (all.length > 10000) return editOff();
        else editOn();

        for (var i = 0; i < all.length; i++) {
            var entity = all[i];
            switch (entity.geometry()) {
                case 'line':
                    entity._line = nodeline(entity);
                    lines.push(entity);
                    break;

                case 'area':
                    entity._line = nodeline(entity);
                    areas.push(entity);
                    break;

                case 'point':
                    points.push(entity);
                    break;

                case 'vertex':
                    vertices.push(entity);
                    break;
            }
        }

        var parentStructure = graph.parentStructure(ways);
        var wayAccuracyHandles = [];
        for (i = 0; i < ways.length; i++) {
            accuracyHandles(ways[i], wayAccuracyHandles);
        }

        drawVertices(vertices, parentStructure, filter);
        drawAccuracyHandles(wayAccuracyHandles, filter);
        drawCasings(lines, filter);
        drawFills(areas, filter);
        drawStrokes(lines, filter);
        drawPoints(points, filter);
    }

    // updates handles by reference
    function accuracyHandles(way, handles) {
        for (var i = 0; i < way.nodes.length - 1; i++) {
            if (iD.util.geo.dist(way.nodes[i].loc, way.nodes[i + 1].loc) > 0.0001) {
                handles.push({
                    loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5),
                    way: way.id,
                    index: i + 1,
                    accuracy: true
                });
            }
        }
    }

    function pointTransform(entity) {
        return 'translate(' + iD.util.geo.roundCoords(projection(entity.loc)) + ')';
    }

    function drawVertices(vertices, parentStructure, filter) {
        function shared(d) { return parentStructure[d.id] > 1; }

        var circles = g.hit.selectAll('g.vertex')
            .filter(filter)
            .data(vertices, key);

        circles.exit().remove();

        var cg = circles.enter()
            .insert('g', ':first-child')
            .attr('class', 'node vertex');

        cg.append('circle')
            .attr('class', 'stroke')
            .attr('r', 6);

        cg.append('circle')
            .attr('class', 'fill')
            .attr('r', 4);

        circles.attr('transform', pointTransform)
            .classed('shared', shared);
    }

    function drawAccuracyHandles(waynodes, filter) {
        var handles = g.hit.selectAll('circle.accuracy-handle')
            .filter(filter)
            .data(waynodes, function (d) { return [d.way, d.index].join(","); });
        handles.exit().remove();
        handles.enter().append('circle')
            .attr({ r: 3, 'class': 'accuracy-handle' });
        handles.attr('transform', pointTransform);
    }

    function editOff() {
        notice.message('Zoom in to edit the map');
        surface.selectAll('.layer-g *').remove();
    }

    function editOn() {
        notice.message('');
    }

    function drawLines(data, filter, group, class_gen) {
        var lines = group.selectAll('path')
            .filter(filter)
            .data(data, key);
        lines.exit().remove();
        lines.enter().append('path');
        lines
            .order()
            .attr('d', getline)
            .attr('class', class_gen);
        return lines;
    }

    function drawFills(areas, filter) {
        drawLines(areas, filter, g.fill, class_area);
    }

    function drawCasings(ways, filter) {
        drawLines(ways, filter, g.casing, class_casing);
    }

    function drawPoints(points, filter) {
        var groups = g.hit.selectAll('g.point')
            .filter(filter)
            .data(points, key);

        groups.exit().remove();

        var group = groups.enter().append('g')
            .attr('class', 'node point');

        group.append('circle')
            .attr('class', 'stroke')
            .attr({ r: 10 });

        group.append('circle')
            .attr('class', 'fill')
            .attr({ r: 10 });

        group.append('image')
            .attr({ width: 16, height: 16 })
            .attr('transform', 'translate(-8, -8)');

        groups.attr('transform', pointTransform);

        groups.select('image').attr('xlink:href', iD.Style.pointImage);
    }

    function drawStrokes(ways, filter) {
        var strokes = drawLines(ways, filter, g.stroke, class_stroke);

        // Determine the lengths of oneway paths
        var lengths = {},
            oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) {
                lengths[d.id] = Math.floor(this.getTotalLength() / alength);
            }).data();

        var uses = defs.selectAll('path')
            .filter(filter)
            .data(oneways, key);
        uses.exit().remove();
        uses.enter().append('path');
        uses
            .attr('id', function(d) { return 'shadow-' + d.id; })
            .attr('d', getline);

        var labels = g.text.selectAll('text')
            .filter(filter)
            .data(oneways, key);
        labels.exit().remove();
        var tp = labels.enter()
            .append('text').attr({ 'class': 'oneway', dy: 4 })
            .append('textPath').attr('class', 'textpath');
        g.text.selectAll('.textpath')
            .filter(filter)
            .attr('xlink:href', function(d, i) { return '#shadow-' + d.id; })
            .text(function(d) {
                return (new Array(Math.floor(lengths[d.id]))).join('►----');
            });
    }

    function connectionLoad(err, result) {
        history.merge(result);
        redraw(Object.keys(result.entities));
    }

    function zoomPan() {
        if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
            if (!dblclickEnabled) {
                zoom.scale(projection.scale())
                    .translate(projection.translate());
                return d3.event.sourceEvent.preventDefault();
            }
        }
        var fast = (d3.event.scale === projection.scale() && fastEnabled);
        projection
            .translate(d3.event.translate)
            .scale(d3.event.scale);
        if (fast) {
            if (!translateStart) translateStart = d3.event.translate.slice();
            var a = d3.event.translate,
                b = translateStart,
                translate = 'translate(' + ~~(a[0] - b[0]) + 'px,' +
                    ~~(a[1] - b[1]) + 'px)';
            tilegroup.style(transformProp, translate);
            surface.style(transformProp, translate);
        } else {
            redraw();
            translateStart = null;
        }
    }

    function resetTransform() {
        if (!surface.style(transformProp)) return;
        translateStart = null;
        surface.style(transformProp, '');
        tilegroup.style(transformProp, '');
        redraw();
    }

    function redraw(difference) {
        dispatch.move(map);
        tilegroup.call(background);
        if (map.zoom() > 16) {
            connection.loadTiles(projection);
            drawVector(difference);
        } else {
            editOff();
        }
        return map;
    }

    function pointLocation(p) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
    }

    function locationPoint(l) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
    }

    map.mouseCoordinates = function() {
        return projection.invert(d3.mouse(surface.node()));
    };

    map.dblclickEnable = function(_) {
        if (!arguments.length) return dblclickEnabled;
        dblclickEnabled = _;
        return map;
    };

    map.fastEnable = function(_) {
        if (!arguments.length) return fastEnabled;
        fastEnabled = _;
        return map;
    };

    map.zoom = function(z) {
        if (!arguments.length) {
            return Math.max(Math.log(projection.scale()) / Math.LN2 - 8, 0);
        }
        var scale = 256 * Math.pow(2, z),
            center = pxCenter(),
            l = pointLocation(center);
        scale = Math.max(1024, Math.min(256 * Math.pow(2, 24), scale));
        projection.scale(scale);
        zoom.scale(projection.scale());
        var t = projection.translate();
        l = locationPoint(l);
        t[0] += center[0] - l[0];
        t[1] += center[1] - l[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        return redraw();
    };

    map.size = function(_) {
        if (!arguments.length) return dimensions;
        dimensions = _;
        surface
            .size(dimensions)
            .selectAll('#clip-rect')
            .size(dimensions);
        background.size(dimensions);
        return redraw();
    };

    map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); };
    map.zoomOut = function() { return map.zoom(Math.floor(map.zoom() - 1)); };

    map.center = function(loc) {
        if (!arguments.length) {
            return projection.invert(pxCenter());
        } else {
            var t = projection.translate(),
                c = pxCenter(),
                ll = projection(loc);
            projection.translate([
                t[0] - ll[0] + c[0],
                t[1] - ll[1] + c[1]]);
            zoom.translate(projection.translate());
            return redraw();
        }
    };

    map.centerEase = function(loc) {
        var from = map.center().slice(), t = 0;
        d3.timer(function() {
            map.center(iD.util.geo.interp(from, loc, (t += 1) / 10));
            return t == 10;
        }, 20);
    };

    map.extent = function() {
        return [projection.invert([0, 0]), projection.invert(dimensions)];
    };

    map.flush = function () {
        connection.flush();
        return map;
    };

    map.connection = function(_) {
        if (!arguments.length) return connection;
        connection = _;
        connection.on('load', connectionLoad);
        return map;
    };

    map.hint = function (_) {
        if (_ === false) {
            d3.select('div.inspector-wrap')
                .style('opacity', 0)
                .style('display', 'none');
        } else {
            d3.select('div.inspector-wrap')
                .html('')
                .style('display', 'block')
                .transition()
                .style('opacity', 1);
            d3.select('div.inspector-wrap')
                .append('div')
                .attr('class','inspector-inner')
                .text(_);
        }
    };

    map.history = function (_) {
        if (!arguments.length) return history;
        history = _;
        history.on('change.map', redraw);
        return map;
    };

    map.keybinding = function (_) {
        if (!arguments.length) return keybinding;
        keybinding = _;
        return map;
    };

    map.background = background;
    map.projection = projection;
    map.redraw = redraw;

    return d3.rebind(map, dispatch, 'on');
};
<|endoftext|>


<commit_before>iD.Inspector = function() {
    var event = d3.dispatch('changeTags', 'changeWayDirection',
        'update', 'remove', 'close', 'splitWay'),
        taginfo = iD.taginfo(),
        inspectorwrap;

    function drawhead(selection) {
        function osmLink(d) {
            return 'http://www.openstreetmap.org/browse/' + d.type + '/' + d.osmId();
        }
        function emitChangeDirection(d) { event.changeWayDirection(iD.Entity(d)); }
        function emitSplitWay(d) { event.splitWay(iD.Entity(d)); }
        selection.html('');
        var h2 = selection.append('h2');
        h2.append('span').attr('class', function(d) {
            var icons = { way: 'line', node: 'point' };
            return 'icon big icon-pre-text big-' + icons[d.type];
        });
        h2.append('span').text(iD.util.friendlyName(selection.datum()));
        selection.append('a')
            .attr('class', 'permalink')
            .attr('href', osmLink)
            .text('View on OSM');
        if (selection.datum().type === 'way') {
            selection.append('a')
                .attr('class', 'permalink')
                .attr('href', '#')
                .text('Reverse Direction')
                .on('click', emitChangeDirection);
        }
        if (selection.datum().type === 'node' && !selection.datum()._poi) {
            selection.append('a')
                .attr('class', 'permalink')
                .attr('href', '#')
                .text('Split Way')
                .on('click', emitSplitWay);
        }
    }

    function inspector(selection) {
        selection.each(function(entity) {

            function draw(tags) {

                function setValue(d) {
                    d.value = this.value;
                }

                function setKey(d) {
                    d.key = this.value;
                }

                function emptyTag(d) {
                    return d.key === '';
                }

                function pushMore() {
                    if (d3.event.keyCode === 9) {
                        draw(inspector.tags());
                    }
                }

                function bindTypeahead(d, i) {
                    var selection = d3.select(this);
                    selection.call(d3.typeahead()
                        .data(function(selection, callback) {
                            taginfo.values(selection.datum().key, function(err, data) {
                                callback(data.data);
                            });
                        }));
                }

                tags = d3.entries(tags);
                tags.push({ key: '', value: ''});

                var li = inspectorwrap.selectAll('li')
                    .data(tags, function(d) { return d.key; });

                li.exit().remove();

                var row = li.enter().append('li').attr('class','tag-row');
                var inputs = row.append('div').attr('class','input-wrap');

                li.classed('tag-row-empty', emptyTag);

                inputs.append('input')
                    .property('type', 'text')
                    .attr('class', 'key')
                    .property('value', function(d) { return d.key; })
                    .on('keyup.update', setKey);

                inputs.append('input')
                    .property('type', 'text')
                    .attr('class', 'value')
                    .property('value', function(d) { return d.value; })
                    .on('keyup.update', setValue)
                    .on('keydown.push-more', pushMore)
                    .each(bindTypeahead);

                var removeBtn = row.append('button')
                    .attr('tabindex', -1)
                    .attr('class','remove minor')
                    .on('click', removeTag);

                removeBtn.append('span').attr('class', 'icon remove');

                var helpBtn = row.append('button')
                    .attr('tabindex', -1)
                    .attr('class', 'tag-help minor')
                    .append('a')
                        .attr('tabindex', -1)
                        .attr('target', '_blank')
                        .on('click', function(d) {
                            taginfo.docs(d, function(err, docs) {
                                var en = _.find(docs, function(d) {
                                    return d.lang == 'en';
                                });
                                if (en) {
                                    var types = [];
                                    if (en.on_area) types.push('area');
                                    if (en.on_node) types.push('point');
                                    if (en.on_way) types.push('line');
                                    en.types = types;
                                    var mod = iD.modal();
                                    mod.select('.content')
                                        .datum(en)
                                        .call(iD.tagReference);
                                }
                            });
                            d3.event.preventDefault();
                        })
                        .attr('href', function(d) {
                            return 'http://taginfo.openstreetmap.org/keys/' + d.key;
                        });

                helpBtn.append('span').attr('class', 'icon inspect');

                return li;
            }

            function removeTag(d) {
                var tags = inspector.tags();
                delete tags[d.key];
                draw(tags);
            }

            function apply(entity) {
                event.changeTags(entity, inspector.tags());
                event.close(entity);
            }

            function drawbuttons(selection) {
                selection.append('button')
                    .attr('class', 'apply wide action')
                    .html("Apply")
                    .on('click', apply);
                selection.append('button')
                    .attr('class', 'delete wide action')
                    .html("Delete")
                    .on('click', function(entity) { event.remove(entity); });
            }

            selection.html("").append('button')
                .attr('class', 'narrow close')
                .html("")
                .on('click', function() {
                    event.close(entity);
                });

            selection.append('div')
                .attr('class', 'head inspector-inner')
                .call(drawhead);

            var inspectorbody = selection.append('div')
                .attr('class', 'inspector-body');

            inspectorwrap = inspectorbody
                .append('ul').attr('class', 'inspector-inner tag-wrap fillL2');

            inspectorwrap.append('h4').text('Edit tags');

            var formsel = draw(entity.tags);

            inspectorbody.append('div')
                .attr('class', 'inspector-buttons').call(drawbuttons);

            var inHeight = inspectorbody.node().offsetHeight;

            inspectorbody.style('display', 'none')
                .style('margin-top', (-inHeight) + 'px');

            var inspectortoggle = selection.append('button')
                .attr('class', 'inspector-toggle action')
                .on('click', function() {
                    inspectortoggle.style('display', 'none');
                    inspectorbody
                        .style('display', 'block')
                        .transition()
                        .style('margin-top', '0px');
                });

            formsel.select('input').node().focus();

            inspectortoggle.append('span')
                .text('Details')
                .attr('class','label');
        });
    }

    function unentries(entries) {
        return d3.nest()
            .key(function(d) { return d.key; })
            .rollup(function(v) { return v[0].value; })
            .map(entries);
    }

    inspector.tags = function () {
        var grabbed = [];
        function grab(d) { if (d.key !== '') grabbed.push(d); }
        inspectorwrap.selectAll('li').each(grab);
        return unentries(grabbed);
    };

    return d3.rebind(inspector, event, 'on');
};
<commit_msg>Don't need to copy here<commit_after>iD.Inspector = function() {
    var event = d3.dispatch('changeTags', 'changeWayDirection',
        'update', 'remove', 'close', 'splitWay'),
        taginfo = iD.taginfo(),
        inspectorwrap;

    function drawhead(selection) {
        function osmLink(d) {
            return 'http://www.openstreetmap.org/browse/' + d.type + '/' + d.osmId();
        }
        function emitChangeDirection(d) { event.changeWayDirection(d); }
        function emitSplitWay(d) { event.splitWay(d); }
        selection.html('');
        var h2 = selection.append('h2');
        h2.append('span').attr('class', function(d) {
            var icons = { way: 'line', node: 'point' };
            return 'icon big icon-pre-text big-' + icons[d.type];
        });
        h2.append('span').text(iD.util.friendlyName(selection.datum()));
        selection.append('a')
            .attr('class', 'permalink')
            .attr('href', osmLink)
            .text('View on OSM');
        if (selection.datum().type === 'way') {
            selection.append('a')
                .attr('class', 'permalink')
                .attr('href', '#')
                .text('Reverse Direction')
                .on('click', emitChangeDirection);
        }
        if (selection.datum().type === 'node' && !selection.datum()._poi) {
            selection.append('a')
                .attr('class', 'permalink')
                .attr('href', '#')
                .text('Split Way')
                .on('click', emitSplitWay);
        }
    }

    function inspector(selection) {
        selection.each(function(entity) {

            function draw(tags) {

                function setValue(d) {
                    d.value = this.value;
                }

                function setKey(d) {
                    d.key = this.value;
                }

                function emptyTag(d) {
                    return d.key === '';
                }

                function pushMore() {
                    if (d3.event.keyCode === 9) {
                        draw(inspector.tags());
                    }
                }

                function bindTypeahead(d, i) {
                    var selection = d3.select(this);
                    selection.call(d3.typeahead()
                        .data(function(selection, callback) {
                            taginfo.values(selection.datum().key, function(err, data) {
                                callback(data.data);
                            });
                        }));
                }

                tags = d3.entries(tags);
                tags.push({ key: '', value: ''});

                var li = inspectorwrap.selectAll('li')
                    .data(tags, function(d) { return d.key; });

                li.exit().remove();

                var row = li.enter().append('li').attr('class','tag-row');
                var inputs = row.append('div').attr('class','input-wrap');

                li.classed('tag-row-empty', emptyTag);

                inputs.append('input')
                    .property('type', 'text')
                    .attr('class', 'key')
                    .property('value', function(d) { return d.key; })
                    .on('keyup.update', setKey);

                inputs.append('input')
                    .property('type', 'text')
                    .attr('class', 'value')
                    .property('value', function(d) { return d.value; })
                    .on('keyup.update', setValue)
                    .on('keydown.push-more', pushMore)
                    .each(bindTypeahead);

                var removeBtn = row.append('button')
                    .attr('tabindex', -1)
                    .attr('class','remove minor')
                    .on('click', removeTag);

                removeBtn.append('span').attr('class', 'icon remove');

                var helpBtn = row.append('button')
                    .attr('tabindex', -1)
                    .attr('class', 'tag-help minor')
                    .append('a')
                        .attr('tabindex', -1)
                        .attr('target', '_blank')
                        .on('click', function(d) {
                            taginfo.docs(d, function(err, docs) {
                                var en = _.find(docs, function(d) {
                                    return d.lang == 'en';
                                });
                                if (en) {
                                    var types = [];
                                    if (en.on_area) types.push('area');
                                    if (en.on_node) types.push('point');
                                    if (en.on_way) types.push('line');
                                    en.types = types;
                                    var mod = iD.modal();
                                    mod.select('.content')
                                        .datum(en)
                                        .call(iD.tagReference);
                                }
                            });
                            d3.event.preventDefault();
                        })
                        .attr('href', function(d) {
                            return 'http://taginfo.openstreetmap.org/keys/' + d.key;
                        });

                helpBtn.append('span').attr('class', 'icon inspect');

                return li;
            }

            function removeTag(d) {
                var tags = inspector.tags();
                delete tags[d.key];
                draw(tags);
            }

            function apply(entity) {
                event.changeTags(entity, inspector.tags());
                event.close(entity);
            }

            function drawbuttons(selection) {
                selection.append('button')
                    .attr('class', 'apply wide action')
                    .html("Apply")
                    .on('click', apply);
                selection.append('button')
                    .attr('class', 'delete wide action')
                    .html("Delete")
                    .on('click', function(entity) { event.remove(entity); });
            }

            selection.html("").append('button')
                .attr('class', 'narrow close')
                .html("")
                .on('click', function() {
                    event.close(entity);
                });

            selection.append('div')
                .attr('class', 'head inspector-inner')
                .call(drawhead);

            var inspectorbody = selection.append('div')
                .attr('class', 'inspector-body');

            inspectorwrap = inspectorbody
                .append('ul').attr('class', 'inspector-inner tag-wrap fillL2');

            inspectorwrap.append('h4').text('Edit tags');

            var formsel = draw(entity.tags);

            inspectorbody.append('div')
                .attr('class', 'inspector-buttons').call(drawbuttons);

            var inHeight = inspectorbody.node().offsetHeight;

            inspectorbody.style('display', 'none')
                .style('margin-top', (-inHeight) + 'px');

            var inspectortoggle = selection.append('button')
                .attr('class', 'inspector-toggle action')
                .on('click', function() {
                    inspectortoggle.style('display', 'none');
                    inspectorbody
                        .style('display', 'block')
                        .transition()
                        .style('margin-top', '0px');
                });

            formsel.select('input').node().focus();

            inspectortoggle.append('span')
                .text('Details')
                .attr('class','label');
        });
    }

    function unentries(entries) {
        return d3.nest()
            .key(function(d) { return d.key; })
            .rollup(function(v) { return v[0].value; })
            .map(entries);
    }

    inspector.tags = function () {
        var grabbed = [];
        function grab(d) { if (d.key !== '') grabbed.push(d); }
        inspectorwrap.selectAll('li').each(grab);
        return unentries(grabbed);
    };

    return d3.rebind(inspector, event, 'on');
};
<|endoftext|>