Skip to content

Instantly share code, notes, and snippets.

@jhnklly
Last active August 29, 2015 14:26
Show Gist options
  • Save jhnklly/0e9aa2d68a971716e49e to your computer and use it in GitHub Desktop.
Save jhnklly/0e9aa2d68a971716e49e to your computer and use it in GitHub Desktop.
albers mapsense.js
svg.mapsense-map {
width: 100%;
height: 100%;
}
.mapsense-attribution {
background-color: rgba(0, 0, 0, 0.5);
color: #ccc;
position: absolute;
bottom: 0;
right: 0;
padding: 10px;
font-size: 11px;
font-family: sans-serif;
line-height: 5px;
text-decoration: none;
user-select: none;
cursor: default;
}
.mapsense-attribution a {
text-decoration: none;
color: #ccc;
}
path {
vector-effect: non-scaling-stroke;
stroke-linejoin: round;
stroke-linecap: round;
}
/* general rules */
path._undefined.none {
fill: none;
}
path.landuse.other {
fill: none;
stroke: none;
}
.park {
opacity: 1;
}
.water_line {
opacity: .5;
fill: none;
}
.runway {
opacity: .7;
}
.rail_major,
.rail_minor {
stroke-dasharray: 4, 3;
}
.roads,
.admin {
fill: none;
}
.disputed_border,
.state_border {
stroke-dasharray: 8, 3;
}
/* admins */
.country_border {
stroke-width: 2;
}
/* zoom-dependent styling for roads & admins */
.admin._3,
.admin._2,
.admin._1 {
stroke-width: 1;
}
.admin._4 {
stroke-width: 1.5;
}
.state_border._2 {
stroke-width: .3;
}
.state_border._3,
.state_border._4 {
stroke-width: .5;
}
.state_border._8,
.state_border._9,
.state_border._10,
.state_border._11 {
stroke-width: 1.5;
opacity: .75;
}
.state_border._12,
.state_border._13,
.state_border._14,
.state_border._15,
.state_border._16,
.state_border._17 {
stroke-width: 2;
stroke-dasharray: 10, 4;
opacity: .75;
}
/* roads */
.ne_10m_roads._3 {
stroke-width: 0.5;
}
.ne_10m_roads._4,
.ne_10m_roads._5 {
stroke-width: 0.75;
}
.ne_10m_roads._6 {
opacity: 0.75;
}
.motorway._6 {
stroke-width: .75;
}
.motorway._7,
.ne_10m_roads._7 {
stroke-width: 1;
}
.arterial_major._7 {
stroke-width: .5;
}
/* zoom 8 */
.ne_10m_roads._8 {
stroke-width: 1.25;
opacity: 0.75;
}
.motorway._8 {
stroke-width: 1.25;
opacity: 0.75;
}
.arterial_major._8 {
stroke-width: .5;
opacity: .75;
}
.arterial_minor._8 {
stroke-width: 0.5;
opacity: .5;
}
/* zoom 9 */
.motorway._9 {
stroke-width: 1.5;
}
.arterial_major._9 {
stroke-width: .75;
}
.arterial_minor._9 {
stroke-width: 0.5;
opacity: .5;
}
/* zoom 10 */
.motorway._10 {
stroke-width: 1.75;
}
.arterial_major._10 {
stroke-width: 1.25;
}
.arterial_minor._10 {
stroke-width: 1;
}
.road_med._10 {
stroke-width: .75;
}
/* zoom 11 */
.motorway._11 {
stroke-width: 3;
}
.arterial_major._11,
.runway._11 {
stroke-width: 1.75;
}
.arterial_minor._11 {
stroke-width: 1.25;
}
.road_med._11 {
stroke-width: 1;
}
.road_minor._11 {
stroke-width: .5;
}
/* zoom 12 */
.motorway._12 {
stroke-width: 3.5;
}
.arterial_major._12,
.runway._12 {
stroke-width: 2;
}
.arterial_minor._12 {
stroke-width: 1.5;
}
.road_med._12 {
stroke-width: 1;
}
.road_minor._12 {
stroke-width: .75;
}
/* zoom 13 */
.motorway._13 {
stroke-width: 5;
}
.arterial_major._13,
.runway._13 {
stroke-width: 3.5;
}
.arterial_minor._13 {
stroke-width: 2.5;
}
.road_med._13 {
stroke-width: 2;
}
.road_minor._13 {
stroke-width: 1.5;
}
.path._13,
.water_line._13 {
stroke-width: 0.3;
}
/* zoom 14 */
.motorway._14 {
stroke-width: 6.5;
}
.arterial_major._14,
.runway._14 {
stroke-width: 4.75;
}
.arterial_minor._14 {
stroke-width: 4.25;
}
.road_med._14 {
stroke-width: 3.75;
}
.road_minor._14 {
stroke-width: 2.5;
}
.rail_minor._14 {
stroke-width: 1;
}
.rail_major._14 {
stroke-width: 1.5;
}
.path._14,
.water_line._14 {
stroke-width: 0.75;
}
/* zoom 15 */
.motorway._15 {
stroke-width: 8.5;
}
.arterial_major._15,
.runway._15 {
stroke-width: 7;
}
.arterial_minor._15 {
stroke-width: 6;
}
.road_med._15 {
stroke-width: 5;
}
.road_minor._15 {
stroke-width: 4;
}
.rail_minor._15 {
stroke-width: 1.5;
}
.rail_major._15 {
stroke-width: 1.5;
}
.path._15,
.water_line._15 {
stroke-width: 1;
}
/* zoom 16 */
.motorway._16 {
stroke-width: 12;
}
.arterial_major._16,
.runway._16 {
stroke-width: 9;
}
.arterial_minor._16 {
stroke-width: 8;
}
.road_med._16 {
stroke-width: 7;
}
.road_minor._16 {
stroke-width: 6;
}
.rail_minor._16 {
stroke-width: 1.5;
}
.rail_major._16 {
stroke-width: 2;
}
.path._16,
.water_line._16 {
stroke-width: 2;
}
/* zoom 17 */
.motorway._17 {
stroke-width: 15;
}
.arterial_major._17,
.runway._17 {
stroke-width: 10;
}
.arterial_minor._17 {
stroke-width: 9;
}
.road_med._17 {
stroke-width: 8;
}
.road_minor._17 {
stroke-width: 8;
}
.rail_minor._17 {
stroke-width: 3;
stroke-dasharray: 7, 6;
}
.rail_major._17 {
stroke-width: 4;
stroke-dasharray: 7, 6;
}
.path._17,
.water_line._17 {
stroke-width: 5;
}
/* zoom 18 */
.motorway._18 {
stroke-width: 16;
}
.arterial_major._18,
.runway._18 {
stroke-width: 10;
}
.arterial_minor._18 {
stroke-width: 10;
}
.road_med._18 {
stroke-width: 9;
}
.road_minor._18 {
stroke-width: 8;
}
.rail_minor._18 {
stroke-width: 3;
stroke-dasharray: 7, 6;
}
.rail_major._18 {
stroke-width: 4;
stroke-dasharray: 7, 6;
}
.path._18,
.water_line._18 {
stroke-width: 5;
}
/*
Style-dependent Style!
*/
.mapnat.labels {
font-size: 14;
fill: #777;
font-weight: 600;
text-transform: uppercase;
stroke-width: .3;
/*stroke: grey;*/
font-stretch: expanded;
letter-spacing: 1.5;
font-family: "Josefin Sans";
}
.mapnat.tile-background {
fill: rgba(203, 230, 243,1); /*blue*/
}
.mapnat.land {
fill: #e6e6e6;
stroke: rgba(68, 167, 228, 0.5); /* stroke blue*/
stroke-width: 1;
}
.mapnat.water_polygon,
.mapnat.water {
fill: rgba(203, 230, 243,1); /*blue*/
stroke: rgba(68, 167, 228, 0.5); /* stroke blue*/
stroke-width: 1;
}
.mapnat.country_border,
.mapnat.disputed_border {
stroke: #aaa;
}
.mapnat.state_border {
stroke: #aaa;
}
.mapnat.water_line {
stroke: #c8d0df;
}
.mapnat.landuse.park {
/*fill: rgba(200,250,200,0.5);
*/
fill: rgb(138,200,111);
fill-opacity: 0.1;
stroke: none;
}
.mapnat.park._0,
.mapnat.park._1,
.mapnat.park._2,
.mapnat.park._3,
.mapnat.park._4,
.mapnat.park._5,
.mapnat.park._6 {
fill: none;
stroke: none;
}
.mapnat.building {
fill: #d7d6d1;
}
.mapnat.school {
fill: #eadddb;
stroke: none;
}
.mapnat.other {
fill: none;
stroke: none;
}
.mapnat.urban {
fill: rgba(177, 135, 74, 0.07);
stroke: none;
}
.mapnat.ne_10m_roads {
stroke: #ededed;
}
.mapnat.motorway {
stroke: #f6f4f3;
}
.mapnat.arterial_major {
stroke: #f4f4ef;
}
.mapnat.arterial_minor {
stroke: #f1eded;
}
.mapnat.road_med {
stroke: #f6f3f3;
}
.mapnat.road_minor {
stroke: #faf8f8;
}
.mapnat.rail_major {
stroke: #cccccc;
}
.mapnat.rail_minor {
stroke: #cccccc;
}
.mapnat.runway {
stroke: #ccc;
}
.mapnat.path {
stroke: #ccc;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://d3js.org/topojson.v1.min.js" charset="utf-8"></script>
<script src="https://developer.mapsense.co/mapsense.js" charset="utf-8"></script>
<link type="text/css" href="https://developer.mapsense.co/mapsense.css" rel="stylesheet"/>
<link rel="stylesheet" href="basemap-natural.css" media="screen" type="text/css">
<style>
html, body, #myMap{
height: 100%;
width: 100%;
margin: 0; padding: 0;
font-family: sans-serif;
overflow: hidden;
}
#myMap {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
}
path {
vector-effect: non-scaling-stroke;
stroke-linejoin: round;
stroke-linecap: round;
stroke: #777;
fill: none;
}
circle {
fill: rgba(68, 167, 228, 0.5);
stroke: none;
}
.top-left {
position: absolute;
top: 5px;
left: 5px;
}
.btn {
background-color: rgba(255,255,255,0.8);
outline: 1px solid rgba(0,0,0,0.3);
min-width: 60px;
padding: 2px 4px;
margin: 2px;
display: inline-block;
}
.clipper {
stroke: blue;
}
g { clip-path: none; }
</style>
</head>
<body>
<div id="myMap"></div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.6/proj4-src.js' type="text/javascript"></script>
<script src='http://d3js.org/queue.v1.min.js' type="text/javascript"></script>
<script src='sphericalmercator.js' type="text/javascript"></script>
<script>
var width = 960,
height = 600;
var dz = 2,
z = 5 + dz,
x = d3.range(4 << dz, 11 << dz),
y = d3.range(10 << dz, 15 << dz);
var projection = d3.geo.albers()
//.scale(1250)
//.translate([width / 2, height / 2]);
var xy = projection([-122,33]);
var sph = new SphericalMercator();
/*var svg = d3.select("body").append("svg")
//.attr("id", "myMap")
.attr("width", width)
.attr("height", height);
*/
var projpath = d3.geo.path()
.projection(projection);
//.context(context);
var mercator = proj4('+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 '
+ '+x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext '
+ '+over +no_defs'),
// Albers Equal Area
albers = proj4('+proj=aea +lat_1=29.5 +lat_2=45.5 +lat_0=37.5 +lon_0=-96 '
+ '+x_0=0 +y_0=0 +ellps=sphere +nadgrids=@null '
+ '+towgs84=0,0,0,0,0,0,0 +units=m +no_defs');
function WGS84ToMap(pt) {
pt = mercator.inverse(albers.forward([pt.lon, pt.lat]));
return { lon: pt[0], lat: pt[1] };
}
function mapToWGS84(pt) {
pt = albers.inverse(mercator.forward([pt.lon, pt.lat]));
return { lon: pt[0], lat: pt[1] };
}
var q = queue(6);
/*
x.forEach(function(x) {
y.forEach(function(y) {
q.defer(renderTile, x, y, z);
});
});
*/
var the_key = "key-2d5eacd8b924489c8ed5e8418bd883bc";
var demographics_url = "https://{S}-api.mapsense.co/universes/mapsense.demographics/{Z}/{X}/{Y}.topojson?api-key=" + the_key;
demographics_url += "&where=name!='Puerto Rico'";
demographics_url += "&select=name,population";
demographics_url += "&ringSpan=15";
demographics_url += "&lineSpan=15";
demographics_url += "&s=20";
var map = mapsense.map("#myMap") //tell it where to go
.center({lon: -120, lat: 40.808}) //set a center
.zoom(5)
;
map.tileSize({x: 256,y: 256});
var layer = mapsense.topoJson() //init a topojson layer
.url(mapsense.url(demographics_url).hosts(['a', 'b', 'c', 'd'])) //tell it where to look
.clip(true)
.selection(
function(d){
/*
var projection = d3.geo.albers()
.center([0, 0])
.rotate([96, 0])
.parallels([29.5, 45.5])
.scale(1000)
.translate([0,0])
.precision(0.0001);
// align projection
var tr = projection([bounds[0].lon, bounds[0].lat]),
bl = projection([bounds[1].lon, bounds[1].lat]),
tr2 = project(cbounds[0]),
bl2 = project(cbounds[1]);
var scale_x = (tr2[0] - bl2[0]) / (tr[0] - bl[0]),
scale_y = (tr2[1] - bl2[1]) / (tr[1] - bl[1]);
projection.scale(1000*scale_x);
c0 = projection(pcenter);
c1 = project(pcenter2);
projection.translate([c1[0]-c0[0], c1[1]-c0[1]]);
c0 = projection(pcenter);
c1 = project(pcenter2);
path = d3.geo.path()
.projection(projection);
*/
d.attr("class", "mapFeatures") //use a d3 selection to class each feature
.attr("d", projpath)
//.attr("style","transform: translate(0, -0); -webkit-transform: translate(0, -0);")
//.attr("d", path)
;
d3.selectAll('g').attr('style','clip-path: none; transform: translate(0, -0); -webkit-transform: translate(0, -0);');
}
);
map.add(layer); //add the topojson layer to your map
map.interact(true);
map.add(mapsense.drag());
map.add(mapsense.wheel());
map.add(mapsense.dblclick());
map.add(mapsense.touch());
//map.add(mapsense.hash());
//d3.selectAll('#org\\.polymaps\\.1').remove()
//d3.selectAll('g').attr('clip-path','')
//d3.selectAll('g').attr('style','clip-path: none; transform: translate(0, -0); -webkit-transform: translate(0, -0);');
function renderTile(x, y, z, callback) {
//d3.json("http://www.somebits.com:8001/rivers/" + z + "/" + x + "/" + y + ".json", function(error, json) {
d3.json("https://b-api.mapsense.co/universes/mapsense.demographics/" + z + "/" + x + "/" + y +".topojson?api-key=key-2d5eacd8b924489c8ed5e8418bd883bc&where=layer=='state'&s=15", function(error, tj) {
//var xy = projection([-122,33]);
bbox = sph.bbox(x,y,z);
bbox_json = {"type": "FeatureCollection","features": [{"type": "Feature","properties": {},"geometry": {"type": "Polygon","coordinates": [[[bbox[0],bbox[1]],[bbox[0],bbox[3]],[bbox[2],bbox[3]],[bbox[2],bbox[1]],[bbox[0],bbox[1]]]]}}]};
//console.log(JSON.stringify(bbox_json));
var tile_coords = "tile_" + x + "_" + y;
svg.insert("clipPath")
.data(bbox_json.features)
.attr('id',tile_coords)
.attr('class','clipper')
.append('path')
.attr('d',projpath) // run projpath function on the attached data element
;
svg.insert("path")
//.datum(topojson.object(world, world.objects.land))
.datum(function(d){
if ( tj.objects.state ) {
return topojson.feature(tj, tj.objects.state);
}
})
.attr("class", "land")
.attr("d", projpath) // path is a the albers projection function above; it projects the path assigned via datum
.attr("clip-path", "url(#" + tile_coords + ")")
;
callback(null);
});
}
function topologyFeatures(topology) {
function convert(topology, object, layer, features) {
var featureOrCollection = topojson.feature(topology, object),
layerFeatures;
if (featureOrCollection.type === "FeatureCollection") {
layerFeatures = featureOrCollection.features;
} else {
layerFeatures = [featureOrCollection];
}
layerFeatures.forEach(function(f) {
f.properties.layer = layer;
});
features.push.apply(features, layerFeatures);
}
var features = [];
for (var o in topology.objects) {
convert(topology, topology.objects[o], o, features);
}
return features;
}
function topo2Geo(tj) {
var gj = {
type: "FeatureCollection",
features: topologyFeatures(tj)
};
return gj;
}
</script>
</body>
</html>
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment