Merge pull request #1093 from systemed/layers

Layers
This commit is contained in:
Tom MacWright
2013-03-20 13:12:44 -07:00
11 changed files with 410 additions and 111 deletions

View File

@@ -1433,6 +1433,14 @@ div.combobox {
height:18px;
}
.background-control .layer-toggle-gpx .layer-extent {
display:none;
}
.background-control .layer-toggle-gpx.selected .layer-extent {
display:inline-block;
}
/* Geocoder */
.geocode-control, .geocode-control form {
@@ -1489,9 +1497,12 @@ div.combobox {
background:#000;
}
#surface, #tile-g {
#surface, #layer-g, .layer-layer {
position:absolute;
top:0;
left: 0;
right: 0;
bottom: 0;
transform-origin:0 0;
-ms-transform-origin:0 0;
-webkit-transform-origin:0 0;
@@ -1507,10 +1518,6 @@ div.combobox {
position: static;
}
#tile-g {
opacity: 0.5;
}
/* About Section
------------------------------------------------------- */

View File

@@ -886,6 +886,14 @@ text.point {
pointer-events: visibleStroke;
}
/* GPX Paths */
path.gpx {
stroke:#6AFF25;
stroke-width:2;
fill:transparent;
pointer-events: none;
}
/* Modes */
.mode-draw-line .vertex.active,

View File

@@ -184,3 +184,6 @@ en:
out: Zoom Out
imagery:
provided_by: "Imagery provided by {source}"
gpx:
local_layer: "Local GPX file"
drag_drop: "Drag and drop a .gpx file on the page"

View File

@@ -28,6 +28,7 @@
<script src='js/lib/d3-compat.js'></script>
<script src='js/lib/bootstrap-tooltip.js'></script>
<script src='js/lib/rtree.js'></script>
<script src='js/lib/togeojson.js'></script>
<script src='js/id/id.js'></script>
<script src='js/id/util.js'></script>
@@ -41,6 +42,7 @@
<script src='js/id/renderer/background.js'></script>
<script src='js/id/renderer/background_source.js'></script>
<script src='js/id/renderer/localgpx.js'></script>
<script src='js/id/renderer/map.js'></script>
<script src="js/id/svg.js"></script>

View File

@@ -91,7 +91,8 @@ window.iD = function () {
};
/* Map */
context.background = function() { return map.background; };
context.layers = function() { return map.layers; };
context.background = function() { return map.layers[0]; };
context.surface = function() { return map.surface; };
context.projection = map.projection;
context.tail = map.tail;
@@ -124,7 +125,7 @@ window.iD = function () {
var q = iD.util.stringQs(location.hash.substring(1)), detected = false;
if (q.layer) {
context.background()
context.layers()[0]
.source(_.find(backgroundSources, function(l) {
if (l.data.sourcetag === q.layer) {
detected = true;
@@ -163,6 +164,8 @@ iD.detect = function() {
browser.locale = navigator.language;
browser.filedrop = (window.FileReader && 'ondrop' in window);
function nav(x) {
return navigator.userAgent.indexOf(x) !== -1;
}

View File

@@ -2,7 +2,8 @@ iD.BackgroundSource = {};
// derive the url of a 'quadkey' style tile from a coordinate object
iD.BackgroundSource.template = function(data) {
var generator = function(coord) {
function generator(coord) {
var u = '';
for (var zoom = coord[2]; zoom > 0; zoom--) {
var b = 0;
@@ -25,7 +26,7 @@ iD.BackgroundSource.template = function(data) {
var subdomains = r.split(':')[1].split(',');
return subdomains[coord[2] % subdomains.length];
});
};
}
generator.data = data;

View File

@@ -0,0 +1,94 @@
iD.LocalGpx = function(context) {
var tileSize = 256,
projection,
gj = {},
enable = true,
size = [0, 0],
transformProp = iD.util.prefixCSSProperty('Transform'),
path = d3.geo.path().projection(projection),
source = d3.functor('');
function render(selection) {
path.projection(projection);
var surf = selection.selectAll('svg')
.data(enable ? [gj] : []);
surf.exit().remove();
surf.enter()
.append('svg')
.style('position', 'absolute');
var paths = surf
.selectAll('path')
.data(function(d) { return [d]; });
paths
.enter()
.append('path')
.attr('class', 'gpx');
paths
.attr('d', path);
}
function toDom(x) {
return (new DOMParser()).parseFromString(x, 'text/xml');
}
render.projection = function(_) {
if (!arguments.length) return projection;
projection = _;
return render;
};
render.enable = function(_) {
if (!arguments.length) return enable;
enable = _;
return render;
};
render.geojson = function(_) {
if (!arguments.length) return gj;
gj = _;
return render;
};
render.size = function(_) {
if (!arguments.length) return size;
size = _;
return render;
};
render.id = 'layer-gpx';
function over() {
d3.event.stopPropagation();
d3.event.preventDefault();
d3.event.dataTransfer.dropEffect = 'copy';
}
d3.select('body')
.attr('dropzone', 'copy')
.on('drop.localgpx', function() {
d3.event.stopPropagation();
d3.event.preventDefault();
var f = d3.event.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function(e) {
render.geojson(toGeoJSON.gpx(toDom(e.target.result)));
context.redraw();
context.map().pan([0, 0]);
};
reader.readAsText(f);
})
.on('dragenter.localgpx', over)
.on('dragexit.localgpx', over)
.on('dragover.localgpx', over);
return render;
};

View File

@@ -11,8 +11,9 @@ iD.Map = function(context) {
dblclickEnabled = true,
transformStart,
minzoom = 0,
background = iD.Background()
.projection(projection),
layers = [
iD.Background().projection(projection),
iD.LocalGpx(context).projection(projection)],
transformProp = iD.util.prefixCSSProperty('Transform'),
points = iD.svg.Points(roundedProjection, context),
vertices = iD.svg.Vertices(roundedProjection, context),
@@ -21,7 +22,7 @@ iD.Map = function(context) {
midpoints = iD.svg.Midpoints(roundedProjection),
labels = iD.svg.Labels(roundedProjection, context),
tail = iD.ui.Tail(),
surface, tilegroup;
surface, layergroup;
function map(selection) {
context.history()
@@ -29,8 +30,8 @@ iD.Map = function(context) {
selection.call(zoom);
tilegroup = selection.append('div')
.attr('id', 'tile-g');
layergroup = selection.append('div')
.attr('id', 'layer-g');
var supersurface = selection.append('div')
.style('position', 'absolute');
@@ -47,10 +48,9 @@ iD.Map = function(context) {
.attr('id', 'surface')
.call(iD.svg.Surface());
map.size(selection.size());
map.surface = surface;
map.tilesurface = tilegroup;
map.layersurface = layergroup;
supersurface
.call(tail);
@@ -131,7 +131,7 @@ iD.Map = function(context) {
'scale(' + scale + ')' +
'translate(' + tX + 'px,' + tY + 'px) ';
tilegroup.style(transformProp, transform);
layergroup.style(transformProp, transform);
surface.style(transformProp, transform);
queueRedraw();
@@ -142,7 +142,7 @@ iD.Map = function(context) {
var prop = surface.node().style[transformProp];
if (!prop || prop === 'none') return false;
surface.node().style[transformProp] = '';
tilegroup.node().style[transformProp] = '';
layergroup.node().style[transformProp] = '';
return true;
}
@@ -165,7 +165,18 @@ iD.Map = function(context) {
}
if (!difference) {
tilegroup.call(background);
var sel = layergroup
.selectAll('.layer-layer')
.data(layers);
sel.exit().remove();
sel.enter().append('div')
.attr('class', 'layer-layer');
sel.each(function(layer) {
d3.select(this).call(layer);
});
}
if (map.editable()) {
@@ -260,7 +271,9 @@ iD.Map = function(context) {
var center = map.center();
dimensions = _;
surface.size(dimensions);
background.size(dimensions);
layers.map(function(l) {
l.size(dimensions);
});
projection.clipExtent([[0, 0], dimensions]);
setCenter(center);
return redraw();
@@ -371,7 +384,7 @@ iD.Map = function(context) {
return map;
};
map.background = background;
map.layers = layers;
map.projection = projection;
map.redraw = redraw;

View File

@@ -1,9 +1,13 @@
iD.ui.Background = function(context) {
var event = d3.dispatch('cancel', 'save'),
key = 'b',
opacities = [1, 0.5, 0];
var layers = context.backgroundSources();
opacities = [1, 0.5, 0],
directions = [
['left', [1, 0]],
['top', [0, -1]],
['right', [-1, 0]],
['bottom', [0, 1]]],
layers = context.backgroundSources();
function getSources() {
var ext = context.map().extent();
@@ -15,24 +19,6 @@ iD.ui.Background = function(context) {
function background(selection) {
var content = selection.append('div')
.attr('class', 'content fillD map-overlay hide'),
shown = false;
var tooltip = bootstrap.tooltip()
.placement('right')
.html(true)
.title(iD.ui.tooltipHtml(t('background.description'), key));
var button = selection.append('button')
.attr('tabindex', -1)
.attr('class', 'fillD')
.on('click.background-toggle', toggle)
.call(tooltip);
button.append('span')
.attr('class', 'layers icon');
function toggle() {
tooltip.hide(button);
setVisible(content.classed('hide'));
@@ -55,56 +41,23 @@ iD.ui.Background = function(context) {
}
}
context.surface().on('mousedown.background-outside', function() {
setVisible(false);
});
context.container().on('mousedown.background-outside', function() {
setVisible(false);
});
var opa = content
.append('div')
.attr('class', 'opacity-options-wrapper');
opa.append('h4')
.text(t('background.title'));
var opacityList = opa.append('ul')
.attr('class', 'opacity-options');
function setOpacity(d) {
context.map().tilesurface
context.map().layersurface.selectAll('.layer-layer')
.filter(function(d) { return d == context.map().layers[0]; })
.transition()
.style('opacity', d)
.attr('data-opacity', d);
opacityList.selectAll('li')
.classed('selected', false);
d3.select(this)
.classed('selected', true);
if (d3.event) {
d3.select(this)
.classed('selected', true);
}
}
opacityList.selectAll('div.opacity')
.data(opacities)
.enter()
.append('li')
.attr('data-original-title', function(d) {
return t('background.percent_brightness', { opacity: (d * 100) });
})
.on('click.set-opacity', setOpacity)
.html("<div class='select-box'></div>")
.call(bootstrap.tooltip()
.placement('top'))
.append('div')
.attr('class', 'opacity')
.style('opacity', String);
// Make sure there is an active selection by default
opa.select('.opacity-options li:nth-child(2)')
.classed('selected', true);
function selectLayer(d) {
content.selectAll('a.layer')
.classed('selected', function(d) {
return d.data.name === context.background().source().data.name;
@@ -135,9 +88,16 @@ iD.ui.Background = function(context) {
selectLayer(d);
}
var layerList = content
.append('ul')
.attr('class', 'toggle-list fillL');
function clickGpx(d) {
d3.event.preventDefault();
if (!_.isEmpty(context.map().layers[1].geojson())) {
context.map().layers[1]
.enable(!context.map().layers[1].enable());
d3.select(this)
.classed('selected', context.map().layers[1].enable());
context.redraw();
}
}
function update() {
var layerLinks = layerList.selectAll('a.layer')
@@ -169,26 +129,122 @@ iD.ui.Background = function(context) {
return d.data.name;
});
gpxLayerItem
.classed('selected', function() {
var gpxLayer = context.map().layers[1];
return !_.isEmpty(gpxLayer.geojson()) &&
gpxLayer.enable();
});
layerLinks.exit()
.remove();
selectLayer(context.background().source());
}
context.map().on('move.background-update', _.debounce(update, 1000));
function clickNudge(d) {
var interval = window.setInterval(nudge, 100);
update();
d3.select(this).on('mouseup', function() {
window.clearInterval(interval);
nudge();
});
function nudge() {
context.background().nudge(d[1], context.map().zoom());
context.redraw();
}
}
var content = selection.append('div')
.attr('class', 'content fillD map-overlay hide'),
tooltip = bootstrap.tooltip()
.placement('right')
.html(true)
.title(iD.ui.tooltipHtml(t('background.description'), key)),
button = selection.append('button')
.attr('tabindex', -1)
.attr('class', 'fillD')
.on('click.background-toggle', toggle)
.call(tooltip),
opa = content
.append('div')
.attr('class', 'opacity-options-wrapper'),
shown = false;
button.append('span')
.attr('class', 'layers icon');
opa.append('h4')
.text(t('background.title'));
context.surface().on('mousedown.background-outside', function() {
setVisible(false);
});
context.container().on('mousedown.background-outside', function() {
setVisible(false);
});
var opacityList = opa.append('ul')
.attr('class', 'opacity-options');
opacityList.selectAll('div.opacity')
.data(opacities)
.enter()
.append('li')
.attr('data-original-title', function(d) {
return t('background.percent_brightness', { opacity: (d * 100) });
})
.on('click.set-opacity', setOpacity)
.html("<div class='select-box'></div>")
.call(bootstrap.tooltip()
.placement('top'))
.append('div')
.attr('class', 'opacity')
.style('opacity', String);
// Make sure there is an active selection by default
opa.select('.opacity-options li:nth-child(2)')
.classed('selected', true);
var layerList = content
.append('ul')
.attr('class', 'toggle-list fillL');
var gpxLayerItem = content
.append('ul')
.style('display', iD.detect().filedrop ? 'block' : 'none')
.attr('class', 'toggle-list fillL')
.append('li')
.append('a')
.classed('layer-toggle-gpx', true)
.call(bootstrap.tooltip()
.title(t('gpx.drag_drop'))
.placement('right'))
.on('click.set-gpx', clickGpx);
gpxLayerItem
.append('span')
.attr('class', 'icon toggle');
gpxLayerItem.append('span')
.text(t('gpx.local_layer'));
gpxLayerItem
.append('a')
.attr('class', 'icon geocode layer-extent')
.on('click', function() {
d3.event.preventDefault();
d3.event.stopPropagation();
context.map()
.extent(d3.geo.bounds(context.map().layers[1].geojson()));
});
var adjustments = content
.append('div')
.attr('class', 'adjustments pad1');
var directions = [
['left', [1, 0]],
['top', [0, -1]],
['right', [-1, 0]],
['bottom', [0, 1]]];
adjustments.append('a')
.text(t('background.fix_misalignment'))
.attr('href', '#')
@@ -197,8 +253,7 @@ iD.ui.Background = function(context) {
.on('click', function() {
var exp = d3.select(this).classed('expanded');
nudge_container.style('display', exp ? 'none' : 'block');
d3.select(this)
.classed('expanded', !exp);
d3.select(this).classed('expanded', !exp);
d3.event.preventDefault();
});
@@ -212,20 +267,7 @@ iD.ui.Background = function(context) {
.append('button')
.attr('class', function(d) { return d[0] + ' nudge'; })
.text(function(d) { return d[0]; })
.on('mousedown', function(d) {
var interval = window.setInterval(nudge, 100);
d3.select(this).on('mouseup', function() {
window.clearInterval(interval);
nudge();
});
function nudge() {
context.background().nudge(d[1], context.map().zoom());
context.redraw();
}
});
.on('mousedown', clickNudge);
nudge_container.append('button')
.text(t('background.reset'))
@@ -235,8 +277,12 @@ iD.ui.Background = function(context) {
context.redraw();
});
var keybinding = d3.keybinding('background');
context.map()
.on('move.background-update', _.debounce(update, 1000));
update();
setOpacity(0.5);
var keybinding = d3.keybinding('background');
keybinding.on(key, toggle);
d3.select(document)

120
js/lib/togeojson.js Normal file
View File

@@ -0,0 +1,120 @@
toGeoJSON = (function() {
var removeSpace = (/\s*/g), trimSpace = (/^\s*|\s*$/g), splitSpace = (/\s+/);
function okhash(x) {
if (!x || !x.length) return 0;
for (var i = 0, h = 0; i < x.length; i++) {
h = ((h << 5) - h) + x.charCodeAt(i) | 0;
} return h;
}
function get(x, y) { return x.getElementsByTagName(y); }
function attr(x, y) { return x.getAttribute(y); }
function attrf(x, y) { return parseFloat(attr(x, y)); }
function get1(x, y) { var n = get(x, y); return n.length ? n[0] : null; }
function numarray(x) {
for (var j = 0, o = []; j < x.length; j++) o[j] = parseFloat(x[j]);
return o;
}
function nodeVal(x) { return x && x.firstChild && x.firstChild.nodeValue; }
function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); }
function coord(v) {
var coords = v.replace(trimSpace, '').split(splitSpace), o = [];
for (var i = 0; i < coords.length; i++) o.push(coord1(coords[i]));
return o;
}
function fc() { return { type: 'FeatureCollection', features: [] }; }
t = {
kml: function(doc, o) {
o = o || {};
var gj = fc(), styleIndex = {},
geotypes = ['Polygon', 'LineString', 'Point'],
placemarks = get(doc, 'Placemark'), styles = get(doc, 'Style');
if (o.styles) for (var k = 0; k < styles.length; k++) {
styleIndex['#' + styles[k].id] = okhash(styles[k].innerHTML).toString(16);
}
for (var j = 0; j < placemarks.length; j++) {
gj.features = gj.features.concat(getPlacemark(placemarks[j]));
}
function getGeometry(root) {
var geomNode, geomNodes, i, j, k, geoms = [];
if (get1(root, 'MultiGeometry')) return getGeometry(get1(root, 'MultiGeometry'));
for (i = 0; i < geotypes.length; i++) {
geomNodes = get(root, geotypes[i]);
if (geomNodes) {
for (j = 0; j < geomNodes.length; j++) {
geomNode = geomNodes[j];
if (geotypes[i] == 'Point') {
geoms.push({ type: 'Point',
coordinates: coord1(nodeVal(get1(geomNode, 'coordinates')))
});
} else if (geotypes[i] == 'LineString') {
geoms.push({ type: 'LineString',
coordinates: coord(nodeVal(get1(geomNode, 'coordinates')))
});
} else if (geotypes[i] == 'Polygon') {
var rings = get(geomNode, 'LinearRing'), coords = [];
for (k = 0; k < rings.length; k++) {
coords.push(coord(nodeVal(get1(rings[k], 'coordinates'))));
}
geoms.push({ type: 'Polygon', coordinates: coords });
}
}
}
}
return geoms;
}
function getPlacemark(root) {
var geoms = getGeometry(root), i, properties = {},
name = nodeVal(get1(root, 'name')),
styleUrl = nodeVal(get1(root, 'styleUrl')),
description = nodeVal(get1(root, 'description')),
extendedData = get1(root, 'ExtendedData');
if (!geoms.length) return false;
if (name) properties.name = name;
if (styleUrl && styleIndex[styleUrl]) {
properties.styleUrl = styleUrl;
properties.styleHash = styleIndex[styleUrl];
}
if (description) properties.description = description;
if (extendedData) {
var datas = get(extendedData, 'Data'),
simpleDatas = get(extendedData, 'SimpleData');
for (i = 0; i < datas.length; i++) {
properties[datas[i].getAttribute('name')] = nodeVal(get1(datas[i], 'value'));
}
for (i = 0; i < simpleDatas.length; i++) {
properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]);
}
}
return [{ type: 'Feature', geometry: (geoms.length === 1) ? geoms[0] : {
type: 'GeometryCollection',
geometries: geoms }, properties: properties }];
}
return gj;
},
gpx: function(doc, o) {
var i, j, tracks = get(doc, 'trk'), track, pt, gj = fc();
for (i = 0; i < tracks.length; i++) {
track = tracks[i];
var name = nodeVal(get1(track, 'name'));
var pts = get(track, 'trkpt'), line = [];
for (j = 0; j < pts.length; j++) {
line.push([attrf(pts[j], 'lon'), attrf(pts[j], 'lat')]);
}
gj.features.push({
type: 'Feature',
properties: {
name: name || ''
},
geometry: { type: 'LineString', coordinates: line }
});
}
return gj;
}
};
return t;
})();
if (typeof module !== 'undefined') module.exports = toGeoJSON;

View File

@@ -31,6 +31,7 @@
<script src='../js/lib/d3-compat.js'></script>
<script src='../js/lib/bootstrap-tooltip.js'></script>
<script src='../js/lib/rtree.js'></script>
<script src='../js/lib/togeojson.js'></script>
<script src='../js/id/id.js'></script>
<script src='../js/id/util.js'></script>
@@ -45,6 +46,7 @@
<script src='../js/id/renderer/background.js'></script>
<script src='../js/id/renderer/background_source.js'></script>
<script src='../js/id/renderer/map.js'></script>
<script src='../js/id/renderer/localgpx.js'></script>
<script src="../js/id/svg.js"></script>
<script src="../js/id/svg/areas.js"></script>