Skip to content

Instantly share code, notes, and snippets.

@5un
Created April 11, 2018 18:49
Show Gist options
  • Save 5un/7b1a3d79cc9947ed554024df3b86a1f5 to your computer and use it in GitHub Desktop.
Save 5un/7b1a3d79cc9947ed554024df3b86a1f5 to your computer and use it in GitHub Desktop.
ischool-geo-viz-04
license: mit

04 Adding Custom Map Controls

In this example we add a couple buttons to our map (their style could be improved, sorry!) which allow for toggling the hexbins and crashes point data on and off.

Things to Try out

  • See if you can add a slider for each layer, to change its opacity.
  • What other UI elements might we need for this map? How would we go about making them?

Built with blockbuilder.org

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

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

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

<!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