Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Checkpoint first working version.

Some of the important things left to do:

  - Variable-size buffers when missing Content-Length.
  - Example of rendering dots on top of tiles.
  - Other projections? Make location-agnostic?
  - Full-scene antialiasing to eliminate tile seams.
  - Retry and throttling for tile-loading.

But hey, it works!
  • Loading branch information...
commit f563f0b824888203925725d2613673e10453f23c 1 parent 6b70dcd
Mike Bostock authored
26 LICENSE
View
@@ -0,0 +1,26 @@
+Copyright (c) 2011, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
88 lib/cache.js
View
@@ -0,0 +1,88 @@
+var http = require("http"),
+ url = require("url");
+
+var map = {},
+ head = null,
+ tail = null,
+ size = 512,
+ n = 0;
+
+var headers = {
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.767.1 Safari/534.36"
+};
+
+module.exports = function(key, callback) {
+ var value = map[key];
+
+ // If this value is in the cache…
+ if (value) {
+
+ // Move it to the front of the least-recently used list.
+ if (value.previous) {
+ value.previous.next = value.next;
+ if (value.next) value.next.previous = value.previous;
+ else tail = value.previous;
+ value.previous = null;
+ value.next = head;
+ head.previous = value;
+ head = value;
+ }
+
+ // If the value is loaded, callback.
+ // Otherwise, add the callback to the list.
+ return value.callbacks
+ ? value.callbacks.push(callback)
+ : callback(value.value);
+ }
+
+ // Otherwise, add the value to the cache.
+ value = map[key] = {
+ key: key,
+ next: head,
+ previous: null,
+ callbacks: [callback]
+ };
+
+ // Add the value to the front of the least-recently used list.
+ if (head) head.previous = value;
+ else tail = value;
+ head = value;
+ n++;
+
+ // Load the requested resource!
+ var u = url.parse(key);
+ http.get({
+ host: u.host,
+ port: u.port,
+ path: u.pathname + (u.search ? "?" + u.search : ""),
+ headers: headers
+ }, function(response) {
+ var body = new Buffer(+response.headers['content-length'] || 2048), // TODO realloc
+ offset = 0;
+ response
+ .on("data", function(chunk) {
+ offset += chunk.copy(body, offset);
+ })
+ .on("end", function() {
+ body = body.slice(0, offset);
+ value.callbacks.forEach(function(callback) { callback(body); });
+ delete value.callbacks;
+ });
+ }).on("error", function(error) {
+ callback(null);
+ });
+
+ flush();
+};
+
+// Flush any extra values.
+function flush() {
+ for (var value = tail; n > size && value; value = value.previous) {
+ n--;
+ delete map[value.key];
+ if (value.next) value.next.previous = value.previous;
+ else if (tail = value.previous) tail.next = null;
+ if (value.previous) value.previous.next = value.next;
+ else if (head = value.next) head.previous = null;
+ }
+}
164 lib/image.js
View
@@ -0,0 +1,164 @@
+var Canvas = require("../../node-canvas/lib/canvas"),
+ Image = Canvas.Image,
+ _cache = require("./cache"),
+ _url = require("./url");
+
+module.exports = function() {
+ var image = {},
+ view,
+ url,
+ zoom;
+// transform,
+// levelZoom;
+
+ image.view = function(x) {
+ if (!arguments.length) return view;
+ view = x;
+ return image;
+ };
+
+ image.url = function(x) {
+ if (!arguments.length) return url;
+ url = typeof x === "string" && /{.}/.test(x) ? _url(x) : x;
+ return image;
+ };
+
+ image.zoom = function(x) {
+ if (!arguments.length) return zoom;
+ zoom = x;
+ return image;
+ };
+
+ image.render = function(context, callback) {
+ var viewZoom = view.zoom(),
+ viewZoomFraction = viewZoom - (viewZoom = Math.round(viewZoom)),
+ viewSize = view.size(),
+ viewAngle = view.angle(),
+ tileSize = view.tileSize(),
+ tileCenter = view.locationCoordinate(view.center());
+
+ // get the coordinates of the four corners
+ var c0 = view.pointCoordinate(tileCenter, [0, 0]),
+ c1 = view.pointCoordinate(tileCenter, [viewSize[0], 0]),
+ c2 = view.pointCoordinate(tileCenter, [viewSize[0], viewSize[1]]),
+ c3 = view.pointCoordinate(tileCenter, [0, viewSize[1]]);
+
+ // TODO layer-specific coordinate transform
+// if (transform) {
+// c0 = transform.unapply(c0);
+// c1 = transform.unapply(c1);
+// c2 = transform.unapply(c2);
+// c3 = transform.unapply(c3);
+// tileCenter = transform.unapply(tileCenter);
+// }
+
+ // layer-specific zoom transform
+ var tileLevel = zoom ? zoom(c0[2]) - c0[2] : 0;
+ if (tileLevel) {
+ var k = Math.pow(2, tileLevel);
+ c0[0] *= k;
+ c1[0] *= k;
+ c2[0] *= k;
+ c3[0] *= k;
+ c0[1] *= k;
+ c1[1] *= k;
+ c2[1] *= k;
+ c3[1] *= k;
+ c0[2] =
+ c1[2] =
+ c2[2] =
+ c3[2] += tileLevel;
+ }
+
+ // load the tiles!
+ var loading = 0, z = c0[2], ymax = z < 0 ? 1 : 1 << z;
+ scanTriangle(c0, c1, c2, 0, ymax, scanLine);
+ scanTriangle(c2, c3, c0, 0, ymax, scanLine);
+
+ // scan-line conversion
+ function scanLine(x0, x1, y) {
+ for (var x = x0; x < x1; x++) {
+ load([x, y, z]);
+ }
+ }
+
+ // load a tile from the cache and draw it to the context
+ function load(tile) {
+ loading++;
+ _cache(url(tile), function(buffer) {
+ var image = new Image(), k = Math.pow(2, viewZoomFraction);
+ image.src = buffer;
+ context.save();
+ context.translate(viewSize[0] / 2, viewSize[1] / 2);
+ context.rotate(viewAngle);
+ context.scale(k, k);
+ // TODO transform
+ context.drawImage(
+ image,
+ tileSize[0] * (tile[0] - tileCenter[0]),
+ tileSize[1] * (tile[1] - tileCenter[1]),
+ tileSize[0],
+ tileSize[1]);
+ context.restore();
+ if (!--loading && callback) callback();
+ });
+ }
+
+ return image;
+ };
+
+ return image;
+};
+
+// scan-line conversion
+function edge(a, b) {
+ if (a[1] > b[1]) { var t = a; a = b; b = t; }
+ return {
+ x0: a[0],
+ y0: a[1],
+ x1: b[0],
+ y1: b[1],
+ dx: b[0] - a[0],
+ dy: b[1] - a[1]
+ };
+}
+
+// scan-line conversion
+function scanSpans(e0, e1, ymin, ymax, scanLine) {
+ var y0 = Math.max(ymin, Math.floor(e1.y0)),
+ y1 = Math.min(ymax, Math.ceil(e1.y1));
+
+ // sort edges by x-coordinate
+ if ((e0.x0 == e1.x0 && e0.y0 == e1.y0)
+ ? (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1)
+ : (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
+ var t = e0; e0 = e1; e1 = t;
+ }
+
+ // scan lines!
+ var m0 = e0.dx / e0.dy,
+ m1 = e1.dx / e1.dy,
+ d0 = e0.dx > 0, // use y + 1 to compute x0
+ d1 = e1.dx < 0; // use y + 1 to compute x1
+ for (var y = y0; y < y1; y++) {
+ var x0 = m0 * Math.max(0, Math.min(e0.dy, y + d0 - e0.y0)) + e0.x0,
+ x1 = m1 * Math.max(0, Math.min(e1.dy, y + d1 - e1.y0)) + e1.x0;
+ scanLine(Math.floor(x1), Math.ceil(x0), y);
+ }
+}
+
+// scan-line conversion
+function scanTriangle(a, b, c, ymin, ymax, scanLine) {
+ var ab = edge(a, b),
+ bc = edge(b, c),
+ ca = edge(c, a);
+
+ // sort edges by y-length
+ if (ab.dy > bc.dy) { var t = ab; ab = bc; bc = t; }
+ if (ab.dy > ca.dy) { var t = ab; ab = ca; ca = t; }
+ if (bc.dy > ca.dy) { var t = bc; bc = ca; ca = t; }
+
+ // scan span! scan span!
+ if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine);
+ if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine);
+}
5 lib/mappy.js
View
@@ -0,0 +1,5 @@
+module.exports = {
+ view: require("./view"),
+ image: require("./image"),
+ url: require("./url")
+};
25 lib/polymaps/LICENSE
View
@@ -0,0 +1,25 @@
+Copyright (c) 2010, SimpleGeo and Stamen Design
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of SimpleGeo nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL SIMPLEGEO BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
45 lib/transform.js
View
@@ -0,0 +1,45 @@
+module.exports = function(a, b, c, d, e, f) {
+ var transform = {},
+ zoomDelta,
+ zoomFraction,
+ k;
+
+ if (!arguments.length) {
+ a = 1; c = 0; e = 0;
+ b = 0; d = 1; f = 0;
+ }
+
+ transform.zoomFraction = function(x) {
+ if (!arguments.length) return zoomFraction;
+ zoomFraction = x;
+ zoomDelta = Math.floor(zoomFraction + Math.log(Math.sqrt(a * a + b * b + c * c + d * d)) / Math.LN2);
+ k = Math.pow(2, -zoomDelta);
+ return transform;
+ };
+
+ transform.apply = function(x) {
+ var k0 = Math.pow(2, -x[2]),
+ k1 = Math.pow(2, x[2] - zoomDelta);
+ return [
+ (a * x[0] * k0 + c * x[1] * k0 + e) * k1,
+ (b * x[0] * k0 + d * x[1] * k0 + f) * k1,
+ x[2] - zoomDelta
+ ];
+ };
+
+ transform.unapply = function(x) {
+ var k0 = Math.pow(2, -x[2]),
+ k1 = Math.pow(2, x[2] + zoomDelta);
+ return [
+ (x[0] * k0 * d - x[1] * k0 * c - e * d + f * c) / (a * d - b * c) * k1,
+ (x[0] * k0 * b - x[1] * k0 * a - e * b + f * a) / (c * b - d * a) * k1,
+ x[2] + zoomDelta
+ ];
+ };
+
+// transform.toString = function() {
+// return "matrix(" + [a * k, b * k, c * k, d * k].join(" ") + " 0 0)";
+// };
+
+ return transform.zoomFraction(0);
+};
53 lib/url.js
View
@@ -0,0 +1,53 @@
+module.exports = function(template) {
+ var hosts = [],
+ repeat = true;
+
+ function format(c) {
+ var max = c[2] < 0 ? 1 : 1 << c[2],
+ column = c[0];
+ if (repeat) {
+ column = c[0] % max;
+ if (column < 0) column += max;
+ } else if ((column < 0) || (column >= max)) {
+ return null;
+ }
+ return template.replace(/{(.)}/g, function(s, v) {
+ switch (v) {
+ case "S": return hosts[(Math.abs(c[2]) + c[1] + column) % hosts.length];
+ case "Z": return c[2];
+ case "X": return column;
+ case "Y": return c[1];
+ case "B": {
+ var nw = po.map.coordinateLocation([column, c[1], c[2]]),
+ se = po.map.coordinateLocation([column + 1, c[1] + 1, c[2]]),
+ pn = Math.ceil(Math.log(c[2]) / Math.LN2);
+ return se.lat.toFixed(pn)
+ + "," + nw.lon.toFixed(pn)
+ + "," + nw.lat.toFixed(pn)
+ + "," + se.lon.toFixed(pn);
+ }
+ }
+ return v;
+ });
+ }
+
+ format.template = function(x) {
+ if (!arguments.length) return template;
+ template = x;
+ return format;
+ };
+
+ format.hosts = function(x) {
+ if (!arguments.length) return hosts;
+ hosts = x;
+ return format;
+ };
+
+ format.repeat = function(x) {
+ if (!arguments.length) return repeat;
+ repeat = x;
+ return format;
+ };
+
+ return format;
+};
229 lib/view.js
View
@@ -0,0 +1,229 @@
+// Canvas = require("canvas"),
+
+var _view = module.exports = function() {
+ var view = {},
+ size = [0, 0],
+ tileSize = [256, 256],
+ ymin = -180, // lat2y(centerRange[0][1])
+ ymax = 180, // lat2y(centerRange[1][1])
+ center = [-122.41948, 37.76487],
+ centerRange = [[-Infinity, y2lat(ymin)], [Infinity, y2lat(ymax)]],
+ zoom = 12,
+ zoomFraction = 0,
+ zoomFactor = 1, // Math.pow(2, zoomFraction)
+ zoomRange = [1, 18],
+ angle = 0,
+ angleCos = 1, // Math.cos(angle)
+ angleSin = 0, // Math.sin(angle)
+ angleCosi = 1, // Math.cos(-angle)
+ angleSini = 0; // Math.sin(-angle)
+
+ view.locationCoordinate = function(l) {
+ var c = _view.locationCoordinate(l),
+ k = Math.pow(2, zoom);
+ return [c[0] * k, c[1] * k, c[2] + zoom];
+ };
+
+ view.coordinateLocation = _view.coordinateLocation;
+
+ view.coordinatePoint = function(tileCenter, c) {
+ var kc = Math.pow(2, zoom - c[2]),
+ kt = Math.pow(2, zoom - tileCenter[2]),
+ dx = (c[0] * kc - tileCenter[0] * kt) * tileSize[0] * zoomFactor,
+ dy = (c[1] * kc - tileCenter[1] * kt) * tileSize[1] * zoomFactor;
+ return [
+ size[0] / 2 + angleCos * dx - angleSin * dy,
+ size[1] / 2 + angleSin * dx + angleCos * dy
+ ];
+ };
+
+ view.pointCoordinate = function(tileCenter, p) {
+ var kt = Math.pow(2, zoom - tileCenter[2]),
+ dx = (p[0] - size[0] / 2) / zoomFactor,
+ dy = (p[1] - size[1] / 2) / zoomFactor;
+ return [
+ tileCenter[0] * kt + (angleCosi * dx - angleSini * dy) / tileSize[0],
+ tileCenter[1] * kt + (angleSini * dx + angleCosi * dy) / tileSize[1],
+ zoom
+ ];
+ };
+
+ view.locationPoint = function(l) {
+ var k = Math.pow(2, zoom + zoomFraction - 3) / 45,
+ dx = (l[0] - center[0]) * k * tileSize[0],
+ dy = (lat2y(center[1]) - lat2y(l[1])) * k * tileSize[1];
+ return [
+ size[0] / 2 + angleCos * dx - angleSin * dy,
+ size[1] / 2 + angleSin * dx + angleCos * dy
+ ];
+ };
+
+ view.pointLocation = function(p) {
+ var k = 45 / Math.pow(2, zoom + zoomFraction - 3),
+ dx = (p[0] - size[0] / 2) * k,
+ dy = (p[1] - size[1] / 2) * k;
+ return [
+ center[0] + (angleCosi * dx - angleSini * dy) / tileSize[0],
+ y2lat(lat2y(center[1]) - (angleSini * dx + angleCosi * dy) / tileSize[1])
+ ];
+ };
+
+ function rezoom() {
+ if (zoomRange) {
+ if (zoom < zoomRange[0]) zoom = zoomRange[0];
+ else if (zoom > zoomRange[1]) zoom = zoomRange[1];
+ }
+ zoomFraction = zoom - (zoom = Math.round(zoom));
+ zoomFactor = Math.pow(2, zoomFraction);
+ }
+
+ function recenter() {
+ if (!centerRange) return;
+ var k = 45 / Math.pow(2, zoom + zoomFraction - 3);
+
+ // constrain latitude
+ var y = Math.max(Math.abs(angleSin * size[0] / 2 + angleCos * size[1] / 2),
+ Math.abs(angleSini * size[0] / 2 + angleCosi * size[1] / 2)),
+ lat0 = y2lat(ymin - y * k / tileSize[1]),
+ lat1 = y2lat(ymax + y * k / tileSize[1]);
+ center[1] = Math.max(lat0, Math.min(lat1, center[1]));
+
+ // constrain longitude
+ var x = Math.max(Math.abs(angleSin * size[1] / 2 + angleCos * size[0] / 2),
+ Math.abs(angleSini * size[1] / 2 + angleCosi * size[0] / 2)),
+ lon0 = centerRange[0][0] - x * k / tileSize[0],
+ lon1 = centerRange[1][0] + x * k / tileSize[0];
+ center[0] = Math.max(lon0, Math.min(lon1, center[0]));
+ }
+
+ view.size = function(x) {
+ if (!arguments.length) return size;
+ size = x;
+ recenter();
+ return view;
+ };
+
+ view.tileSize = function(x) {
+ if (!arguments.length) return tileSize;
+ tileSize = x;
+ recenter();
+ return view;
+ };
+
+ view.center = function(x) {
+ if (!arguments.length) return center;
+ center = x;
+ recenter();
+ return view;
+ };
+
+ view.panBy = function(x) {
+ var k = 45 / Math.pow(2, zoom + zoomFraction - 3),
+ dx = x[0] * k,
+ dy = x[1] * k;
+ return view.center([
+ center[0] + (angleSini * dy - angleCosi * dx) / tileSize[0],
+ y2lat(lat2y(center[1]) + (angleSini * dx + angleCosi * dy) / tileSize[1])
+ ]);
+ };
+
+ view.centerRange = function(x) {
+ if (!arguments.length) return centerRange;
+ centerRange = x;
+ if (centerRange) {
+ ymin = centerRange[0][1] > -90 ? lat2y(centerRange[0][1]) : -Infinity;
+ ymax = centerRange[0][1] < 90 ? lat2y(centerRange[1][1]) : Infinity;
+ } else {
+ ymin = -Infinity;
+ ymax = Infinity;
+ }
+ recenter();
+ return view;
+ };
+
+ view.zoom = function(x) {
+ if (!arguments.length) return zoom + zoomFraction;
+ zoom = x;
+ rezoom();
+ return view.center(center);
+ };
+
+ view.zoomBy = function(z, x0, l) {
+ if (arguments.length < 2) return view.zoom(zoom + zoomFraction + z);
+
+ // compute the location of x0
+ if (arguments.length < 3) l = view.pointLocation(x0);
+
+ // update the zoom level
+ zoom = zoom + zoomFraction + z;
+ rezoom();
+
+ // compute the new point of the location
+ var x1 = view.locationPoint(l);
+ return view.panBy([x0[0] - x1[0], x0[1] - x1[1]]);
+ };
+
+ view.zoomRange = function(x) {
+ if (!arguments.length) return zoomRange;
+ zoomRange = x;
+ return view.zoom(zoom + zoomFraction);
+ };
+
+ view.extent = function(x) {
+ if (!arguments.length) return [
+ view.pointLocation([0, size[1]]),
+ view.pointLocation([size[0], 0])
+ ];
+
+ // compute the extent in points, scale factor, and center
+ var bl = view.locationPoint(x[0]),
+ tr = view.locationPoint(x[1]),
+ k = Math.max((tr[0] - bl[0]) / size[0], (bl[1] - tr[1]) / size[1]),
+ l = view.pointLocation([(bl[0] + tr[0]) / 2, (bl[1] + tr[1]) / 2]);
+
+ // update the zoom level
+ zoom = zoom + zoomFraction - Math.log(k) / Math.LN2;
+ rezoom();
+
+ // set the new center
+ return view.center(l);
+ };
+
+ view.angle = function(x) {
+ if (!arguments.length) return angle;
+ angle = x;
+ angleCos = Math.cos(angle);
+ angleSin = Math.sin(angle);
+ angleCosi = Math.cos(-angle);
+ angleSini = Math.sin(-angle);
+ recenter();
+ return view;
+ };
+
+ return view;
+};
+
+function y2lat(y) {
+ return 360 / Math.PI * Math.atan(Math.exp(y * Math.PI / 180)) - 90;
+}
+
+function lat2y(lat) {
+ return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360));
+}
+
+_view.locationCoordinate = function(l) {
+ var k = 1 / 360;
+ return [
+ (l[0] + 180) * k,
+ (180 - lat2y(l[1])) * k,
+ 0
+ ];
+};
+
+_view.coordinateLocation = function(c) {
+ var k = 45 / Math.pow(2, c[2] - 3);
+ return [
+ k * c[0] - 180,
+ y2lat(180 - k * c[1])
+ ];
+};
10 package.json
View
@@ -0,0 +1,10 @@
+{
+ "name": "mappy",
+ "description": "Map rendering backed by Cairo",
+ "version": "0.0.1",
+ "author": "Mike Bostock <mbostock@gmail.com>",
+ "keywords": ["maps", "mapping", "canvas", "cairo"],
+ "repository": "git://github.com/mbostock/node-mappy",
+ "engines": { "node": "0.4.x" },
+ "main": "./lib/mappy.js"
+}
29 test/test-map.js
View
@@ -0,0 +1,29 @@
+var fs = require("fs"),
+ mappy = require("../"),
+ Canvas = require("../../node-canvas/lib/canvas"),
+ Image = Canvas.Image;
+
+var w = 1280,
+ h = 720;
+
+var view = mappy.view()
+ .size([w, h]);
+
+var image = mappy.image()
+ .view(view)
+ .url(mappy.url("http://{S}tile.cloudmade.com"
+ + "/1a1b06b230af4efdbb989ea99e9841af" // http://cloudmade.com/register
+ + "/998/256/{Z}/{X}/{Y}.png")
+ .hosts(["a.", "b.", "c.", ""]));
+
+var canvas = new Canvas(w, h),
+ context = canvas.getContext("2d");
+
+image.render(context, done);
+
+function done() {
+ var out = fs.createWriteStream(__dirname + "/test.png")
+ canvas.createPNGStream().on("data", function(chunk) {
+ out.write(chunk);
+ });
+}
Please sign in to comment.
Something went wrong with that request. Please try again.