Skip to content

Instantly share code, notes, and snippets.

@akbstone
Last active December 23, 2015 22:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save akbstone/6704124 to your computer and use it in GitHub Desktop.
Save akbstone/6704124 to your computer and use it in GitHub Desktop.
Openlayers2 + Proj4js + D3js.hexbin

Displays earthquake data for past 30 days. Hexes sized according to largest earthquake within. Opacity determined by most recent earthquake within.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/openlayers/2.12/OpenLayers.debug.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/proj4js/1.1.0/proj4js-compressed.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.hexbin.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="OpenLayers.D3.js"></script>
<script type="text/javascript">
load = function(){
var sourceProj = new OpenLayers.Projection("EPSG:4326");
var destProj;
var lae_extent = [-9036842.762,-9036842.762,9036842.762,9036842.762];
var projections = {
'EPSG:3857':{
label:'Web Mercator',
def:"+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 +no_defs",
//extent:[],
center:[-170,60],
zoom:3,
extent:[-20037508.34,-20037508.34,20037508.34,20037508.34],
wrapDateLine:true
},
'EPSG:3574':{
label:'Azimuthal Equal Area: Atlantic',
def:"+proj=laea +lat_0=90 +lon_0=-40 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
extent:lae_extent,
center:[-40,90],
zoom:1,
wrapDateLine:false
},
'EPSG:3572':{
label:'Azimuthal Equal Area: Alaska',
def:"+proj=laea +lat_0=90 +lon_0=-150 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs",
extent:lae_extent,
center:[-150,90],
selected:true,
zoom:1
},
'EPSG:3031':{
label:'Antarctic Stereographic',
def:"+proj=stere +lat_0=-90 +lon_0=0 +lat_ts=-71 +ellps=WGS84 +datum=WGS84 +units=m",
extent:[-12400000,-12400000, 12400000, 12400000],
center:[0,-90],
zoom:2,
wrapDateLine:false
}
};
var tools = $('<div>').attr('id','tools');
$('body').append(tools);
var projectionSwitcher = $('<select>').attr('id','projection_switcher');
for(var k in projections){
var p = projections[k];
var sel = p.selected === true ? ' selected="selected"' : '';
projectionSwitcher.append('<option value="' + k + '"'+ sel +'>' + p.label + '</option>');
}
projectionSwitcher.on('change',function(e){
setProjection(this.value);
});
tools.append(projectionSwitcher);
var map,baseLayer,features;
initMap = function(){
if(map){
map.destroy();
}
baseLayer = new OpenLayers.Layer.WMS(
"GINA WMS",//layer label
"http://wms.alaskamapped.org/bdl/",
{
layers: 'BestDataAvailableLayer', //layer wms name
},
{
animationEnabled:true,
isBaseLayer:true,
wrapDateLine:destProj.wrapDateLine,
transitionEffect: 'resize',
attribution: '<a href="http://www.gina.alaska.edu">GINA</a>'
}
);
var mapOps = {projection:destProj.olProjection};
mapOps.wrapDateLine = destProj.wrapDateLine;
if(destProj.extent){
mapOps.maxExtent = destProj.extent;
if(!mapOps.wrapDateLine){
mapOps.restrictedExtent = destProj.extent
}
}
map = new OpenLayers.Map('map',mapOps);
map.addLayer(baseLayer);
var d3Layer = new OpenLayers.Layer.D3Hex('d3Hexes',{
//wrapDateLine:true,
d3XAccessor:function(f){
return f.geometry.coordinates[0];
},
d3YAccessor:function(f){
return f.geometry.coordinates[1];
},
d3HexRadius:10,
d3HexOpacity:function(d){
console.log('getting op..')
return this.getTimeOp(d3.max(d,function(f){return f[2].properties.time}));
},
d3PointOpacity:function(f){
return this.getTimeOp(f.properties.time);
},
d3VisibleHexRadius:function(d){
return this.getMagRadius(d3.max(d,function(f){return f[2].properties.mag}));
},
d3PointRadius:function(f){
return this.getMagRadius(f.properties.mag)
},
d3PointStroke:"#999",
getMagRadius:function(mag){
var self = this;
var max = this.d3HexRadius - 1;
var min = 2;
var diff = max - min;
var s = min;
if(!isNaN(this.minMag) && !isNaN(this.maxMag)){
s = min + ((mag - this.minMag )/ (this.maxMag - this.minMag)) * diff;
}
return s;
},
getTimeOp:function(time){
var self = this;
var max = 1;
var min = .3;
var diff = max - min;
var s = min;
if(!isNaN(this.minTime) && !isNaN(this.maxTime)){
s = min + ((time - this.minTime )/ (this.maxTime - this.minTime)) * diff;
}
return s;
}
});
map.addLayer(d3Layer);
map.setCenter(new OpenLayers.LonLat(destProj.center).transform(sourceProj,destProj.olProjection),destProj.zoom);
reprojectFeatures = function(features){
var feats = [];
var nofeats = [];
var ext = map.getMaxExtent();
features.forEach(function(f){
if(!f.geometry.coordinates.source){
f.geometry.coordinates.source = [f.geometry.coordinates[0],f.geometry.coordinates[1]];
}
var lat = f.geometry.coordinates.source[1];
var ll = new OpenLayers.LonLat(f.geometry.coordinates.source[0],f.geometry.coordinates.source[1]).transform(sourceProj,destProj.olProjection);
f.geometry.coordinates[0] = ll.lon;
f.geometry.coordinates[1] = ll.lat;
if(map.options.wrapDateLine || ext.containsLonLat(ll)){
//console.log('+ ' + lat)
feats.push(f);
}else{
//console.log('- ' + lat)
//nofeats.push(f)
}
});
d3Layer.minMag = d3.min(feats,function(f){
return f.properties.mag;
});
d3Layer.maxMag = d3.max(feats,function(f){
return f.properties.mag;
});
d3Layer.minTime = d3.min(feats,function(f){
return f.properties.time;
});
d3Layer.maxTime = d3.max(feats,function(f){
return f.properties.time;
});
//console.log('NOFE')
//console.log(nofeats)
d3Layer.d3LoadFeatures(feats);
}
if(!features){
$.getJSON('http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson')
.done(function(resp){
features = resp.features;
reprojectFeatures(features);
});
}else{
reprojectFeatures(features)
}
}
setProjection = function(proj){
if(projections.hasOwnProperty(proj)){
if(!Proj4js.defs[proj] && projections[proj].def){
Proj4js.defs[proj] = projections[proj].def;
}
if(!projections[proj].olProjection){
projections[proj].olProjection = new OpenLayers.Projection(proj);
}
destProj = projections[proj];
initMap();
}
}
console.log(projectionSwitcher.value)
setProjection(projectionSwitcher[0].value);
}
</script>
<style type="text/css">
html,body,#map{
margin:0px;
padding:0px;
width:100%;
height:100%;
background:black;
}
#map .olControlAttribution {
right: 10px;
bottom:10px;
}
.olControlAttribution a{
color:#FFF;
}
#tools{
position: absolute;
top:20px;
right:20px;
z-index:1500;
width:220px;
padding:10px;
background:rgba(255,255,255,.5);
border-radius: 5px;
border:0px solid #ccc;
text-align:center;
}
</style>
</head>
<body onload="load();">
<div id="map"></div>
</body>
OpenLayers.Layer.D3 = OpenLayers.Class(OpenLayers.Layer.Vector, {
d3FeaturesNodeClass:'d3features',
d3Features:[],
afterAdd:function(){
var self = this;
if(this.renderer.CLASS_NAME.match(/SVG$/)){
this.d3InitSVG();
this.events.register('moveend',this,this.onMoveEnd);
}else{
}
},
onMoveEnd:function(e){
this.onPan();
this.d3RenderFeatures();
},
onPan:function(){
if(this.d3FeaturesNode){
this.d3ClearRoot();
var ext = this.map.getExtent();
var res = this.map.getResolution();
var l = -ext.left/res;
var t = ext.top/res;
var _l = this.renderer.left;
var _t = this.renderer.top;
var _xOffset = this.renderer.xOffset;
var _x = l - _l + _xOffset;
var _y = t - _t;
this.d3FeaturesNode.attr("transform", "translate(" + _x + "," + _y + ")");
}
},
getFeaturesInView:function(){
var self = this;
var feats = [];
var boundsArr = this.getViewportBoundsArr();
if(this.d3Features){
this.d3Features.forEach(function(f){
if(self.inViewPort(new OpenLayers.LonLat(self.d3XAccessor(f),self.d3YAccessor(f)),boundsArr)){
feats.push(f)
}
})
}
return feats;
},
//accepts lonlat and array of bnds obj
//for finding features within viewport
//bndsArr accounts for dateline
inViewPort: function(ll,bndsArr){
var inView = false;
if(bndsArr && bndsArr.length > 0){
//var _oll = {lon:ll[0],lat:ll[1]};
inView = bndsArr[0].containsLonLat(ll);
if(!inView && bndsArr.length > 1){
inView = bndsArr[1].containsLonLat(ll);
}
}
return inView;
},
getViewportBoundsArr:function(){
var destProj = this.map.projection;
var llProj = new OpenLayers.Projection("EPSG:4326");
var isMercator = destProj.projCode.match('3857') || destProj.projCode.match('900913') ? true : false;
var bnds = this.map.getExtent();
var bndsArr;
if(!isMercator){
bndsArr = [bnds];
}else{
var nBnds = bnds.clone().transform(destProj,llProj);
var nBnds1 = nBnds.clone();
//var nBnds1 = [[nBnds.left,n],[]]
bndsArr = [nBnds1];
if(
((nBnds.left > 0 && nBnds.right < 0) ||
(nBnds.left > 0 && nBnds.left > nBnds.right) ||
(nBnds.left < 0 && nBnds.left > nBnds.right))
){
//small hack!
var smallNum = .0000000001;
bndsArr = [
new OpenLayers.Bounds(nBnds.left,nBnds.bottom,180-smallNum,nBnds.top),
new OpenLayers.Bounds(-180 + smallNum,nBnds.bottom,nBnds.right,nBnds.top)
]
}
bndsArr.forEach(function(b){
b.transform(llProj,destProj);
});
}
return bndsArr;
},
d3InitSVG:function(){
this.d3Div = d3.select(this.div);
var size = this.map.getSize();
this.olRoot = this.d3Div.selectAll("svg");
this.olRoot.remove();
this.d3Root = this.d3Div.append("svg")
.attr('width',size.w)
.attr('height',size.h);
this.d3ClearRoot();
},
d3LoadFeatures:function(features){
this.d3Features = features;
this.d3ClearRoot();
this.d3RenderFeatures();
},
d3ClearRoot:function(){
if(this.d3Root){
this.d3Root.select('.' + this.d3FeaturesNodeClass).remove();
}
this.d3FeaturesNode = this.d3Root.append('g')
.attr('class',this.d3FeaturesNodeClass);
},
d3RenderFeatures:function(){
},
d3FeatureXAccessor:function(f){
return f[0];
},
d3FeatureYAccessor:function(f){
return f[1];
},
d3Project:function(f){
var p = [this.d3XAccessor(f),this.d3YAccessor(f)];
this.renderer.calculateFeatureDx({left:p[0],right:p[0]},this.map.getMaxExtent());
var featureDx = this.renderer.featureDx;
var geometry = {x:p[0],y:p[1]};
var resolution = this.renderer.getResolution();
var x = ((geometry.x - featureDx) / resolution + this.renderer.left);
var y = (this.renderer.top - geometry.y / resolution);
return [x,y];
}
});
OpenLayers.Layer.D3Hex = OpenLayers.Class(OpenLayers.Layer.D3, {
//used for calculating hexes
d3HexRadius:10,
//used for drawing hexes
d3VisibleHexRadius:function(d){
return this.d3HexRadius - 1;
},
//limit, above which, points are drawn rather than hexes
d3HexZoomLimit:6,
d3RenderFeatures:function(){
if(this.d3HexZoomLimit && this.map.getZoom() > this.d3HexZoomLimit){
this.d3DrawPoints();
}else{
this.d3UpdateHexData();
this.d3DrawHexes();
}
},
d3MakeHexBin:function(){
var size = this.map.getSize();
this.hexbin = d3.hexbin()
.size([size.w,size.h])
.radius(this.d3HexRadius);
},
d3DrawPoints:function(){
//console.log('Make points..')
var feats = this.getFeaturesInView();
this.d3FeaturesNode.selectAll("circle")
.data(feats)
.enter().append("circle")
.attr(this.d3GetPointAttr())
.style(this.d3GetPointStyle())
},
d3DrawHexes:function(){
var self = this;
this.d3FeaturesNode.selectAll("path")
.data(this.hexData)
.enter().append("path")
.attr(this.d3GetHexAttr())
.style(this.d3GetHexStyle())
},
d3UpdateHexData:function(){
if(!this.hexbin){
this.d3MakeHexBin();
}
var points = [];
var self = this;
this.d3Features.forEach(function(f){
var p = self.d3Project(f);
p.push(f);
points.push(p);
});
this.hexData = this.hexbin(points);
this.d3AfterHexDataUpdate();
},
//this makes is a place to set up variables used for styling based on how the hexes were distributed out
d3AfterHexDataUpdate:function(){
this.d3MaxHexBinSize = d3.max(this.hexData,function(h){
return h.length
})
this.d3MinHexBinSize = d3.min(this.hexData,function(h){
return h.length
});
},
d3HexFill:"#FFF",
d3PointFill:"#FFF",
d3GetHexStyle:function(){
var self = this;
return {
'fill':function(d){
if(typeof self.d3HexFill == 'function'){
self.d3HexFill(d,this);
}else{
return self.d3HexFill;
}
},
'opacity':function(d){
if(typeof self.d3HexOpacity == 'function'){
return self.d3HexOpacity(d,this);
}else{
return self.d3HexOpacity;
}
},
'stroke':function(d){
if(typeof self.d3HexStroke == 'function'){
return self.d3HexStroke(d,this);
}else{
return self.d3HexStroke;
}
},
}
},
d3GetHexAttr:function(){
var self = this;
return {
"d":function(d) {
return self.hexbin.hexagon(self.d3VisibleHexRadius(d));
},
"transform":function(d) {
return "translate(" + d.x + "," + d.y + ")";
}
};
},
d3PointRadius:function(d){
return 5;
},
d3GetPointAttr:function(){
var self = this;
return {
"r":function(d) {
return self.d3PointRadius(d);
},
"transform":function(d) {
var p = self.d3Project(d);
return "translate(" + p[0] + "," + p[1] + ")";
}
}
},
d3GetPointStyle:function(){
var self = this;
return {
'fill':function(d){
if(typeof self.d3PointFill == 'function'){
self.d3PointFill(d,this);
}else{
return self.d3PointFill;
}
},
'opacity':function(d){
if(typeof self.d3PointOpacity == 'function'){
return self.d3PointOpacity(d,this);
}else{
return self.d3PointOpacity;
}
},
'stroke':function(d){
if(typeof self.d3PointStroke == 'function'){
return self.d3PointStroke(d,this);
}else{
return self.d3PointStroke;
}
},
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment