SSHRC Insight Grant Awarded!

I’m excited to share my proposal for a SSHRC Insight Grant was funded this spring. The grant will fund a 4-year (2017-2021), $222,016.00 project titled “Time, geography, and food: how time use, social-spatial context, transportation options, and personal economics affect access to food in cities.”

More on this soon.

 

Mapbox GL JS 3D buildings

Here’s another mapbox gl js example – this time using Mapbox’s new ability to visualize 3-dimensional data. I found 3D massing data of buildings in Toronto on the City of Toronto’s open data site. Buildings are coloured based on their height, and a click event creates a pop up with the clicked building’s height.

Here’s the javascript, followed by the html:

Javascript

mapboxgl.accessToken = //<your access code here>;

var map = new mapboxgl.Map({
 container: 'map',
 style: 'mapbox://styles/mwidener/ciuvowcno00ee2js5wibckxv3',
 center: [-79.388924,43.645],
 zoom: 14,
 minZoom: 13, //restrict map zoom - buildings not visible beyond 13
 maxZoom: 20,
 pitch: 60, //tilt camera
 bearing: 17.5 //adjust angle we're looking (17.5 degrees from north)
});

var features=[];

var layers = [
 //Color Height threshold Label
 ['#ffffcc', 15, '0-15 Meters'],
 ['#ffeda0', 30, '15-30 Meters'],
 ['#fed976', 50, '30-50 Meters'],
 ['#feb24c', 100, '50-100 Meters'],
 ['#fd8d3c', 200, '100-200 Meters'],
 ['#fc4e2a', 300, '200-300 Meters'],
 ['#e31a1c', 400, '300-400 Meters'],
 ['#bd0026', 500, '400-500 Meters']
];

//create a popup object and give it some properties. we'll generate/overwrite a new popup each time we click.
var popup = null;

map.on('style.load', function(){
 var legend = document.getElementById('legend'); //get the legend from the HTML document
 map.addSource('building_source', {
 'url': 'vector',
 'url': 'mapbox://mwidener.1ayxbus1' //location of 3d building data
 });

 layers.forEach(function(layer, i){
 map.addLayer({
 'id': 'building_layer-' + i,
 'type': 'fill',
 'source': 'building_source',
 'paint': {
 'fill-color': layer[0],
 'fill-opacity': 0.6,
 'fill-extrude-height': { //grab the height from the 'EleZ' attribute from building data
 'type': 'identity',
 'property': 'EleZ'
 },
 'fill-extrude-base': 0
 },
 'source-layer': 'Archive-bcz1gm' //source layer of 3d building data on mapbox studio
 });
 // Build out legends
 //***********//
 var item = document.createElement('div');//each layer gets a 'row' - this isn't in the legend yet, we do this later
 var key = document.createElement('span');//add a 'key' to the row. A key will be the color circle
 var value = document.createElement('span');//add a value variable to the 'row' in the legend

 key.className = 'legend-key'; //the key will take on the shape and style properties defined here, in the HTML
 key.style.backgroundColor = layer[0]; // the background color is retreived from teh layers array

 value.id = 'legend-value-' + i; //give the value variable an id that we can access and change

 item.appendChild(key); //add the key (color cirlce) to the legend row
 item.appendChild(value);//add the value to the legend row

 legend.appendChild(item); // Add row to the legend

 });

 layers.forEach(function(layer, i) {
 var filters = ['all',['<=', 'EleZ', layer[1]]];
 if (i !== 0) filters.push(['>', 'EleZ', layers[i - 1][1]]);
 map.setFilter('building_layer-' + i, filters);
 document.getElementById('legend-value-' + i).textContent = layer[2];//.toLocaleString(); //as we iterate through the layers
 //get the correct row, and add the appropriate text //get the correct row, and add the appropriate text
 });
});

//create an array that has the name of all layers created above
var all_added_layers = []
layers.forEach(function(layer,i){
 all_added_layers.push('building_layer-'+i);
})

map.on('mousemove', function(e){
 features = map.queryRenderedFeatures(e.point, {all_added_layers}); //get features that are in building layers
 if (features.length > 0){
 console.log(features[0].id);
 //use the following code to change the cursor to a pointer ('pointer') instead of the default ('')
 map.getCanvas().style.cursor = (features[0].properties.EleZ != null) ? 'pointer' : '';
 }
 //if there are no provinces under our mouse, then change the cursor back to the default
 else {
 map.getCanvas().style.cursor = '';
 }
});

//an event where when there is a mouse click, send the event data (represented by e) to a function that does something
map.on('click', function(e) {
 popup = new mapboxgl.Popup({
 closeButton: true,
 closeOnClick: true
 });

 //get the spatial features where your mouse is currently located. note we use the pixel location (e.point) and not lat/lon here.
 //also specify the feature we want to pay attention to - 'provinces_base'
 var features = map.queryRenderedFeatures(e.point, {layers: all_added_layers});
 if (features.length > 0 && features[0].properties.EleZ != null){ //if there is a feature in our features array then proceed. otherwise, we don't have anything to work with
 //center at click locations
 map.panTo(e.lngLat, {'duration': 1000});
 //set the location of our popup to the lnglat of our click (note we use e.lnglat here and NOT e.point)
 popup.setLngLat(e.lngLat);
 //give the popup content
 popup.setHTML(
 '<center>The height of this building is approximately: <b>' + Math.round(features[0].properties.EleZ) + '</b> meters.'
 );
 //finally add the popup to the map
 popup.addTo(map);
 }

 //if there are no features that we're interested in, reset the filter for provinces_hover and remove any popup.
 else{
 //map.setFilter('provinces_hover',['==','PRVNAME','']);
 popup.remove();
 }

});

HTML

<!DOCTYPE html>
<html>
<head>
 <meta charset=utf-8 />
 <title>Toronto Skyline</title>
 <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
 <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.26.0/mapbox-gl.js'></script>
 <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.26.0/mapbox-gl.css' rel='stylesheet' />
 <style>
 body { margin:0; padding:0; }
 #map { position:absolute; top:0; bottom:0; width:100%; }
 </style>
 <style>
 .legend-container {
 position: absolute;
 bottom: 0px;
 right: 0px;
 padding: 0px 10px;
 margin-bottom: 30px;
 z-index: 1;
 }
 .legend {
 font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
 background-color: white; /*background color of legend*/
 padding: 10px; /*around stuff inside of legend*/
 border-radius: 10px; /*round the edges of the legend div*/
 box-shadow: 10px 10px 20px rgba(0,0,0,0.60); /*horizontal shadow, vertical shadow, size of shadow, color*/
 }
 .legend h2 {
 margin: 0px 0px 5px; /*top margin, left/right margin, bottom margin*/
 }
 .legend-key {
 display: inline-block;
 border-radius: 50%; /*curve border ... make a circle*/
 width: 20px; /*size of circle*/
 height: 20px;
 margin-right: 10px; /*position of circle*/
 }
 </style>

</head>
<body>
<div id='map' class='map'> </div>
<div class='legend-container'>
 <div id='legend' class='legend'>
 <h2>Height</h2>
 <i>Use right mouse button to navigate 3d space.
 <br>Click on a building to see its height.</i>
 <!-- All the other legend stuff will be added here as we programatically insert divs/spans-->
 </div>
</div>
<script src='./TorontoBuildings.js'></script>
</body>
</html>

Mapbox GL JS – Click and Hover Events

We just went over events in my mapbox gl js class at the University of Toronto. For fun, I updated the “GeoCV” map on my homepage to add these features. Here’s the JS code:

//Javascript File
mapboxgl.accessToken = '<INSERT YOUR ACCESS KEY HERE>';
var popup = null
var map = new mapboxgl.Map({
 container: 'map',
 style: 'mapbox://styles/mwidener/ciukh23bo000b2io4qrcq87s9',
 center: [-84.516476, 39.131658],
 zoom: 2.5
});

//academic_positions is a vector tile file stored on mapbox studio
//~~~FEATURES:~~~~
//Academic_Positions
//3 properties | This layer contains mostly Points
//Name  String
//Position  String
//Time Period  String

map.on('style.load', function(){
 map.addSource('academic_positions_src',{
 'type': 'vector',
 'url': 'mapbox://mwidener.ciulhf3s100662zmwys884b2b-0d75j'
 });

//display two layers ... both drawing from the vector tiles from 
//source: 'academic_provinces_src'
 map.addLayer({
 'id': 'academic_positions',
 'type': 'circle',
 'source': 'academic_positions_src',
 'interactive': true,
 'layout': {},
 'paint': {
 'circle-color': 'red',
 'circle-radius': 5,
 },
 'source-layer': 'Academic_Positions'
 });

 map.addLayer({
 'id': 'academic_positions_clicked',
 'type': 'circle',
 'source': 'academic_positions_src',
 'interactive': true,
 'layout': {},
 'paint': {
 'circle-color': 'red',
 'circle-radius': 6, //make point a little bigger after click
 },
 'source-layer': 'Academic_Positions',
 'filter': [ '==', 'Name', '' ] 
// start with a filter that doesn't select anything
 });

});

//an event where when there is a mouse click, 
//send the event data (represented by e) to a 
//function that does something

map.on('click', function(e) {
 //get the spatial features where your mouse is currently located. 
 //note we use the pixel location (e.point) and not lat/lon here.
 //also specify the feature we want to pay attention 
 //to - 'academic_positions'
 var features = map.queryRenderedFeatures(e.point, {
 layers: ['academic_positions']
 });
 if (!features.length) {
 return;
 }

 popup = new mapboxgl.Popup({
 closeButton: true,
 closeOnClick: true
 });

 //set a filter on the academic_positions_clicked layer 
 //so that the point we clicked on shows up
 map.setFilter('academic_positions_clicked',['==','Name',features[0].properties.Name]);
 //set the location of our popup to the 
 //lnglat of our click (note we use e.lnglat here and NOT e.point)
 popup.setLngLat(e.lngLat);
 //give the popup content
 popup.setHTML(
 "<b>" + features[0].properties.Name + "</b>" + 
 "<br>Michael's position: " + features[0].properties.Position +" "
 );
 //finally add the popup to the map
 popup.addTo(map);
 map.panTo(e.lngLat);
 });

//When the mouse moves over a spatial layer 
//we care about (e.g. a point) let's change the mouse cursor
map.on("mousemove", function(e) {
 //get the province feature underneath the mouse
 var features = map.queryRenderedFeatures(e.point, {
 layers: ["academic_positions"]
 });
 //if there's a point under our mouse, then do the following.
 if (features.length > 0) {
 //use the following code to change the 
 //cursor to a pointer ('pointer') instead of the default ('')
 map.getCanvas().style.cursor = (features[0].properties.Name !== null) ? 'pointer' : '';
 }
 //if there are no points under our mouse, 
 //then change the cursor back to the default
 else {
 map.getCanvas().style.cursor = '';
 }
});

 

And here’s the HTML:

<!DOCTYPE html>
<html>
<head>
 <meta charset=utf-8 />
 <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
 <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.26.0/mapbox-gl.js'></script>
 <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.26.0/mapbox-gl.css' rel='stylesheet' />
 <style>
 body { margin:0; padding:0; }
 #map { 
 position:absolute; 
 top:0; 
 bottom:0; 
 width:100%; 
 }
 .map-overlay {
 position: absolute;
 bottom: 0;
 right: 0;
 background: rgba(255, 255, 255, 0.8);
 margin-right: 10px;
 margin-bottom:24px;
 padding-left: 3px;
 padding-right:3px;
 padding-top:3px;
 padding-bottom:3px;
 font-family: Arial, sans-serif;
 border-radius: 5px;
 }


 </style>
</head>
<body>
 <div id='map' class='map'> </div>
 <!--<div id='map'></div>-->
 <div class='map-overlay' id='features'><b><center> Click on a red point <br> to see where <br> Michael has been! </b></center></div>
 <script src='./geocv.js'></script>
</body>
</html>

Mapbox GL Hex Bins Example

For my Developing Web Maps course this spring, I’ve been putting together a ton of examples using the new Mapbox GL API and their TURF API (a spatial analysis toolkit).

I plan on putting up a few posts reviewing how to use Mapbox GL over the summer, as I found the number of online resources lacking. I figure I can help rectify this a bit by sharing my own code and lessons learned.

As a preview, here is an example from the class I’m teaching next Wednesday where I generate a hex bin grid using TURF, count the number of airport points within each bin, and then color the bins using jenks natural breaks.

I’m looking forward to sharing more – but as you can probably tell by the lack of posts, it’s been a busy first year at the new gig at University of Toronto’s Department of Geography and Planning!

geoJSON file of Canadian Airports    HTML:turfExample2   JS: turfExample2

JS Code:

mapboxgl.accessToken = <YOUR MAPBOX KEY>;

var map = new mapboxgl.Map({
 container: 'map',
 style: 'mapbox://styles/mwidener/cilkw1mzf001q9jkmn4212sdw',
 center: [-90, 52],
 zoom: 3
});


//CREATE A BOUNDING BOX AROUND POINTS (ALSO USE FOR HEX GRID)
var enveloped = turf.envelope(canadianAirports); //send point geojson to turf, creates an 'envelope' (bounding box) around points
var result = { //put the resulting envelope in a geojson format FeatureCollection
 "type": "FeatureCollection",
 "features": [enveloped] //don't forget brackets
};

//CREATE A HEX GRID
//must be in order: minX, minY, maxX, maxY ... you have to pick these out from your envelope that you created previously
var bbox = [enveloped.geometry.coordinates[0][0][0],enveloped.geometry.coordinates[0][0][1],enveloped.geometry.coordinates[0][2][0],enveloped.geometry.coordinates[0][2][1]];
var hexgridUnits = 'kilometers' //units that will determine the width of the hex grid
var cellWidth = 200 //in the units you defined above
var hexgrid = turf.hexGrid(bbox,cellWidth,hexgridUnits); //makes the new geojson hexgrid features

//COUNT THE NUMBER OF AIRPORTS IN EACH HEX BIN
var hexAirports = turf.count(hexgrid,canadianAirports,'airportCount');

//create jenks natural breaks - generates min, breaks, max ... remember for 5 categories, we only need 4 numbers
var numberBreaks = 6
var jenksbreaks = turf.jenks(hexAirports,'airportCount',numberBreaks);
var colors = ['#ffffb2','#fed976','#feb24c','#fd8d3c','#f03b20','#bd0026']
var transparency = [.3,0.5,0.5,0.5,0.5,0.5]

jenksbreaks.forEach(function(element,i){
 if (i > 0){
 jenksbreaks[i] = [element, colors[i-1],transparency[i-1]];
 }
 else{
 jenksbreaks[i] = [element, null];
 }
});


map.on('style.load', function(){
 map.addSource('airports',{
 "type": "geojson",
 "data": canadianAirports
 })
 map.addLayer({
 "id": "airportsLayer",
 "type": "circle",
 "source": "airports",
 "layout": {},
 "paint":{
 'circle-color': "black",
 'circle-radius': 1,
 'circle-opacity': 1
 }
 });

// //HEXGRID EXAMPLE
 map.addSource('airportHexGrid',{
 "type": "geojson",
 "data": hexAirports //this is the hexgrid we just created!
 });
 for(i = 0; i < jenksbreaks.length; i++){
 if (i > 0){
 map.addLayer({
 "id": "airportHexGrid-" + (i-1),
 "type": "fill",
 "source": "airportHexGrid",
 "layout": {},
 "paint":{
 'fill-color': jenksbreaks[i][1],
 'fill-opacity': jenksbreaks[i][2],
 }
 },"airports");
 };
 };

 jenksbreaks.forEach(function(jenksbreak, i){
 if (i > 0){
 var filters = ['all',['<=', 'airportCount', jenksbreak[0]]];
 if(i>1){
 filters.push(['>', 'airportCount', jenksbreaks[i - 1][0]]);
 map.setFilter('airportHexGrid-' + (i-1), filters);
 };
 console.log(filters);
 };
 });

});

 

 

Update: Fall 2015 Edition

Much has happened since I last posted. Over the summer my family successfully crossed the border and settled in our new home in Toronto. Since then, we’ve been running around town, making sure all of our (and our car’s) paper work is handed in on time.

12031452_10206021132380680_2775120001946996382_o

When in Canada …

It’s exciting being in a new department – meeting new scholars and students, chatting about new ideas, and exploring the institution. I’ve been happy to find that there’s plenty of energy at the Dept. of Geography and Planning and across the campus of U of T. At the moment I’m mostly acclimatizing myself to the new work environment, but as I get more comfortable I’m hope to become more involved in the various academic communities – like UTTRI and the various departments and centers concerned with health.

Teaching

This fall I’ll be teaching Analytical Methods (GGR 270) and GIS for Public Health (GGR 300 … temporary code), and in the winter term I teach a new course on developing web GISystems. For the latter course we’ll be using MapBox, an open source web mapping platform that I’ve found to be quite flexible. Should be fun!

I am also looking for highly qualified masters and PhD level graduate students to apply to work with me at the University of Toronto in either Geography or Planning (same department, different degrees). Starting 2016, I want to get my lab up and running, and motivated graduate students, interested in health, transportation, and/or cities are encouraged to apply. If you’re curious if you might be a good fit, please send me an email.

Conferences

I am helping out with a few conferences in 2016. First, I am organizing the second year of sessions on Mobility, Health, and the City with Jerry Shannon (University of Georgia) and Debs Ghosh (University of Connecticut) for the 2016 AAG in San Francisco. Last year, we had great turn out and very interesting presentations/discussions … we expect the same for next spring. If you have any interest in participating in these sessions, please send me an email and I’ll let you know what’s going on.

Secondly, I am co-organzing the workshops for the GIScience conference with Steve Farber, being held in Montreal in the fall of 2016. This is still a ways off, so I’m sure I’ll post more about this later on.

I will also be attending the TRB again in January and CAG in Halifax. Hope to see some of you there.

Research

STPaths

Sample space-time paths for the CCTST grant

I’m still working away at the grant awarded through CCTST at Children’s Hospital and UC on the mobility patterns of children with asthma and their parents. Things slowed down considerably due to the move, but we’re finally picking up steam again. I expect to have more on that later on in the semester.

Beyond this, my former MS student, Nate Wessel, and I published a paper on cartography and visualization for bicycle maps – this will appear in the journal Cartographic Perspectives … a link will be posted when it is up.

Many other things are under review and I’ll post more about them as they are presented/published.

New Position Beginning July 2015 / New Paper Accepted

I’m happy to officially announce that I accepted a faculty position last January in the Department of Geography and Program in Planning at the University of Toronto – St. George beginning July 2015. I’m excited for the move and can’t wait to continue my work, begin new collaborations, and work with students in Canada.

In other news, a new paper has been accepted in the journal Aerospace Medicine and Human Performance, titled “Ground and Helicopter Emergency Medical Services Time Tradeoffs Assessed with Geographic Information” with Zac Ginsberg, Daniel Schleith, Doug Floccare, Jon Hirshon, and Sam Galvagno. I’ll post an update when the piece is available online!

Two papers accepted – Food Access/Older Adults

Two more papers were recently accepted for publication at the end of this past November.

The first article will appear in the Journal of Transport Geography, and follows up on the dynamic access to supermarkets research I published in 2013 in Health and Place. In the new paper, we examine transit commuters and find that this population sees a much greater increase in spatial accessibility to food stores than the automobile commuters in the previous paper.

Widener, Farber, Horner, Neutens (in press) Spatiotemporal accessibility to supermarkets using public transit: an interaction potential approach in Cincinnati, Ohio. Journal of Transport Geography.

The second accepted paper will appear in The Professional Geographer, and is led by Mark Horner (Florida State) and Daniel Schleith (my PhD student at UC). Using a jobs-housing spatial framework, we examine the older adult population’s ability to access employment opportunities in a number of Florida cities.

Horner, Schleith, Widener (in press) An analysis of the commuting and jobs-housing patterns of older adult workers. The Professional Geography.

I’ll repost here with links when the articles are officially available online!

Patient Centered Outcomes Research Grant Funded

I’m happy to announce that my proposal, “Identifying disparities in pediatric asthma outcomes based on spatiotemporal access to relevant health services”, submitted to the CCTST PCOR funding announcement was funded.

My co-Is (all from Cincinnati Children’s Hospital Medical Center) and I are very excited about the possibility of furthering our understanding of triggers of asthma that go beyond pollutants in the environment.

Work will begin November 1st, and I’ll post updates as they come.

New paper accepted to Natural Hazards Review

A manuscript I am a collaborator on was just accepted for publication at the American Society of Civil Engineer’s journal: Natural Hazards Review.

The paper, titled “Impacts of disrupted road networks in siting relief facility locations: A case study for Leon County, FL” was lead by PhD student Holly Nowell and coauthored by Dr. Mark Horner and myself.

I’ll post more when the online first version becomes available!