Skip to content

Instantly share code, notes, and snippets.

@wboykinm
Forked from summer4096/fancyCanvas.js
Last active August 29, 2015 14:10
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save wboykinm/fb20b2a9a52092c9abba to your computer and use it in GitHub Desktop.
L.tileLayer.fancyCanvas = function(url){
var layer = L.tileLayer.canvas({async: true});
layer.setUrl(url);
var dataSource = function(x, y, z, done){
var url = layer.getTileUrl({x: x, y: y, z: z});
d3.xhr(url).responseType('arraybuffer').get(done);
};
layer.data = function(fn){
dataSource = fn;
return layer;
};
layer.projections = {};
layer.projections.WGS84 = function(offset){
offset = offset || {x: 0, y: 0};
return d3.geo.transform({
point: function(y, x) {
var point = layer._map.latLngToLayerPoint(new L.LatLng(x, y));
this.stream.point(point.x-tileOffset.x, point.y-tileOffset.y);
}
});
};
layer.modes = {};
layer.modes.geojson = {
extensions: ['geojson', 'json'],
get: function(url, callback){
d3.json(url, callback);
},
parse: function(data, canvas){
var tileOffset = {
x: parseInt(d3.select(canvas).style('left').slice(0, -2)),
y: parseInt(d3.select(canvas).style('top').slice(0, -2))
};
return {
data: {layer: data},
projection: layer.projections.WGS84(tileOffset)
}
}
};
layer.modes.topojson = {
extensions: ['topojson'],
get: function(url, callback){
d3.json(url, callback);
},
parse: function(data, canvas){
var tileOffset = {
x: parseInt(d3.select(canvas).style('left').slice(0, -2)),
y: parseInt(d3.select(canvas).style('top').slice(0, -2))
};
var layers = {};
for (var key in data.objects) {
layers[key] = topojson.feature(data, data.objects[key]);
}
return {
data: layers,
projection: layer.projections.WGS84(tileOffset)
}
}
};
layer.modes.protobuf = {
extensions: ['mvt', 'pbf'],
get: function(url, callback){
d3.xhr(url).responseType('arraybuffer').get(callback);
},
parse: function(data, canvas){
var tile = new vectorTile.VectorTile( new pbf( new Uint8Array(data) ) );
var layers = {};
for (var key in tile.layers) {
layers[key] = tile.layers[key].toGeoJSON();
}
//console.log(layers);
return {
data: layers,
projection: d3.geo.transform({
point: function(x, y) {
x = x/tile.layers[layer.__currentLayer].extent*canvas.width*(1/window.devicePixelRatio);
y = y/tile.layers[layer.__currentLayer].extent*canvas.height*(1/window.devicePixelRatio);
this.stream.point(x, y);
}
})
};
}
};
var modeOption = 'auto';
layer.mode = function(_mode){
modeOption = _mode;
return layer;
};
var renderers = [];
layer.render = function(layerName, fn){
renderers.push({
layer: layerName,
run: fn
});
return layer;
};
layer.drawTile = function(canvas, tilePoint, zoom) {
var context = canvas.getContext('2d');
tilePoint = {x: tilePoint.x, y: tilePoint.y, z: zoom};
var mode;
if (modeOption == 'auto') {
var extension = layer._url.split('?')[0].split('.').pop();
for (var key in layer.modes) {
if (layer.modes[key].extensions.indexOf(extension) != -1) {
mode = layer.modes[key];
break;
}
}
if (!mode) {
throw new Error('I don\'t know what to do with URLs ending in .'+extension);
}
} else {
mode = layer.modes[modeOption];
}
var url = layer.getTileUrl(tilePoint);
mode.get(url, function(err, xhr){
if (err) {
throw err;
}
var result = mode.parse(xhr.response, canvas);
var path = d3.geo.path()
.projection(result.projection)
.context(context);
if (renderers.length) {
renderers.forEach(function(renderer){
if (!result.data[renderer.layer]) return;
layer.__currentLayer = renderer.layer;
result.data[renderer.layer].features.forEach(function(feature){
context.beginPath();
path(feature);
renderer.run(context, feature, tilePoint);
});
});
} else {
throw new Error('No renderer specified!');
}
layer.tileDrawn(canvas);
});
};
return layer;
};
<!DOCTYPE html>
<html>
<head>
<title>Canvas Layer!</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css">
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<div id="map"></div>
<script src="https://cdn.rawgit.com/jondavidjohn/hidpi-canvas-polyfill/master/dist/hidpi-canvas.js"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src="http://d3js.org/d3.v3.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://wzrd.in/standalone/pbf@latest"></script>
<script src="vectortile.js"></script>
<script src="fancyCanvas.js"></script>
<script src="layer.js"></script>
</body>
</html>
var map = L.map('map', {
center: [38.18543, -109.89386],
zoom: 12,
zoomControl: false
});
//L.tileLayer('http://{s}.tile.stamen.com/terrain-background/{z}/{x}/{y}.jpg').addTo(map);
//var url = 'http://{s}.tile.openstreetmap.us/vectiles-highroad/{z}/{x}/{y}.topojson';
var url = 'https://{s}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6-dev,mapbox.mapbox-terrain-v1/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6IlhHVkZmaW8ifQ.hAMX5hSW-QnTeRCMAy9A8Q';
var roadSizes = {
"highway": 5,
"major_road": 3,
"minor_road": 1,
"rail": 0,
"path": 0.5
};
var colors = {
land: '#FCFBE7',
water: '#368ed9',
grass: '#E6F2C1',
beach: '#FFEEC7',
park: '#DAF2C1',
cemetery: '#D6DED2',
wooded: '#C3D9AD',
agriculture: '#F2E8B6',
building: '#E4E0E0',
hospital: 'rgb(229,198,195)',
school: '#FFF5CC',
sports: '#B8E6B8',
residential: '#FCFBE7',
commercial: '#FCFBE7',
industrial: '#FCFBE7',
parking: '#EEE',
big_road: '#d28585',
little_road: '#bbb'
};
var landColors = {
cemetery: colors.cemetery,
college: colors.school,
commercial: colors.industrial,
common: colors.park,
forest: colors.wooded,
golf_course: colors.sports,
grass: colors.grass,
hospital: colors.hospital,
industrial: colors.industrial,
park: colors.park,
parking: colors.parking,
pedestrian: colors.pedestrian_fill,
pitch: colors.sports,
residential: colors.residential,
school: colors.school,
sports_center: colors.sports,
stadium: colors.sports,
university: colors.school,
wood: colors.wooded
};
L.tileLayer.fancyCanvas(url)
.render('landuse', function(context, d, tile){
if (tile.z > 12 && landColors[d.properties.class]) {
context.fillStyle = landColors[d.properties.class];
context.fill();
}
})
.render('hillshade', function(context, d){
var parts = d.properties.class.split('_');
var amount = parts[0];
var type = parts[1];
var shadow = '100, 50, 150';
var highlight = '255, 255, 150';
var alpha = 1;
if (amount == 'medium') {
alpha = 0.2;
} else if (amount == 'full') {
alpha = 0.3;
}
context.fillStyle = 'rgba('+(type == 'shadow' ? shadow : highlight)+', '+alpha+')';
context.fill();
})
.render('contour', function(context, d){
context.strokeStyle = 'rgba(0,0,0,0.2)';
context.lineWidth = 0.5;
context.stroke();
})
.render('road', function(context, d, tile){
var big = (d.properties.type == 'motorway' || d.properties.type == 'trunk');
context.strokeStyle = big ? colors.big_road : colors.little_road;
context.lineWidth = 1;
if (tile.z < 8) {
if (big) context.stroke();
} else {
context.stroke();
}
})
.render('building', function(context, d){
context.fillStyle = '#666';
context.fill();
})
.render('water', function(context, d){
context.fillStyle = colors.water;
context.fill();
})
.render('waterway', function(context, d){
context.strokeStyle = colors.water;
context.lineWidth = 1;
context.stroke();
})
.addTo(map);
html, body {
height: 100%;
padding: 0;
margin: 0;
}
#map {
width: 100%;
height: 100%;
background: #bcd9bf;
}
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.vectorTile=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
var VectorTileLayer = require('./vectortilelayer');
module.exports = VectorTile;
function VectorTile(buffer, end) {
this.layers = {};
this._buffer = buffer;
end = end || buffer.length;
while (buffer.pos < end) {
var val = buffer.readVarint(),
tag = val >> 3;
if (tag == 3) {
var layer = this.readLayer();
if (layer.length) this.layers[layer.name] = layer;
} else {
buffer.skip(val);
}
}
}
VectorTile.prototype.readLayer = function() {
var buffer = this._buffer,
bytes = buffer.readVarint(),
end = buffer.pos + bytes,
layer = new VectorTileLayer(buffer, end);
buffer.pos = end;
return layer;
};
// Returns a dictionary of layers as individual GeoJSON feature collections, keyed by layer name
VectorTile.prototype.toGeoJSON = function () {
var json = {};
var layerNames = Object.keys(this.layers);
for (var n=0; n < layerNames.length; n++) {
json[layerNames[n]] = this.layers[layerNames[n]].toGeoJSON();
}
return json;
};
},{"./vectortilelayer":3}],2:[function(require,module,exports){
'use strict';
var Point = require('point-geometry');
module.exports = VectorTileFeature;
function VectorTileFeature(buffer, end, extent, keys, values) {
this.properties = {};
// Public
this.extent = extent;
this.type = 0;
// Private
this._buffer = buffer;
this._geometry = -1;
this._keys = keys;
end = end || buffer.length;
while (buffer.pos < end) {
var val = buffer.readVarint(),
tag = val >> 3;
if (tag == 1) {
this._id = buffer.readVarint();
} else if (tag == 2) {
var tagEnd = buffer.pos + buffer.readVarint();
while (buffer.pos < tagEnd) {
var key = keys[buffer.readVarint()];
var value = values[buffer.readVarint()];
this.properties[key] = value;
}
} else if (tag == 3) {
this.type = buffer.readVarint();
} else if (tag == 4) {
this._geometry = buffer.pos;
buffer.skip(val);
} else {
buffer.skip(val);
}
}
}
VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
VectorTileFeature.prototype.loadGeometry = function() {
var buffer = this._buffer;
buffer.pos = this._geometry;
var bytes = buffer.readVarint(),
end = buffer.pos + bytes,
cmd = 1,
length = 0,
x = 0,
y = 0,
lines = [],
line;
while (buffer.pos < end) {
if (!length) {
var cmd_length = buffer.readVarint();
cmd = cmd_length & 0x7;
length = cmd_length >> 3;
}
length--;
if (cmd === 1 || cmd === 2) {
x += buffer.readSVarint();
y += buffer.readSVarint();
if (cmd === 1) {
// moveTo
if (line) {
lines.push(line);
}
line = [];
}
line.push(new Point(x, y));
} else if (cmd === 7) {
// closePolygon
line.push(line[0].clone());
} else {
throw new Error('unknown command ' + cmd);
}
}
if (line) lines.push(line);
return lines;
};
VectorTileFeature.prototype.bbox = function() {
var buffer = this._buffer;
buffer.pos = this._geometry;
var bytes = buffer.readVarint(),
end = buffer.pos + bytes,
cmd = 1,
length = 0,
x = 0,
y = 0,
x1 = Infinity,
x2 = -Infinity,
y1 = Infinity,
y2 = -Infinity;
while (buffer.pos < end) {
if (!length) {
var cmd_length = buffer.readVarint();
cmd = cmd_length & 0x7;
length = cmd_length >> 3;
}
length--;
if (cmd === 1 || cmd === 2) {
x += buffer.readSVarint();
y += buffer.readSVarint();
if (x < x1) x1 = x;
if (x > x2) x2 = x;
if (y < y1) y1 = y;
if (y > y2) y2 = y;
} else if (cmd !== 7) {
throw new Error('unknown command ' + cmd);
}
}
return [x1, y1, x2, y2];
};
VectorTileFeature.prototype.toGeoJSON = function () {
var geojson = {
type: 'Feature',
geometry: {},
properties: {}
};
for (var k=0; k < this._keys.length; k++) {
var key = this._keys[k];
geojson.properties[key] = this.properties[key];
}
geojson.geometry.coordinates = this.loadGeometry();
for (var r=0; r < geojson.geometry.coordinates.length; r++) {
var ring = geojson.geometry.coordinates[r];
for (var c=0; c < ring.length; c++) {
ring[c] = [
ring[c].x,
ring[c].y
];
}
}
if (VectorTileFeature.types[this.type] == 'Point') {
geojson.geometry.type = 'Point';
}
else if (VectorTileFeature.types[this.type] == 'LineString') {
if (geojson.geometry.coordinates.length == 1) {
geojson.geometry.coordinates = geojson.geometry.coordinates[0];
geojson.geometry.type = 'LineString';
}
else {
geojson.geometry.type = 'MultiLineString';
}
}
else if (VectorTileFeature.types[this.type] == 'Polygon') {
geojson.geometry.type = 'Polygon';
}
return geojson;
};
},{"point-geometry":4}],3:[function(require,module,exports){
'use strict';
var VectorTileFeature = require('./vectortilefeature.js');
module.exports = VectorTileLayer;
function VectorTileLayer(buffer, end) {
// Public
this.version = 1;
this.name = null;
this.extent = 4096;
this.length = 0;
// Private
this._buffer = buffer;
this._keys = [];
this._values = [];
this._features = [];
var val, tag;
end = end || buffer.length;
while (buffer.pos < end) {
val = buffer.readVarint();
tag = val >> 3;
if (tag === 15) {
this.version = buffer.readVarint();
} else if (tag === 1) {
this.name = buffer.readString();
} else if (tag === 5) {
this.extent = buffer.readVarint();
} else if (tag === 2) {
this.length++;
this._features.push(buffer.pos);
buffer.skip(val);
} else if (tag === 3) {
this._keys.push(buffer.readString());
} else if (tag === 4) {
this._values.push(this.readFeatureValue());
} else {
buffer.skip(val);
}
}
}
VectorTileLayer.prototype.readFeatureValue = function() {
var buffer = this._buffer,
value = null,
bytes = buffer.readVarint(),
end = buffer.pos + bytes,
val, tag;
while (buffer.pos < end) {
val = buffer.readVarint();
tag = val >> 3;
if (tag == 1) {
value = buffer.readString();
} else if (tag == 2) {
throw new Error('read float');
} else if (tag == 3) {
value = buffer.readDouble();
} else if (tag == 4) {
value = buffer.readVarint();
} else if (tag == 5) {
throw new Error('read uint');
} else if (tag == 6) {
value = buffer.readSVarint();
} else if (tag == 7) {
value = Boolean(buffer.readVarint());
} else {
buffer.skip(val);
}
}
return value;
};
// return feature `i` from this layer as a `VectorTileFeature`
VectorTileLayer.prototype.feature = function(i) {
if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds');
this._buffer.pos = this._features[i];
var end = this._buffer.readVarint() + this._buffer.pos;
return new VectorTileFeature(this._buffer, end, this.extent, this._keys, this._values);
};
VectorTileLayer.prototype.toGeoJSON = function () {
var geojson = {
type: 'FeatureCollection',
features: []
};
for (var f=0; f < this.length; f++) {
geojson.features.push(this.feature(f).toGeoJSON());
}
return geojson;
};
},{"./vectortilefeature.js":2}],4:[function(require,module,exports){
'use strict';
module.exports = Point;
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
clone: function() { return new Point(this.x, this.y); },
add: function(p) { return this.clone()._add(p); },
sub: function(p) { return this.clone()._sub(p); },
mult: function(k) { return this.clone()._mult(k); },
div: function(k) { return this.clone()._div(k); },
rotate: function(a) { return this.clone()._rotate(a); },
matMult: function(m) { return this.clone()._matMult(m); },
unit: function() { return this.clone()._unit(); },
perp: function() { return this.clone()._perp(); },
round: function() { return this.clone()._round(); },
mag: function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
},
equals: function(p) {
return this.x === p.x &&
this.y === p.y;
},
dist: function(p) {
return Math.sqrt(this.distSqr(p));
},
distSqr: function(p) {
var dx = p.x - this.x,
dy = p.y - this.y;
return dx * dx + dy * dy;
},
angle: function() {
return Math.atan2(this.y, this.x);
},
angleTo: function(b) {
return Math.atan2(this.y - b.y, this.x - b.x);
},
angleWith: function(b) {
return this.angleWithSep(b.x, b.y);
},
// Find the angle of the two vectors, solving the formula for the cross product a x b = |a||b|sin(θ) for θ.
angleWithSep: function(x, y) {
return Math.atan2(
this.x * y - this.y * x,
this.x * x + this.y * y);
},
_matMult: function(m) {
var x = m[0] * this.x + m[1] * this.y,
y = m[2] * this.x + m[3] * this.y;
this.x = x;
this.y = y;
return this;
},
_add: function(p) {
this.x += p.x;
this.y += p.y;
return this;
},
_sub: function(p) {
this.x -= p.x;
this.y -= p.y;
return this;
},
_mult: function(k) {
this.x *= k;
this.y *= k;
return this;
},
_div: function(k) {
this.x /= k;
this.y /= k;
return this;
},
_unit: function() {
this._div(this.mag());
return this;
},
_perp: function() {
var y = this.y;
this.y = this.x;
this.x = -y;
return this;
},
_rotate: function(angle) {
var cos = Math.cos(angle),
sin = Math.sin(angle),
x = cos * this.x - sin * this.y,
y = sin * this.x + cos * this.y;
this.x = x;
this.y = y;
return this;
},
_round: function() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
};
// constructs Point from an array if necessary
Point.convert = function (a) {
if (a instanceof Point) {
return a;
}
if (Array.isArray(a)) {
return new Point(a[0], a[1]);
}
return a;
};
},{}],5:[function(require,module,exports){
module.exports.VectorTile = require('./lib/vectortile.js');
module.exports.VectorTileFeature = require('./lib/vectortilefeature.js');
module.exports.VectorTileLayer = require('./lib/vectortilelayer.js');
},{"./lib/vectortile.js":1,"./lib/vectortilefeature.js":2,"./lib/vectortilelayer.js":3}]},{},[5])(5)
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment