Skip to content

Instantly share code, notes, and snippets.

@clhenrick
Last active July 20, 2018 20:17
Show Gist options
  • Save clhenrick/8454120e900df5b04b24aeb2bac1d3f9 to your computer and use it in GitHub Desktop.
Save clhenrick/8454120e900df5b04b24aeb2bac1d3f9 to your computer and use it in GitHub Desktop.
ischool-geo-viz-04
license: mit

02 Data Driven Styling With MapBoxGL

In this step we style our data using one of its properties, crash_type. Notice that we do this by adding stops to our style configuration, and that the size of our circles transitions smoothly as we zoom in and out.

Things to Try Out:

  • Change the stop values for the 'circle-radius' property. Remember that stops accepts an array of arrays, where each inner array has a value for zoom level and a value for the circle radius. What happens if we add more than two arrays to our stops array?

Helpful links

Built with blockbuilder.org

forked from clhenrick's block: ischool-geo-viz-template

forked from clhenrick's block: ischool-geo-viz-02

<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>04: Custom Map Controls</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src="//d3js.org/d3.v3.min.js"></script>
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.35.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.35.0/mapbox-gl.css' rel='stylesheet' />
<!-- NEW: add turf.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/2.0.2/turf.min.js"></script>
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
.map-overlay {
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
position: absolute;
width: auto;
top: 0;
left: 0;
padding: 10px;
z-index: 10;
}
.map-overlay .map-overlay-inner {
background-color: #fff;
box-shadow:0 1px 2px rgba(0, 0, 0, 0.10);
border-radius: 3px;
padding: 10px;
margin-bottom: 10px;
}
.map-overlay-inner h4 {
margin: 0;
}
.map-overlay-inner button {
display: inline-block;
width: auto;
height: 20px;
border: none;
cursor: pointer;
background-image: none;
background-color: cadetblue;
}
.map-overlay-inner button.active {
background-color: lightgreen;
}
.map-overlay-inner button:focus {
outline: none;
}
.map-overlay-inner button:hover {
box-shadow:inset 0 0 0 3px rgba(0, 0, 0, 0.10);
}
</style>
</head>
<body>
<div class="map-overlay">
<div class="map-overlay-inner">
<h4>Map Layers</h4>
<button id="hexbins" value="crashesHexGrid" class="active">Hex Bins</button>
<button id="crashes" value="crashes">Crashes</button>
</div>
</div>
<div id='map'></div>
<script>
// set our mapbox access token
mapboxgl.accessToken = 'pk.eyJ1IjoiZW5qYWxvdCIsImEiOiIzOTJmMjBiZmI2NGQ2ZjAzODhiMzhiOGI2MTI1YTk4YSJ9.sIOXXU3TPp5dLg_L3cUxhQ';
var dataURL = "https://raw.githubusercontent.com/clhenrick/geovisualization_workshop_ischool/master/data/nyc_crashes.geojson"
// create a new map; set the style, zoom, and center
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v9',
center: [-74.0059, 40.7127],
zoom: 10
});
// function to call once the map has loaded
map.on('load', function() {
console.log('map loaded!');
// we'll use d3.json to load the point data asynchronously
d3.json(dataURL, function(error, data) {
// report an error if their was a problem getting the geojson data
if (error) throw error;
// filter out data with no geometry, otherwise MapboxGL throws an error and won't load data
data.features = data.features.filter(function(d) {
return d.geometry;
});
// bounding box for our data's extent (I computed this ahead of time)
var bbox = [ -74.24939, 40.50394, -73.701195, 40.90995 ];
// our cell size, try changing this to a different value like 10
var cellSize = 1;
// units of our cells
var units = 'kilometers';
// create our hex grid
var hexGrid = turf.hexGrid(bbox, cellSize, units);
// count the number of crashes in each hex grid, this will take a few seconds.
// in an real life app we would want to display a loading GIF or message here
console.log('binning data, hold on tight...');
var hexCrashes = turf.count(hexGrid, data, 'totalCrashes');
console.log('done! ', hexCrashes);
// create a jenks natural breaks classification, see: https://en.wikipedia.org/wiki/Jenks_natural_breaks_optimization
// the number of breaks we are using
var numberBreaks = 5;
// NOTE: turf-jenks is technically depreciated, but it still works
var jenksBreaks = turf.jenks(hexCrashes, 'totalCrashes', numberBreaks);
console.log('our jenksBreaks array: ', jenksBreaks);
// via color brewer: http://colorbrewer2.org/#type=sequential&scheme=RdPu&n=5
var colors = ['#feebe2', '#fbb4b9', '#f768a1', '#c51b8a', '#7a0177'];
// create our color stops for the hexCrashes map layer style
var colorStops = jenksBreaks.map(function(b, i) {
if (i > 0) {
// here we map each color in the color array to it's corresponding bin
return [b, colors[i -1]];
} else {
// in the case of 0, hide the bin
return [b, 'rgba(255, 255, 255, 0)'];
}
});
// add our hex crashes layer data
map.addSource('crashesHexGrid', {
type: 'geojson',
data: hexCrashes
});
// add our hex crashes layer style
map.addLayer({
"id": "crashesHexGrid",
"type": "fill",
"source": "crashesHexGrid",
"layout": {},
"paint":{
'fill-color': {
property: 'totalCrashes',
stops: colorStops,
},
'fill-opacity': 0.6
}
});
// add the original crashes point data
map.addSource('crashes', {
type: 'geojson',
'data': data,
});
// add the crashes point layer
map.addLayer({
id: 'crashes',
type: 'circle',
source: 'crashes',
'layout': {},
'paint': {
'circle-color': {
property: 'crash_type',
type: 'categorical',
stops: [
['no_injury_fatality', '#FECC5C'],
['injury', '#FD8D3C'],
['fatality', '#F03B20'],
['injury_and_fatality', '#BD0026']
]
},
'circle-opacity': 0.8,
'circle-radius': {
'base': 1.75,
'stops': [[12, 2], [22, 180]]
},
}
}, 'crashesHexGrid');
// disable our crashes layer & set our hexbin layer to visible (necessary for button interactions)
map.setLayoutProperty('crashes', 'visibility', 'none');
map.setLayoutProperty('crashesHexGrid', 'visibility', 'visible');
var buttonIds = ['hexbins', 'crashes'];
for (var i = 0, l = buttonIds.length; i < l; i++) {
var el = document.getElementById(buttonIds[i]);
el.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
var visibility = map.getLayoutProperty(this.value, 'visibility');
if (visibility === 'visible') {
map.setLayoutProperty(this.value, 'visibility', 'none');
this.className = '';
} else {
this.className = 'active';
map.setLayoutProperty(this.value, 'visibility', 'visible');
}
}
}
}); // end d3.json
}); // end map.on('load')
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment