Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Stash iteration of gpstile

  • Loading branch information...
commit f1888154c6bad360a6b9f425f31a4d2eef7ab2e9 1 parent ea41c2b
@tmcw tmcw authored
View
2  index.html
@@ -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/gpstile.js'></script>
<script src='js/id/id.js'></script>
<script src='js/id/util.js'></script>
@@ -39,6 +40,7 @@
<script src="js/id/geo.js"></script>
<script src="js/id/geo/extent.js"></script>
+ <script src='js/id/renderer/gpx.js'></script>
<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>
View
85 js/id/renderer/gpx.js
@@ -0,0 +1,85 @@
+iD.Gpx = function() {
+ var tileSize = 256,
+ tile = d3.geo.tile(),
+ projection,
+ cache = {},
+ offset = [0, 0],
+ tileOrigin,
+ z,
+ transformProp = iD.util.prefixCSSProperty('Transform'),
+ source = d3.functor('');
+
+ function tileSizeAtZoom(d, z) {
+ return Math.ceil(tileSize * Math.pow(2, z - d[2])) / tileSize;
+ }
+
+ // Update tiles based on current state of `projection`.
+ function background(selection) {
+ tile.scale(projection.scale())
+ .translate(projection.translate());
+
+ tileOrigin = [
+ projection.scale() / 2 - projection.translate()[0],
+ projection.scale() / 2 - projection.translate()[1]];
+
+ z = Math.max(Math.log(projection.scale()) / Math.log(2) - 8, 0);
+
+ render(selection);
+ }
+
+ // Derive the tiles onscreen, remove those offscreen and position them.
+ // Important that this part not depend on `projection` because it's
+ // rentered when tiles load/error (see #644).
+ function render(selection) {
+
+ var requests = tile();
+
+ function imageTransform(d) {
+ var _ts = tileSize * Math.pow(2, z - d[2]);
+ var scale = tileSizeAtZoom(d, z);
+ return 'translate(' +
+ (Math.round((d[0] * _ts) - tileOrigin[0])) + 'px,' +
+ (Math.round((d[1] * _ts) - tileOrigin[1])) + 'px)' +
+ 'scale(' + scale + ',' + scale + ')';
+ }
+
+ var canvas = selection
+ .selectAll('canvas')
+ .data(requests, function(d) { return d.join(','); });
+
+ canvas.exit()
+ .style(transformProp, imageTransform)
+ .classed('tile-loaded', false)
+ .each(function() {
+ var tile = this;
+ window.setTimeout(function() {
+ // this tile may already be removed
+ if (tile.parentNode) {
+ tile.parentNode.removeChild(tile);
+ }
+ }, 300);
+ });
+
+ canvas.enter().append('canvas')
+ .attr('class', 'tile')
+ .each(function(d) {
+ gpsTile(d, this, function() {});
+ });
+
+ canvas.style(transformProp, imageTransform);
+ }
+
+ background.projection = function(_) {
+ if (!arguments.length) return projection;
+ projection = _;
+ return background;
+ };
+
+ background.size = function(_) {
+ if (!arguments.length) return tile.size();
+ tile.size(_);
+ return background;
+ };
+
+ return background;
+};
View
9 js/id/renderer/map.js
@@ -13,6 +13,8 @@ iD.Map = function(context) {
minzoom = 0,
background = iD.Background()
.projection(projection),
+ gpx = iD.Gpx()
+ .projection(projection),
transformProp = iD.util.prefixCSSProperty('Transform'),
points = iD.svg.Points(roundedProjection, context),
vertices = iD.svg.Vertices(roundedProjection, context),
@@ -32,6 +34,9 @@ iD.Map = function(context) {
tilegroup = selection.append('div')
.attr('id', 'tile-g');
+ gpxgroup = selection.append('div')
+ .attr('id', 'gpx-g');
+
var supersurface = selection.append('div')
.style('position', 'absolute');
@@ -51,6 +56,7 @@ iD.Map = function(context) {
map.size(selection.size());
map.surface = surface;
map.tilesurface = tilegroup;
+ map.gpxsurface = gpxgroup;
supersurface
.call(tail);
@@ -132,6 +138,7 @@ iD.Map = function(context) {
'translate(' + tX + 'px,' + tY + 'px) ';
tilegroup.style(transformProp, transform);
+ gpxgroup.style(transformProp, transform);
surface.style(transformProp, transform);
queueRedraw();
@@ -143,6 +150,7 @@ iD.Map = function(context) {
if (!prop || prop === 'none') return false;
surface.node().style[transformProp] = '';
tilegroup.node().style[transformProp] = '';
+ gpxgroup.node().style[transformProp] = '';
return true;
}
@@ -166,6 +174,7 @@ iD.Map = function(context) {
if (!difference) {
tilegroup.call(background);
+ gpxgroup.call(gpx);
}
if (map.editable()) {
View
465 js/lib/gpstile.js
@@ -0,0 +1,465 @@
+;(function(e,t,n,r){function i(r){if(!n[r]){if(!t[r]){if(e)return e(r);throw new Error("Cannot find module '"+r+"'")}var s=n[r]={exports:{}};t[r][0](function(e){var n=t[r][1][e];return i(n?n:e)},s,s.exports)}return n[r].exports}for(var s=0;s<r.length;s++)i(r[s]);return i})(typeof require!=="undefined"&&require,{1:[function(require,module,exports){
+window.gpsTile = require('./gpstile.js');
+
+},{"./gpstile.js":2}],2:[function(require,module,exports){
+var osmGpx = require('osm-gpx'),
+ sph = require('sphericalmercator'),
+ proj = new sph(),
+ base = 'http://api.openstreetmap.org/api/0.6/trackpoints?bbox=',
+ colors = ['cyan', 'magenta', 'yellow', '#96FFA7'];
+
+function gpsTile(xyz, canvas, cb) {
+ osmGpx(proj.bbox(xyz[0], xyz[1], xyz[2]), 5, function(err, gpx, gj) {
+ if (err) return;
+ draw(canvas, xyz, gj);
+ cb();
+ });
+}
+
+function dist(a, b) {
+ return (Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]));
+}
+
+function tileProj(ll, xyz) {
+ var px = proj.px(ll, xyz[2]);
+ px[0] -= xyz[0] * 256;
+ px[1] -= xyz[1] * 256;
+ return [~~px[0], ~~px[1]];
+}
+
+function draw(c, xyz, gj) {
+ var ctx = c.getContext('2d'), px, color = 0;
+ ctx.lineWidth = 1;
+ ctx.globalAlpha = 0.9;
+ ctx.globalCompositeOperation = 'lighter';
+ for (var i = 0; i < gj.features.length; i++) {
+ var f = gj.features[i],
+ last = [];
+ if (f.geometry.type == 'LineString') {
+ var coords = f.geometry.coordinates;
+ if (coords.length < 2) continue;
+ ctx.strokeStyle = colors[color++ % 4];
+
+ ctx.beginPath();
+ last = px = tileProj(coords[0], xyz);
+ ctx.moveTo(px[0], px[1]);
+
+ for (var j = 1; j < coords.length; j++) {
+ px = tileProj(coords[j], xyz);
+ var d = dist(px, last);
+ if (d > 30) {
+ ctx.stroke();
+ ctx.strokeStyle = colors[color++ % 4];
+ ctx.beginPath();
+ ctx.moveTo(px[0], px[1]);
+ last = px;
+ } else if (d > 2) {
+ ctx.lineTo(px[0], px[1]);
+ last = px;
+ }
+ }
+ ctx.stroke();
+ }
+ }
+}
+
+module.exports = gpsTile;
+
+},{"osm-gpx":3,"sphericalmercator":4}],4:[function(require,module,exports){
+var SphericalMercator = (function(){
+
+// Closures including constants and other precalculated values.
+var cache = {},
+ EPSLN = 1.0e-10,
+ D2R = Math.PI / 180,
+ R2D = 180 / Math.PI,
+ // 900913 properties.
+ A = 6378137,
+ MAXEXTENT = 20037508.34;
+
+
+// SphericalMercator constructor: precaches calculations
+// for fast tile lookups.
+function SphericalMercator(options) {
+ options = options || {};
+ this.size = options.size || 256;
+ if (!cache[this.size]) {
+ var size = this.size;
+ var c = cache[this.size] = {};
+ c.Bc = [];
+ c.Cc = [];
+ c.zc = [];
+ c.Ac = [];
+ for (var d = 0; d < 30; d++) {
+ c.Bc.push(size / 360);
+ c.Cc.push(size / (2 * Math.PI));
+ c.zc.push(size / 2);
+ c.Ac.push(size);
+ size *= 2;
+ }
+ }
+ this.Bc = cache[this.size].Bc;
+ this.Cc = cache[this.size].Cc;
+ this.zc = cache[this.size].zc;
+ this.Ac = cache[this.size].Ac;
+};
+
+// Convert lon lat to screen pixel value
+//
+// - `ll` {Array} `[lon, lat]` array of geographic coordinates.
+// - `zoom` {Number} zoom level.
+SphericalMercator.prototype.px = function(ll, zoom) {
+ var d = this.zc[zoom];
+ var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999);
+ var x = Math.round(d + ll[0] * this.Bc[zoom]);
+ var y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom]));
+ (x > this.Ac[zoom]) && (x = this.Ac[zoom]);
+ (y > this.Ac[zoom]) && (y = this.Ac[zoom]);
+ //(x < 0) && (x = 0);
+ //(y < 0) && (y = 0);
+ return [x, y];
+};
+
+// Convert screen pixel value to lon lat
+//
+// - `px` {Array} `[x, y]` array of geographic coordinates.
+// - `zoom` {Number} zoom level.
+SphericalMercator.prototype.ll = function(px, zoom) {
+ var g = (px[1] - this.zc[zoom]) / (-this.Cc[zoom]);
+ var lon = (px[0] - this.zc[zoom]) / this.Bc[zoom];
+ var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI);
+ return [lon, lat];
+};
+
+// Convert tile xyz value to bbox of the form `[w, s, e, n]`
+//
+// - `x` {Number} x (longitude) number.
+// - `y` {Number} y (latitude) number.
+// - `zoom` {Number} zoom.
+// - `tms_style` {Boolean} whether to compute using tms-style.
+// - `srs` {String} projection for resulting bbox (WGS84|900913).
+// - `return` {Array} bbox array of values in form `[w, s, e, n]`.
+SphericalMercator.prototype.bbox = function(x, y, zoom, tms_style, srs) {
+ // Convert xyz into bbox with srs WGS84
+ if (tms_style) {
+ y = (Math.pow(2, zoom) - 1) - y;
+ }
+ // Use +y to make sure it's a number to avoid inadvertent concatenation.
+ var ll = [x * this.size, (+y + 1) * this.size]; // lower left
+ // Use +x to make sure it's a number to avoid inadvertent concatenation.
+ var ur = [(+x + 1) * this.size, y * this.size]; // upper right
+ var bbox = this.ll(ll, zoom).concat(this.ll(ur, zoom));
+
+ // If web mercator requested reproject to 900913.
+ if (srs === '900913') {
+ return this.convert(bbox, '900913');
+ } else {
+ return bbox;
+ }
+};
+
+// Convert bbox to xyx bounds
+//
+// - `bbox` {Number} bbox in the form `[w, s, e, n]`.
+// - `zoom` {Number} zoom.
+// - `tms_style` {Boolean} whether to compute using tms-style.
+// - `srs` {String} projection of input bbox (WGS84|900913).
+// - `@return` {Object} XYZ bounds containing minX, maxX, minY, maxY properties.
+SphericalMercator.prototype.xyz = function(bbox, zoom, tms_style, srs) {
+ // If web mercator provided reproject to WGS84.
+ if (srs === '900913') {
+ bbox = this.convert(bbox, 'WGS84');
+ }
+
+ var ll = [bbox[0], bbox[1]]; // lower left
+ var ur = [bbox[2], bbox[3]]; // upper right
+ var px_ll = this.px(ll, zoom);
+ var px_ur = this.px(ur, zoom);
+ // Y = 0 for XYZ is the top hence minY uses px_ur[1].
+ var bounds = {
+ minX: Math.floor(px_ll[0] / this.size),
+ minY: Math.floor(px_ur[1] / this.size),
+ maxX: Math.floor((px_ur[0] - 1) / this.size),
+ maxY: Math.floor((px_ll[1] - 1) / this.size)
+ };
+ if (tms_style) {
+ var tms = {
+ minY: (Math.pow(2, zoom) - 1) - bounds.maxY,
+ maxY: (Math.pow(2, zoom) - 1) - bounds.minY
+ };
+ bounds.minY = tms.minY;
+ bounds.maxY = tms.maxY;
+ }
+ return bounds;
+};
+
+// Convert projection of given bbox.
+//
+// - `bbox` {Number} bbox in the form `[w, s, e, n]`.
+// - `to` {String} projection of output bbox (WGS84|900913). Input bbox
+// assumed to be the "other" projection.
+// - `@return` {Object} bbox with reprojected coordinates.
+SphericalMercator.prototype.convert = function(bbox, to) {
+ if (to === '900913') {
+ return this.forward(bbox.slice(0, 2)).concat(this.forward(bbox.slice(2,4)));
+ } else {
+ return this.inverse(bbox.slice(0, 2)).concat(this.inverse(bbox.slice(2,4)));
+ }
+};
+
+// Convert lon/lat values to 900913 x/y.
+SphericalMercator.prototype.forward = function(ll) {
+ var xy = [
+ A * ll[0] * D2R,
+ A * Math.log(Math.tan((Math.PI*0.25) + (0.5 * ll[1] * D2R)))
+ ];
+ // if xy value is beyond maxextent (e.g. poles), return maxextent.
+ (xy[0] > MAXEXTENT) && (xy[0] = MAXEXTENT);
+ (xy[0] < -MAXEXTENT) && (xy[0] = -MAXEXTENT);
+ (xy[1] > MAXEXTENT) && (xy[1] = MAXEXTENT);
+ (xy[1] < -MAXEXTENT) && (xy[1] = -MAXEXTENT);
+ return xy;
+};
+
+// Convert 900913 x/y values to lon/lat.
+SphericalMercator.prototype.inverse = function(xy) {
+ return [
+ (xy[0] * R2D / A),
+ ((Math.PI*0.5) - 2.0 * Math.atan(Math.exp(-xy[1] / A))) * R2D
+ ];
+};
+
+return SphericalMercator;
+
+})();
+
+if (typeof module !== 'undefined' && typeof exports !== 'undefined') {
+ module.exports = exports = SphericalMercator;
+}
+
+},{}],3:[function(require,module,exports){
+var toGeoJSON = require('togeojson'),
+ xml = require('basicrequest');
+var base = 'http://api.openstreetmap.org/api/0.6/trackpoints?bbox=';
+
+function osmGpx(bbox, pages, callback) {
+ if (!callback) {
+ callback = pages;
+ pages = 1;
+ }
+ var gj = null;
+ function run(page) {
+ xml(base + bbox + '&page=' + page, function(err, res) {
+ if (err) return callback(err, null);
+
+ var newGj = toGeoJSON.gpx(res.responseXML);
+ if (--pages && newGj.features.length) {
+ if (!gj) {
+ gj = newGj;
+ } else {
+ gj.features = gj.features.concat(newGj.features);
+ }
+ run(++page);
+ } else {
+ if (!gj) gj = newGj;
+ callback(null, res.responseXML, gj);
+ }
+ });
+ }
+
+ return run(0);
+}
+
+osmGpx.base = function(x) {
+ if (!x) return base;
+ else base = x;
+ return osmGpx;
+};
+
+if (typeof module !== 'undefined') module.exports = osmGpx;
+
+},{"togeojson":5,"basicrequest":6}],5:[function(require,module,exports){
+toGeoJSON = (function() {
+ var removeSpace = (/\s*/g),
+ trimSpace = (/^\s*|\s*$/g),
+ splitSpace = (/\s+/);
+ // generate a short, numeric hash of a string
+ 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;
+ }
+ // all Y children of X
+ 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)); }
+ // one Y child of X, if any, otherwise null
+ function get1(x, y) { var n = get(x, y); return n.length ? n[0] : null; }
+ // cast array x into numbers
+ function numarray(x) {
+ for (var j = 0, o = []; j < x.length; j++) o[j] = parseFloat(x[j]);
+ return o;
+ }
+ // get the content of a text node, if any
+ function nodeVal(x) { return x && x.firstChild && x.firstChild.nodeValue; }
+ // get one coordinate from a coordinate array, if any
+ function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); }
+ // get all coordinates from a coordinate array as [[],[]]
+ 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;
+ }
+
+ // create a new feature collection parent object
+ function fc() {
+ return {
+ type: 'FeatureCollection',
+ features: []
+ };
+ }
+
+ t = {
+ kml: function(doc, o) {
+ o = o || {};
+
+ var gj = fc(),
+ // styleindex keeps track of hashed styles in order to match features
+ styleIndex = {},
+ // atomic geospatial types supported by KML - MultiGeometry is
+ // handled separately
+ geotypes = ['Polygon', 'LineString', 'Point'],
+ // all root placemarks in the file
+ 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,
+ // a feature collection
+ gj = fc();
+ for (i = 0; i < tracks.length; i++) {
+ track = tracks[i];
+ var name = nodeVal(get1(track, 'name')),
+ 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;
+
+},{}],6:[function(require,module,exports){
+module.exports = function(url, callback) {
+ var xhr = new XMLHttpRequest(), twoHundred = /^20\d$/;
+ xhr.onreadystatechange = function() {
+ if (4 == xhr.readyState && 0 !== xhr.status) {
+ if (twoHundred.test(xhr.status)) callback(null, xhr);
+ else callback(xhr, null);
+ }
+ };
+ xhr.onerror = function(e) { return callback(e, null); };
+ xhr.open('GET', url, true);
+ xhr.send();
+};
+
+},{}]},{},[1])
+;
+
Please sign in to comment.
Something went wrong with that request. Please try again.