The goal of this project is to create a process by which we can understand urban heat islands—the phenomenon wherein built and natural conditions conspire to raise temperature in urban areas during periods of hot weather—with readily accessibly data from satellite images. During a spell of hot weather in late July 2019, mercury levels in Philadelphia remained above 90 degrees Fahrenheit during the day and even held above 80 over night. Yet this swelter was not felt equally across the city: the hottest and coolest—comparably—portions of the city differed by as much as 20 degrees. A park could be bearable; a parking lot could be insufferable. Each neighborhood sees unique effects from sun and heat and each season is different. Because of this, the following project is an application that can be used by all. While this application represents a high cost to build, it yields a low cost for experimentation. The subsequent exploration is modest, but folding new cities and layering new methods only becomes easier with such an interface. Additionally, because it lacks advanced statistical analysis, forgoing the statistical for the graphical, it serves as a means to travel the globe by satellite—more qualitatively than quantitatively and with breadth rather than depth, although by allowing for such travel is by no means trivial. To explore the geography of heat with this tool, use this link.
Mapping surface temperature throughout the year in the united states reveals that at times urban areas—like the Acela Corridor—are warmer than regions at comparable latitudes (see: code below to reproduce).
Remote sensing technology provides robust spatio-temporal data (USGS’s Landsat 8 satellite orbits the earth every 100 minutes and returns to any given location roughly once every two weeks), which, clouds permitting, can be converted into surface temperature. Yet urban heat islands exist at the intersection of global forces and local conditions—where the weather meets the road, so to speak. In addition to investigating the nature of heat islands, this project will also create a workflow and tools for exploring heat islands in counties across the United States. This application will allow any user to zoom in on an area, see hot spots within it, and generate facts and figures that shed additional light on the phenomenon and its possible causes locally.
Toward this end, the following uses Google Earth Engine, a platform to geospatial analysis using JavaScript. Because this is an exercise as well as application, in the interest of practice, it involves manifold sources and types of data. The first half will leverage Landsat data from the United States, the second Sentinel data from the European Union. While former has a spatial resolution of 30 meters squared, the latter is sharper at 15. The map above uses NASA’s Moderate Resolution Imaging Spectroradiometer (MODIS), which is valuable for its temporal if not its spatial resolution, imaging every point on earth every couple of days rather than weeks.
Landsat and Sentinel offer distinct benefits. The former includes a thermal band, making it easy to calculate surface temperature; the latter does not. Yet Sentinel-2 provides a sharper resolution than Landsat 8, at 10 meters for most bands compared to 30. Further complicating the matter is the fact that Sentinel has not been live as long, making it difficult to measure change. Because this study is concerned with temperature, the ability to accurately derive it is important—to say the least. To be a valuable planning tool, we need a resolution that allows us to target aspects of the built environment driving heat islands. While early iterations involved pairing Sentinel images with temperature from Landsat, many of the covariates varied widely from the Landsat temperature—giving strikingly different contours—as visitation times differed. The visual benefit of seeing the features that define heat islands is compromised then. Staying consistent with Landsat data, though not as sharp, presents a clearer relationship between, say, temperature and vegetation, because points can be compared directly in time.
To create a layer of satellite imagery to ground our analysis of heat islands, we compensate for this using a technique called pan-sharpening, which combines a high resolution panchromatic band with the low resolution multispectral ones. This requires the Landsat 8 top-of-atmosphere, as opposed to surface, image collection; surface Landsat products do not have the panchromatic band (B8) necessary to polish the image.
var cloudy = ee.ImageCollection("LANDSAT/LC08/C01/T1_TOA")
.filterDate('2010-01-01', '2018-12-31')
We then filter out the cloudy images and run through a script from the guide, converting the collection into an image with the mosaic
command so that it the function works as written. Note also that we end by embedding visualization parameters into the image with the visualize
function, which is important to the functioning of the application.
var blurry = cloudy.filter(ee.Filter.lt('CLOUD_COVER', 1));
var cleanest = blurry.mosaic()
var hsv = cleanest.select(['B4', 'B3', 'B2']).rgbToHsv();
var sharpest = ee.Image.cat([
hsv.select('hue'), hsv.select('saturation'), cleanest.select('B8')
])
.hsvToRgb()
.visualize({min: 0, max: 0.25, gamma: [1.3, 1.3, 1.3]});
The result is a picture that is not perfect but markedly sharper than the raw image. This image will become a comparison when we layer temperature data over it, allowing us to see the urban forms that dispose an area to relative heat.
As open source software often does, Earth Engine has a strong community behind it. This project uses one particular contribution—a collection of palettes—from Gennadii Donchyts, Fedor Baart & Justin Braaten to improve its look. For more information and to see what other palettes are on offer, see this repository.
To pull in a palette without needing to code each hexadecimal string manually, load the package with the require
function, then choose a palette. The example below shows the scheme used to produce the above animation. Although it saves time in scripting different palettes on the front end, calling the package does slow down processing on the back end.
var palettes = require('users/gena/packages:palettes');
var palette = palettes.misc.kovesi.rainbow_bgyrm_35_85_c71[7];
With this map as a base, we can then layer on surface temperature along with a battery of explanatory variables—various indices to describe the landscape. This requires surface reflectance data, which we then mask using a function from the repository that holds them; much of this analysis uses 2018 but that year was marked by intense rainy and cloudy periods, so for longitudinal study we use 2016.
The goal of this application is to allow users to toggle between an island of land surface temperature and any covariates of interest; this particularly interface facilitates quick transitions between layers to help the process
Rather than rely on the imagination for palettes, a few existing ones correspond roughly to the variables that we are looking at: green for the natural environment, grey for the built environment, and blue for water.
var dataset = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
.filterDate('2018-01-01', '2018-12-31')
.map(maskL8sr)
var palettes = require('users/gena/packages:palettes');
var built = palettes.kovesi.diverging_linear_bjy_30_90_c45[7];
var green = palettes.kovesi.linear_green_5_95_c69[7];
var water = palettes.kovesi.linear_blue_5_95_c73[7];
We will use three measures of the landscape as covariates: normalized difference vegetation index and modifications of this that correspond to buildings and bodes of water. NDVI is simply the (near infrared band - red band) / (near infrared band + red band), while NDWI is the (green band - near infrared band) / (green band - near infrared band) and NDBI is the (shortwave infrared band - near infrared band) / (shortwave infrared band + near infrared band). For more information, see this paper by Xiaoping Liu and colleagues. Earth Engine, however, has a function called normalizedDifference
which takes care of that pesky division—the normalization. We save these equations as functions so that they can be mapped over collections.
var addNDBI = function(image) {
var ndbi = image.normalizedDifference(['B6', 'B5']).rename('NDBI');
return image.addBands(ndbi);
};
var addNDVI = function(image) {
var ndvi = image.normalizedDifference(['B5', 'B4']).rename('NDVI');
return image.addBands(ndvi);
};
var addNDWI = function(image) {
var ndwi = image.normalizedDifference(['B3', 'B5']).rename('NDWI');
return image.addBands(ndwi);
};
This section simply maps over the image collection with the indexing functions before creating a qualityMosaic
. This flattens the collection into an image based on one variable, choosing the pixels that have the highest value across the stack—a mosaic
of this sort is one of extremes.
var ndbi = dataset.map(addNDBI);
var ndbiest = ndbi.qualityMosaic('NDBI').select('NDBI').visualize({min: 0, max: 1, palette: built});
var ndvi = dataset.map(addNDVI);
var ndviest = ndvi.qualityMosaic('NDVI').select('NDVI').visualize({min: -1, max: 1, palette: green});
var ndwi = dataset.map(addNDWI);
var ndwiest = ndwi.qualityMosaic('NDWI').select('NDWI').visualize({min: -1, max: 1, palette: water});
In order to convert this satellite imagery to temperature, we simply need to take the processed data and multiply it by the scaling variable—a constant that is available in the Earth Engine catalogue—before converting from Kelvin to Celsius. This function is then mapped over the image collection as in all other examples.
var addHeat = function(image){
var heat = image.select(['B10']).multiply(0.1).subtract(273.5).rename('HEAT');
return image.addBands(heat);
};
var temp = dataset.map(addHeat);
var heat = palettes.kovesi.diverging_rainbow_bgymr_45_85_c67[7];
var hottest = temp.qualityMosaic('HEAT').select('HEAT').visualize({min: 18, max: 45, palette: heat});
To create a menu of options, we simply need to aggregate all of these layers into an object.
var images = {
'temperature': hottest,
'reality': sharpest,
'natural': ndviest,
'built': ndbiest,
'water': ndwiest,
};
From here, we create two variables, one for the left side and the other for the right, along with two selectors—the buttons that will allow us to change the layer. Now that each selector is assigned to one side or the other, which is why they disappear if the user drags the curtain over them.
var leftMap = ui.Map();
leftMap.setControlVisibility(false);
var leftSelector = addLayerSelector(leftMap, 0, 'top-left');
var rightMap = ui.Map();
rightMap.setControlVisibility(false);
var rightSelector = addLayerSelector(rightMap, 1, 'top-right');
This function, wrapping another function, is what allows the user to change the layer; for an explanation of each line, see this example from the Earth Engine community. We add a selection widget that uses a menu of items—keys
from the saved layers—and updates the map when that widget changes.
function addLayerSelector(mapToChange, defaultValue, position) {
var label = ui.Label('Choose an image to visualize');
function updateMap(selection) {
mapToChange.layers().set(0, ui.Map.Layer(images[selection]));
}
var select = ui.Select({items: Object.keys(images), onChange: updateMap});
select.setValue(Object.keys(images)[defaultValue], true);
var controlPanel =
ui.Panel({widgets: [label, select], style: {position: position}});
mapToChange.add(controlPanel);
}
Finally, we set these two panels up as a splitPanel
with wipe set to true, which gives the curtain effect.
var splitPanel = ui.SplitPanel({
firstPanel: leftMap,
secondPanel: rightMap,
wipe: true,
style: {stretch: 'both'}
});
ui.root.widgets().reset([splitPanel]);
var linker = ui.Map.Linker([leftMap, rightMap]);
leftMap.setCenter(-75.16037464340661, 39.95143720941659, 12);
Finally, we launch the application in the applications menu of the code editor. The result shows a search bar that allows us to move from city to city with ease.
With the search bar, any planner or designer may select his or her city to conduct a more local analysis; this feature also encourages global analysis and comparison between cities. Note also that the the mosaic still shows seems from where we have combined images taken on different days, illustrating an issue with validity in the data.
We can augment this further with functions that allow for a spatio-temporal exploration. As a separate product, for simpler use, it is available at this link, but we will link it into the main application in the final product, able to be viewed below and used here.
To aid precision and to signal that this map accepts user input, we begin by changing the cursor style.
Map.style().set('cursor', 'crosshair');
From there, we set up and panel with its height and location before adding to the map.
var panel = ui.Panel();
panel.style().set({
width: '400px',
position: 'bottom-right'
});
Map.add(panel);
var intro = ui.Panel([
ui.Label({
value: 'Click to explore',
style: {fontSize: '14px', fontWeight: 'bold'}
})
]);
panel.add(intro)
The most advanced code comes from the reaction to a click. We need to create a variable—a feature collection—that stores both point clicked and a buffer around that point so that we have a comparison. We then instantiate a chart which uses a Reducer
to calculate the mean temperature of the comparison (and the point but the mean for that is obviously its value).
Map.onClick(function(coords) {
panel.clear();
var point = ee.FeatureCollection([
ee.Feature(
ee.Geometry.Point(coords.lon, coords.lat).buffer(10000), {label: 'Area Average'}),
ee.Feature(
ee.Geometry.Point(coords.lon, coords.lat), {label: 'Selected Zone'})
]);
var chart = ui.Chart.image.seriesByRegion(
temp, point, ee.Reducer.mean(), 'HEAT', 200, 'system:time_start', 'label')
.setChartType('LineChart')
.setOptions({
title: 'Temperature over time',
vAxis: {title: 'Temperature (Celcius)'},
series: {
0: {color: 'FD92FA'},
1: {color: '0035F9'}
}});
panel.add(chart);
});
After we add this chart to the panel, we have an interface that responds well and its intuitive to use.
In order to assess the heat of a point in relation to the larger region, this application allows the user to click on that area of interest, returning the trend line from that point and a 10 kilometer buffer. We can export the chart using the button in the top right corner.
This product allows any individual—be they a resident, the head of a community group, a planner or otherwise a member city government—to make comparisons between one point and the larger region. The buffer encompasses many small cities and constitutes a significant portion of big ones, indicating that it is a meaningful unit of analysis.
While intended as a demonstration of how easy it is find and export patterns using this application, the above images reveal an interesting trend: the line hugging the average is Center City while the others are North and South Philadelphia, meaning the part represents the whole for one neighborhood but not for the other two, which are consistently hotter.
Beginning to understand the contours of heat in a city requires measures of the built and natural environments that influence them. A popular technique is normalized difference, which takes the value of two bands and—of course—subtracts one from the before dividing by the total value of both. Using this simple technique can shed light on how lush, how built, or how wet a region is—NDVI (vegetation), NDBI (built, or built-up), and NDWI respectively. Compared to NDVI, the enhanced vegetation index (EVI) fails to meaningfully portray the character of the city and it thus falls out of the analysis. We can view these indices simultaneously by modifying an existing script to create a similar one that uses our measures as layers, available here.