From c94ec3b4a99273657ca72df803ae52bc81682bc3 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Tue, 19 Mar 2013 18:41:16 -0400 Subject: [PATCH] Local GPX file support, first push towards multilayer support. --- css/map.css | 8 ++ index.html | 2 + js/id/id.js | 5 +- js/id/renderer/background_source.js | 5 +- js/id/renderer/localgpx.js | 78 ++++++++++++++++++ js/id/renderer/map.js | 37 ++++++--- js/lib/togeojson.js | 120 ++++++++++++++++++++++++++++ 7 files changed, 239 insertions(+), 16 deletions(-) create mode 100644 js/id/renderer/localgpx.js create mode 100644 js/lib/togeojson.js diff --git a/css/map.css b/css/map.css index 18e37c010..a5823d071 100644 --- a/css/map.css +++ b/css/map.css @@ -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, diff --git a/index.html b/index.html index d39868f0f..5b928ea28 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,7 @@ + @@ -41,6 +42,7 @@ + diff --git a/js/id/id.js b/js/id/id.js index 5d8af6019..d325de139 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -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; diff --git a/js/id/renderer/background_source.js b/js/id/renderer/background_source.js index c3a493c6b..3ddc76477 100644 --- a/js/id/renderer/background_source.js +++ b/js/id/renderer/background_source.js @@ -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; diff --git a/js/id/renderer/localgpx.js b/js/id/renderer/localgpx.js new file mode 100644 index 000000000..65419bd20 --- /dev/null +++ b/js/id/renderer/localgpx.js @@ -0,0 +1,78 @@ +iD.LocalGpx = function() { + var tileSize = 256, + projection, + gj = {}, + 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([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.size = function(_) { + if (!arguments.length) return size; + size = _; + return render; + }; + + function over() { + d3.event.stopPropagation(); + d3.event.preventDefault(); + d3.event.dataTransfer.dropEffect = 'copy'; + console.log('here'); + } + + 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) { + gj = toGeoJSON.gpx(toDom(e.target.result)); + }; + + reader.readAsText(f); + }) + .on('dragenter.localgpx', over) + .on('dragexit.localgpx', over) + .on('dragover.localgpx', over); + + return render; +}; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index ba860fe8c..1088b4c55 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -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().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', 'layers-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('.tile-layer-group') + .data(layers); + + sel.exit().remove(); + + sel.enter().append('div') + .attr('class', 'tile-layer-group'); + + 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; diff --git a/js/lib/togeojson.js b/js/lib/togeojson.js new file mode 100644 index 000000000..65cfdd60f --- /dev/null +++ b/js/lib/togeojson.js @@ -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;