mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-14 17:52:55 +00:00
471 lines
15 KiB
JavaScript
471 lines
15 KiB
JavaScript
iD.Map = function() {
|
|
var connection, history,
|
|
dimensions = [],
|
|
dispatch = d3.dispatch('move'),
|
|
hover = null,
|
|
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('stroke'),
|
|
class_fill = iD.Style.styleClasses('stroke'),
|
|
class_area = iD.Style.styleClasses('area'),
|
|
class_casing = iD.Style.styleClasses('casing'),
|
|
transformProp = iD.util.prefixCSSProperty('Transform'),
|
|
support3d = iD.util.support3d(),
|
|
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')
|
|
.on('mouseover', hoverIn)
|
|
.on('mouseout', hoverOut)
|
|
.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 classHover(d) { return d.id === hover; }
|
|
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 = [], waynodes = [],
|
|
extent = map.extent(),
|
|
graph = history.graph();
|
|
|
|
if (!difference) {
|
|
all = graph.intersects(extent);
|
|
filter = d3.functor(true);
|
|
} else {
|
|
var only = {};
|
|
difference.forEach(function (id) {
|
|
only[id] = graph.fetch(id);
|
|
graph.parentWays(id).forEach(function (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)) {
|
|
waynodes.push(a);
|
|
}
|
|
}
|
|
var parentStructure = graph.parentStructure(ways);
|
|
var wayAccuracyHandles = ways.reduce(function(mem, w) {
|
|
return mem.concat(accuracyHandles(w));
|
|
}, []);
|
|
drawHandles(waynodes, parentStructure, filter);
|
|
drawAccuracyHandles(wayAccuracyHandles, filter);
|
|
drawCasings(lines, filter);
|
|
drawFills(areas, filter);
|
|
drawStrokes(lines, filter);
|
|
drawMarkers(points, filter);
|
|
}
|
|
|
|
function accuracyHandles(way) {
|
|
var 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
|
|
});
|
|
}
|
|
}
|
|
return handles;
|
|
}
|
|
|
|
function drawHandles(waynodes, parentStructure, filter) {
|
|
function shared(d) { return parentStructure[d.id] > 1; }
|
|
|
|
var handles = g.hit.selectAll('circle.handle')
|
|
.filter(filter)
|
|
.data(waynodes, key);
|
|
|
|
handles.exit().remove();
|
|
|
|
handles.enter().insert('circle', ':first-child')
|
|
.attr({
|
|
'class': 'handle'
|
|
});
|
|
|
|
handles.attr('transform', function(entity) {
|
|
var p = projection(entity.loc);
|
|
return 'translate(' + [~~p[0], ~~p[1]] +
|
|
')';
|
|
})
|
|
.classed('shared', shared)
|
|
.classed('hover', classHover);
|
|
|
|
handles.transition().duration(50).attr('r', function(d) {
|
|
return d.id === hover ? 8: 4;
|
|
});
|
|
}
|
|
|
|
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', function(entity) {
|
|
var p = projection(entity.loc);
|
|
return 'translate(' + [~~p[0], ~~p[1]] + ')';
|
|
});
|
|
}
|
|
|
|
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')
|
|
.classed('hover', classHover);
|
|
lines
|
|
.order()
|
|
.attr('d', getline)
|
|
.attr('class', class_gen)
|
|
.classed('hover', classHover);
|
|
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 drawMarkers(points, filter) {
|
|
var markers = g.hit.selectAll('g.marker')
|
|
.filter(filter)
|
|
.data(points, key);
|
|
markers.exit().remove();
|
|
var marker = markers.enter().append('g')
|
|
.attr('class', 'marker');
|
|
marker.append('circle')
|
|
.attr({ r: 10, cx: 8, cy: 8 });
|
|
marker.append('image')
|
|
.attr({ width: 16, height: 16 });
|
|
markers.attr('transform', function(d) {
|
|
var pt = projection(d.loc);
|
|
return 'translate(' + [~~pt[0], ~~pt[1]] + ') translate(-8, -8)';
|
|
});
|
|
markers.classed('hover', classHover);
|
|
markers.select('image').attr('xlink:href', iD.Style.markerimage);
|
|
}
|
|
|
|
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 hoverIn() {
|
|
var datum = d3.select(d3.event.target).datum();
|
|
if (datum instanceof iD.Entity) {
|
|
hover = datum.id;
|
|
redraw([hover]);
|
|
d3.select('.messages').text(datum.tags.name || '#' + datum.id);
|
|
}
|
|
}
|
|
|
|
function hoverOut() {
|
|
if (hover) {
|
|
var oldHover = hover;
|
|
hover = null;
|
|
redraw([oldHover]);
|
|
d3.select('.messages').text('');
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (support3d) {
|
|
tilegroup.style(transformProp,
|
|
'translate3d(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px, 0px)');
|
|
surface.style(transformProp,
|
|
'translate3d(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px, 0px)');
|
|
} else {
|
|
tilegroup.style(transformProp,
|
|
'translate(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px)');
|
|
surface.style(transformProp,
|
|
'translate(' + ~~(a[0] - b[0]) + 'px,' + ~~(a[1] - b[1]) + 'px)');
|
|
}
|
|
} 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.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');
|
|
};
|