diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..e960cea1
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1 @@
+articles/extreme-heat-explainer/index.md
\ No newline at end of file
diff --git a/articles/extreme-heat-explainer/components/bias-correction.js b/articles/extreme-heat-explainer/components/bias-correction.js
new file mode 100644
index 00000000..276e2d77
--- /dev/null
+++ b/articles/extreme-heat-explainer/components/bias-correction.js
@@ -0,0 +1,336 @@
+import { useState, useEffect } from 'react'
+import { Box, Flex } from 'theme-ui'
+import {
+ Chart,
+ Ticks,
+ TickLabels,
+ Axis,
+ AxisLabel,
+ Plot,
+ Line,
+ Label,
+ Grid,
+} from '@carbonplan/charts'
+import { Filter, FigureCaption, Colors } from '@carbonplan/components'
+
+import convert from './convert-units'
+import UnitConverter from './unit-converter'
+
+const CITY_LABELS = {
+ bangkok: 'Bangkok',
+ dubai: 'Dubai',
+ karachi: 'Karachi',
+ phoenix: 'Phoenix',
+}
+
+const APPLIED_LABELS = {
+ raw: 'before',
+ 'bias-corrected': 'after',
+}
+
+const x = Array(366)
+ .fill(0)
+ .map((d, i) => i)
+
+const yRange = {
+ c: {
+ Bangkok: [23, 36],
+ Dubai: [16, 37],
+ Karachi: [16, 36],
+ Phoenix: [10, 36],
+ },
+ f: {
+ Bangkok: [convert(23, 'f'), convert(36, 'f')],
+ Dubai: [convert(16, 'f'), convert(37, 'f')],
+ Karachi: [convert(16, 'f'), convert(36, 'f')],
+ Phoenix: [convert(10, 'f'), convert(36, 'f')],
+ },
+}
+
+const yTicks = {
+ c: {
+ Bangkok: [24, 26, 28, 30, 32, 34, 36],
+ Dubai: [16, 20, 24, 28, 32, 36],
+ Karachi: [16, 20, 24, 28, 32, 36],
+ Phoenix: [10, 14, 18, 22, 26, 30, 34],
+ },
+ f: {
+ Bangkok: [75, 80, 85, 90, 95],
+ Dubai: [65, 75, 85, 95],
+ Karachi: [65, 75, 85, 95],
+ Phoenix: [55, 65, 75, 85, 95],
+ },
+}
+
+const xTicks = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366]
+const xTickLabels = [14, 45, 74, 104, 135, 165, 195, 226, 257, 287, 318, 350]
+
+const BiasCorrection = () => {
+ const [city, setCity] = useState('Dubai')
+ const [applied, setApplied] = useState('before')
+ const [data, setData] = useState(null)
+ const [renderData, setRenderData] = useState(null)
+ const [units, setUnits] = useState('c')
+
+ useEffect(() => {
+ fetch(
+ 'https://carbonplan-climate-impacts.s3.us-west-2.amazonaws.com/extreme-heat/v1.0/outputs/web/explainer/bias_correction.json'
+ )
+ .then((response) => response.json())
+ .then((data) => setData(data))
+ }, [])
+
+ return (
+ <>
+
+
+
+ setCity(CITY_LABELS[Object.keys(obj).find((k) => obj[k])])
+ }
+ order={['dubai', 'karachi', 'bangkok', 'phoenix']}
+ sx={{ mb: [5] }}
+ />
+
+ setApplied(APPLIED_LABELS[Object.keys(obj).find((k) => obj[k])])
+ }
+ order={['raw', 'bias-corrected']}
+ sx={{ mb: [5] }}
+ />
+
+
+
+
+
+
+
+
+ i % 3 == 0)}
+ labels={[
+ 'JAN',
+ 'FEB',
+ 'MAR',
+ 'APR',
+ 'MAY',
+ 'JUN',
+ 'JUL',
+ 'AUG',
+ 'SEP',
+ 'OCT',
+ 'NOV',
+ 'DEC',
+ ].filter((d, i) => i % 3 == 0)}
+ sx={{ display: ['initial', 'none', 'none', 'none'] }}
+ />
+
+ Time of year
+
+ WBGT in the shade {units == 'c' ? 'ºC' : 'ºF'}
+
+
+
+
+
+
+ {data && (
+ <>
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'secondary'}
+ width={2}
+ />
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'purple'}
+ width={2}
+ sx={{
+ strokeDasharray: '5,5',
+ opacity: applied === 'before' ? 1 : 0.5,
+ transition: 'opacity 0.15s',
+ }}
+ />
+ {applied === 'after' && (
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'purple'}
+ width={2}
+ />
+ )}
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'pink'}
+ width={2}
+ sx={{
+ strokeDasharray: '5,5',
+ opacity: applied === 'before' ? 1 : 0.5,
+ transition: 'opacity 0.15s',
+ }}
+ />
+ {applied === 'after' && (
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'pink'}
+ width={2}
+ />
+ )}
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'red'}
+ width={2}
+ sx={{
+ strokeDasharray: '5,5',
+ opacity: applied === 'before' ? 1 : 0.5,
+ transition: 'opacity 0.15s',
+ }}
+ />
+ {applied === 'after' && (
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'red'}
+ width={2}
+ />
+ )}
+ >
+ )}
+
+
+
+
+
+ Timeseries of WBGT in the shade for four example cities, with and
+ without bias-correction (dashed lines show “raw” and solid lines show
+ “bias-corrected”). Results are averaged over years for a historical
+ period (1985-2014) and two future periods{' '}
+
+ (2020-2039
+ {' '}
+ and{' '}
+
+ 2040-2059)
+
+ , from a single example GCM (ACCESS-CM2). The gray reference line shows
+ data from UHE-Daily.
+
+ >
+ )
+}
+
+export default BiasCorrection
diff --git a/articles/extreme-heat-explainer/components/city-map.js b/articles/extreme-heat-explainer/components/city-map.js
new file mode 100644
index 00000000..75482143
--- /dev/null
+++ b/articles/extreme-heat-explainer/components/city-map.js
@@ -0,0 +1,187 @@
+import { useState, useEffect } from 'react'
+import { Box, Flex, useThemeUI } from 'theme-ui'
+import { Minimap, Path, Sphere, Points, Graticule } from '@carbonplan/minimaps'
+import { naturalEarth1 } from '@carbonplan/minimaps/projections'
+import { Filter, Colorbar, FigureCaption } from '@carbonplan/components'
+import { useThemedColormap } from '@carbonplan/colormaps'
+
+import UnitConverter from './unit-converter'
+
+const SCENARIO_LABELS = {
+ '1985-2014': 'historical',
+ '2020-2039': 'ssp245-2030',
+ '2040-2059': 'ssp245-2050',
+}
+
+const CityMap = () => {
+ const [scenario, setScenario] = useState('ssp245-2030')
+ const [radiation, setRadiation] = useState('sun')
+ const [threshold, setThreshold] = useState('30.5')
+ const [clim] = useState([0, 140])
+ const [data, setData] = useState(null)
+ const [position, setPosition] = useState(null)
+ const [units, setUnits] = useState('c')
+
+ const THRESHOLD_LABELS_C = {
+ '29.0': '29',
+ 30.5: '30.5',
+ '32.0': '32',
+ }
+
+ const THRESHOLD_LABELS_F = {
+ 84.2: '29',
+ 86.9: '30.5',
+ 89.6: '32',
+ }
+
+ useEffect(() => {
+ fetch(
+ 'https://carbonplan-climate-impacts.s3.us-west-2.amazonaws.com/extreme-heat/v1.0/outputs/web/explainer/maps.json'
+ )
+ .then((response) => response.json())
+ .then((data) => setData(data))
+ }, [])
+
+ useEffect(() => {
+ if (data) {
+ setPosition(data.lat.map((d, i) => [data.lon[i], data.lat[i]]))
+ }
+ }, [data])
+
+ const colormap = useThemedColormap('warm')
+
+ const { theme } = useThemeUI()
+
+ return (
+ <>
+
+
+
+
+ setScenario(
+ SCENARIO_LABELS[Object.keys(obj).find((k) => obj[k])]
+ )
+ }
+ sx={{}}
+ />
+
+ {units == 'f' && (
+
+ setThreshold(
+ THRESHOLD_LABELS_F[Object.keys(obj).find((k) => obj[k])]
+ )
+ }
+ sx={{ display: 'inline-block' }}
+ />
+ )}
+ {units == 'c' && (
+
+ setThreshold(
+ THRESHOLD_LABELS_C[Object.keys(obj).find((k) => obj[k])]
+ )
+ }
+ sx={{ display: 'inline-block' }}
+ />
+ )}
+
+ {units == 'c' ? 'ºC' : 'ºF'}
+
+
+
+ setRadiation(Object.keys(obj).find((k) => obj[k]))
+ }
+ />
+
+
+
+
+
+
+ {data && position && (
+
+ )}
+
+
+
+
+
+
+
+
+ Points show days over threshold for 15,300 urban centers. You can select
+ the time period (1985-2014, 2020-2039, or 2040-2059), the threshold
+ (29.0, 30.5, or 32.0 ºC), and whether to use WBGT in the shade or sun.
+ For each time period, results show medians over time periods to reflect
+ the “typical” heat in any given location. Results are medians across the
+ ensemble of GCMs.
+
+ >
+ )
+}
+
+export default CityMap
diff --git a/articles/extreme-heat-explainer/components/convert-units.js b/articles/extreme-heat-explainer/components/convert-units.js
new file mode 100644
index 00000000..d7a9c4c0
--- /dev/null
+++ b/articles/extreme-heat-explainer/components/convert-units.js
@@ -0,0 +1,14 @@
+const c2f = (c) => {
+ return c * (9 / 5) + 32
+}
+
+const convert = (value, units) => {
+ if (units == 'c') {
+ return value
+ }
+ if (units == 'f') {
+ return c2f(value)
+ }
+}
+
+export default convert
diff --git a/articles/extreme-heat-explainer/components/days-over.js b/articles/extreme-heat-explainer/components/days-over.js
new file mode 100644
index 00000000..f315c282
--- /dev/null
+++ b/articles/extreme-heat-explainer/components/days-over.js
@@ -0,0 +1,468 @@
+import { useState, useEffect } from 'react'
+import { Box, Flex } from 'theme-ui'
+import {
+ Chart,
+ Ticks,
+ TickLabels,
+ Axis,
+ AxisLabel,
+ Plot,
+ Line,
+ Label,
+ Grid,
+} from '@carbonplan/charts'
+import { Slider, Filter, FigureCaption, Colors } from '@carbonplan/components'
+import { format } from 'd3-format'
+
+import convert from './convert-units.js'
+import UnitConverter from './unit-converter.js'
+
+const cities = ['Bangkok', 'Dubai', 'Karachi', 'Phoenix']
+
+const CITY_LABELS = {
+ bangkok: 'Bangkok',
+ dubai: 'Dubai',
+ karachi: 'Karachi',
+ phoenix: 'Phoenix',
+}
+
+const SCENARIO_LABELS = {
+ '1985-2014': 'historical',
+ '2020-2039': 'ssp245-2030',
+ '2040-2059': 'ssp245-2050',
+}
+
+const f = format('.1f')
+
+const x = Array(366)
+ .fill(0)
+ .map((d, i) => i)
+
+const yRange = {
+ c: {
+ Bangkok: [20, 44],
+ Dubai: [14, 44],
+ Karachi: [14, 44],
+ Phoenix: [4, 42],
+ },
+ f: {
+ Bangkok: [convert(20, 'f'), convert(44, 'f')],
+ Dubai: [convert(14, 'f'), convert(44, 'f')],
+ Karachi: [convert(14, 'f'), convert(44, 'f')],
+ Phoenix: [convert(4, 'f'), convert(42, 'f')],
+ },
+}
+
+const yTicks = {
+ c: {
+ Bangkok: [22, 26, 30, 34, 38, 42],
+ Dubai: [16, 20, 24, 28, 32, 36, 40, 44],
+ Karachi: [16, 20, 24, 28, 32, 36, 40, 44],
+ Phoenix: [6, 10, 14, 18, 22, 26, 30, 34, 38, 42],
+ },
+ f: {
+ Bangkok: [70, 80, 90, 100, 110],
+ Dubai: [60, 70, 80, 90, 100, 110],
+ Karachi: [60, 70, 80, 90, 100, 110],
+ Phoenix: [40, 50, 60, 70, 80, 90, 100],
+ },
+}
+
+const years = {
+ historical: Array(30)
+ .fill(0)
+ .map((d, i) => i + 1985),
+ 'ssp245-2030': Array(20)
+ .fill(0)
+ .map((d, i) => i + 2020),
+ 'ssp245-2050': Array(20)
+ .fill(0)
+ .map((d, i) => i + 2040),
+}
+
+const xTicks = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 366]
+const xTickLabels = [14, 45, 74, 104, 135, 165, 195, 226, 257, 287, 318, 350]
+
+const DaysOver = () => {
+ const [city, setCity] = useState('Dubai')
+ const [scenario, setScenario] = useState('historical')
+ const [threshold, setThreshold] = useState(32)
+ const [data, setData] = useState(null)
+ const [means, setMeans] = useState(null)
+ const [daysOver, setDaysOver] = useState(null)
+ const [units, setUnits] = useState('c')
+
+ useEffect(() => {
+ fetch(
+ 'https://carbonplan-climate-impacts.s3.us-west-2.amazonaws.com/extreme-heat/v1.0/outputs/web/explainer/sun_shade_thresholds.json'
+ )
+ .then((response) => response.json())
+ .then((data) => setData(data))
+ }, [])
+
+ useEffect(() => {
+ if (data) {
+ const obj = {}
+ cities.forEach((d) => {
+ obj[d] = {
+ 'historical-shade': x.map(
+ (ix) =>
+ years['historical']
+ .map((iy) => data[d]['historical-shade'][iy][ix])
+ .filter((d) => d)
+ .reduce((a, b) => a + b, 0) /
+ years['historical']
+ .map((iy) => data[d]['historical-shade'][iy][ix])
+ .filter((d) => d).length
+ ),
+ 'historical-sun': x.map(
+ (ix) =>
+ years['historical']
+ .map((iy) => data[d]['historical-sun'][iy][ix])
+ .filter((d) => d)
+ .reduce((a, b) => a + b, 0) /
+ years['historical']
+ .map((iy) => data[d]['historical-sun'][iy][ix])
+ .filter((d) => d).length
+ ),
+ 'ssp245-2030-shade': x.map(
+ (ix) =>
+ years['ssp245-2030']
+ .map((iy) => data[d]['ssp245-2030-shade'][iy][ix])
+ .filter((d) => d)
+ .reduce((a, b) => a + b, 0) /
+ years['ssp245-2030']
+ .map((iy) => data[d]['ssp245-2030-shade'][iy][ix])
+ .filter((d) => d).length
+ ),
+ 'ssp245-2030-sun': x.map(
+ (ix) =>
+ years['ssp245-2030']
+ .map((iy) => data[d]['ssp245-2030-sun'][iy][ix])
+ .filter((d) => d)
+ .reduce((a, b) => a + b, 0) /
+ years['ssp245-2030']
+ .map((iy) => data[d]['ssp245-2030-sun'][iy][ix])
+ .filter((d) => d).length
+ ),
+ 'ssp245-2050-shade': x.map(
+ (ix) =>
+ years['ssp245-2050']
+ .map((iy) => data[d]['ssp245-2050-shade'][iy][ix])
+ .filter((d) => d)
+ .reduce((a, b) => a + b, 0) /
+ years['ssp245-2050']
+ .map((iy) => data[d]['ssp245-2050-shade'][iy][ix])
+ .filter((d) => d).length
+ ),
+ 'ssp245-2050-sun': x.map(
+ (ix) =>
+ years['ssp245-2050']
+ .map((iy) => data[d]['ssp245-2050-sun'][iy][ix])
+ .filter((d) => d)
+ .reduce((a, b) => a + b, 0) /
+ years['ssp245-2050']
+ .map((iy) => data[d]['ssp245-2050-sun'][iy][ix])
+ .filter((d) => d).length
+ ),
+ }
+ })
+ setMeans(obj)
+ }
+ }, [data])
+
+ useEffect(() => {
+ if (data) {
+ setDaysOver({
+ shade: parseInt(
+ years[scenario]
+ .map((d) =>
+ data[city][`${scenario}-shade`][d]
+ .map((i) => (i > threshold ? 1 : 0))
+ .reduce((a, b) => a + b, 0)
+ )
+ .reduce((a, b) => a + b, 0) / years[scenario].length
+ ),
+ sun: parseInt(
+ years[scenario]
+ .map((d) =>
+ data[city][`${scenario}-sun`][d]
+ .map((i) => (i > threshold ? 1 : 0))
+ .reduce((a, b) => a + b, 0)
+ )
+ .reduce((a, b) => a + b, 0) / years[scenario].length
+ ),
+ })
+ }
+ }, [data, city, scenario, threshold])
+
+ return (
+ <>
+
+
+
+ setCity(CITY_LABELS[Object.keys(obj).find((k) => obj[k])])
+ }
+ order={['dubai', 'karachi', 'bangkok', 'phoenix']}
+ sx={{ mb: [5] }}
+ />
+
+ setScenario(SCENARIO_LABELS[Object.keys(obj).find((k) => obj[k])])
+ }
+ sx={{ mb: [5] }}
+ />
+
+
+ `0 0 0 4px ${colors.secondary}`,
+ },
+ '&::-moz-range-thumb': {
+ boxShadow: ({ colors }) => `0 0 0 4px ${colors.secondary}`,
+ },
+ },
+ }}
+ onChange={(e) => setThreshold(Number(e.target.value))}
+ value={threshold}
+ step={0.1}
+ min={yRange['c'][city][0]}
+ max={yRange['c'][city][1]}
+ />
+
+
+
+
+
+
+
+
+ i % 3 == 0)}
+ labels={[
+ 'JAN',
+ 'FEB',
+ 'MAR',
+ 'APR',
+ 'MAY',
+ 'JUN',
+ 'JUL',
+ 'AUG',
+ 'SEP',
+ 'OCT',
+ 'NOV',
+ 'DEC',
+ ].filter((d, i) => i % 3 == 0)}
+ sx={{ display: ['initial', 'none', 'none', 'none'] }}
+ />
+
+ Time of year
+ WBGT {units == 'c' ? 'ºC' : 'ºF'}
+ {daysOver && (
+
+ )}
+ {daysOver && (
+
+ )}
+
+
+
+ {means && (
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'orange'}
+ width={2}
+ />
+ )}
+ {means && (
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'yellow'}
+ width={2}
+ />
+ )}
+ {data &&
+ years[scenario].map((d, i) => {
+ return (
+
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'orange'}
+ width={0.25}
+ sx={{ opacity: 0.4 }}
+ />
+ [
+ x[i],
+ convert(d, units),
+ ])}
+ color={'yellow'}
+ width={0.25}
+ sx={{ opacity: 0.4 }}
+ />
+
+ )
+ })}
+
+
+
+
+
+ Timeseries of WBGT in the shade and in
+ the sun for four example cities. Thin
+ lines show individual years and thick lines show an average. Results are
+ shown for three time periods, one historical (1985-2014) and two future
+ (2020-2039 and 2040-2059), from a single example GCM (ACCESS-CM2).
+ Moving the horizontal line changes the threshold; numbers show the count
+ of days exceeding the specified threshold, averaged across years.{' '}
+
+
+ >
+ )
+}
+
+export default DaysOver
diff --git a/articles/extreme-heat-explainer/components/heat-calculator.js b/articles/extreme-heat-explainer/components/heat-calculator.js
new file mode 100644
index 00000000..4a0fb781
--- /dev/null
+++ b/articles/extreme-heat-explainer/components/heat-calculator.js
@@ -0,0 +1,237 @@
+import { useState, useEffect } from 'react'
+import { Box } from 'theme-ui'
+import { Row, Column, Slider, FigureCaption } from '@carbonplan/components'
+import { format } from 'd3-format'
+
+import convert from './convert-units'
+import UnitConverter from './unit-converter'
+
+const wbgtShade = (tc, rh) => {
+ const va = 0.5
+ const t_k = tc + 273.15
+ const mrt = t_k
+ const wbt =
+ tc * Math.atan(0.151977 * Math.sqrt(rh + 8.313659)) +
+ Math.atan(tc + rh) -
+ Math.atan(rh - 1.676331) +
+ 0.00391838 * rh ** (3 / 2) * Math.atan(0.023101 * rh) -
+ 4.686035
+ const f = (1.1e8 * Math.pow(va, 0.6)) / (0.98 * Math.pow(0.15, 0.4))
+ let a = f / 2
+ const b = -f * t_k - Math.pow(mrt, 4)
+ const rt1 = Math.pow(3, 1 / 3)
+ const rt2 =
+ Math.sqrt(3) * Math.sqrt(27 * Math.pow(a, 4) - 16 * Math.pow(b, 3)) +
+ 9 * Math.pow(a, 2)
+ const rt3 = 2 * 2 ** (2 / 3) * b
+ a = Math.max(a, 0)
+ const bgt_quartic =
+ (-1 / 2) *
+ Math.sqrt(
+ rt3 / (rt1 * Math.pow(rt2, 1 / 3)) +
+ (Math.pow(2, 1 / 3) * Math.pow(rt2, 1 / 3)) / Math.pow(3, 2 / 3)
+ ) +
+ (1 / 2) *
+ Math.sqrt(
+ (4 * a) /
+ Math.sqrt(
+ rt3 / (rt1 * rt2 ** (1 / 3)) +
+ (Math.pow(2, 1 / 3) * Math.pow(rt2, 1 / 3)) / Math.pow(3, 2 / 3)
+ ) -
+ (Math.pow(2, 1 / 3) * Math.pow(rt2, 1 / 3)) / Math.pow(3, 2 / 3) -
+ rt3 / (rt1 * Math.pow(rt2, 1 / 3))
+ )
+
+ const bgt_c = bgt_quartic - 273.15
+
+ return 0.7 * wbt + 0.2 * bgt_c + 0.1 * tc
+}
+
+const sunAdjustment = (radiation, wind) => {
+ return 2.1564 + 0.0054 * radiation - 1.0424 * wind
+}
+
+const sx = {
+ label: {
+ fontFamily: 'heading',
+ letterSpacing: 'smallcaps',
+ textTransform: 'uppercase',
+ fontSize: [2, 2, 2, 3],
+ pt: [2, 2, 2, 3],
+ mt: [1],
+ pb: [0],
+ },
+ valueBig: {
+ fontFamily: 'mono',
+ letterSpacing: 'mono',
+ textTransform: 'uppercase',
+ color: 'yellow',
+ fontSize: [6, 7, 7, 8],
+ },
+ valueMedium: {
+ fontFamily: 'mono',
+ letterSpacing: 'mono',
+ textTransform: 'uppercase',
+ color: 'yellow',
+ fontSize: [4, 5, 5, 6],
+ },
+ valueSmall: {
+ fontFamily: 'mono',
+ letterSpacing: 'mono',
+ textTransform: 'uppercase',
+ color: 'green',
+ fontSize: [3, 3, 3, 4],
+ },
+ unit: {
+ textTransform: 'none',
+ },
+ group: {
+ borderStyle: 'solid',
+ borderWidth: '0px',
+ borderTopWidth: '1px',
+ borderColor: 'muted',
+ mb: [4, 4, 4, 5],
+ },
+}
+
+const ranges = {
+ temperature: [20, 55],
+ humidity: [0, 99],
+ radiation: [300, 900],
+ wind: [0.5, 3],
+}
+
+const f = {
+ wbgt: format('02d'),
+ temperature: format('.1f'),
+ humidity: format('02d'),
+ radiation: format('03d'),
+ wind: format('.1f'),
+}
+
+const HeatCalculator = () => {
+ const [temperature, setTemperature] = useState(32)
+ const [humidity, setHumidity] = useState(60)
+ const [radiation, setRadiation] = useState(300)
+ const [wind, setWind] = useState(2)
+ const [result, setResult] = useState({ shade: 0, sun: 0 })
+ const [units, setUnits] = useState('c')
+
+ useEffect(() => {
+ const shade = wbgtShade(temperature, humidity)
+ const adjustment = sunAdjustment(radiation, wind)
+ setResult({
+ shade: shade,
+ sun: shade + adjustment,
+ })
+ }, [temperature, humidity, radiation, wind])
+
+ return (
+ <>
+
+
+
+
+ WBGT in the shade
+
+ {f.wbgt(convert(result.shade, units))}
+
+ {units == 'c' ? 'ºC' : 'ºF'}
+
+
+
+
+
+
+ WBGT in the sun
+
+ {f.wbgt(convert(result.sun, units))}
+
+ {units == 'c' ? 'ºC' : 'ºF'}
+
+
+
+
+
+
+
+
+ temperature
+
+ {f.temperature(convert(temperature, units))}
+
+ {units == 'c' ? 'ºC' : 'ºF'}
+ {' '}
+
+ setTemperature(Number(e.target.value))}
+ value={temperature}
+ step={0.1}
+ min={ranges['temperature'][0]}
+ max={ranges['temperature'][1]}
+ />
+
+
+ relative humidity
+
+ {f.humidity(humidity)}%{' '}
+
+ setHumidity(Number(e.target.value))}
+ value={humidity}
+ step={1}
+ min={ranges['humidity'][0]}
+ max={ranges['humidity'][1]}
+ />
+
+
+
+
+ solar radiation
+
+ {f.radiation(radiation)}{' '}
+
+ W/m²
+ {' '}
+
+ setRadiation(Number(e.target.value))}
+ value={radiation}
+ step={5}
+ min={ranges['radiation'][0]}
+ max={ranges['radiation'][1]}
+ />
+
+
+ wind speed
+
+ {f.wind(wind)}{' '}
+
+ m/s
+ {' '}
+
+ setWind(Number(e.target.value))}
+ value={wind}
+ step={0.1}
+ min={ranges['wind'][0]}
+ max={ranges['wind'][1]}
+ />
+
+
+
+
+
+ A calculator for wet-bulb globe temperature that includes the same
+ assumptions and approximations as used in our analysis.{' '}
+ Changing the four
+ input variables (temperature, solar radiation, relative humidity, and
+ wind speed) determines WBGT in both the sun and shade. The shade
+ estimate assumes a fixed wind speed of 0.5 m/s and ignores radiation, so
+ changing those parameters doesn’t affect that value.
+
+ >
+ )
+}
+
+export default HeatCalculator
diff --git a/articles/extreme-heat-explainer/components/small.js b/articles/extreme-heat-explainer/components/small.js
new file mode 100644
index 00000000..4eb3e4e2
--- /dev/null
+++ b/articles/extreme-heat-explainer/components/small.js
@@ -0,0 +1,20 @@
+import { Box } from 'theme-ui'
+
+const Small = ({ children }) => {
+ return (
+
+ {children}
+
+ )
+}
+
+export default Small
diff --git a/articles/extreme-heat-explainer/components/unit-converter.js b/articles/extreme-heat-explainer/components/unit-converter.js
new file mode 100644
index 00000000..fcc98d3d
--- /dev/null
+++ b/articles/extreme-heat-explainer/components/unit-converter.js
@@ -0,0 +1,41 @@
+import { Box } from 'theme-ui'
+
+const UnitConverter = ({ units, setUnits }) => {
+ return (
+ <>
+ Display in degrees{' '}
+ setUnits('c')}
+ >
+ Celsius
+ {' '}
+ or{' '}
+ setUnits('f')}
+ >
+ Fahrenheit
+
+ .
+ >
+ )
+}
+
+export default UnitConverter
diff --git a/articles/extreme-heat-explainer/index.md b/articles/extreme-heat-explainer/index.md
new file mode 100644
index 00000000..fffe0848
--- /dev/null
+++ b/articles/extreme-heat-explainer/index.md
@@ -0,0 +1,250 @@
+---
+version: 1.0.0
+title: Modeling extreme heat in a changing climate
+authors:
+ - Oriana Chegwidden
+ - Jeremy Freeman
+date: 09-05-2023
+summary: We developed a new dataset that models the impacts of humid heat with fully public data and code, describing risks now and projecting them in the future.
+quickLook: New data and open methods for modeling humid heat
+card: extreme-heat-explainer
+background: articles/021/sweat
+icon: articles/021/sweat-small
+links:
+ - label: Code and data
+ href: https://github.com/carbonplan/extreme-heat
+ - label: Press coverage
+ href: TK
+components:
+ - name: Small
+ src: ./components/small.js
+ - name: HeatCalculator
+ src: ./components/heat-calculator.js
+ - name: BiasCorrection
+ src: ./components/bias-correction.js
+ - name: DaysOver
+ src: ./components/days-over.js
+ - name: CityMap
+ src: ./components/city-map.js
+color: yellow
+---
+
+If it wasn’t clear before the summer of 2023 that climate change is raising temperatures, it better be clear now. This past July broke the record for the [hottest month ever measured](https://www.nasa.gov/press-release/nasa-clocks-july-2023-as-hottest-month-on-record-ever-since-1880). And that was before a life-threatening heat dome [boiled large swaths of the US Midwest](https://www.nbcnews.com/science/science-news/extreme-heat-dome-midwest-temperatures-hot-weather-rcna101153) in August.
+
+As extreme heat becomes commonplace, people need to know when and where it will occur. But modeling and predicting heat is complicated. Temperature on its own doesn’t tell the whole story. For example, when it’s hot you sweat, and the water evaporates to cool you down. But if there’s too much moisture in the air (high humidity), that cooling effect can stop working. The amounts of sunlight and wind also matter (the former heats you up, and the latter cools you down).While wind generally has a cooling effect, at very high temperatures, wind can increase the heat transfer to a body, as opposed to away from it. These parameters vary from minute to minute. And they vary geographically, often at fine spatial scales, especially within cities where human activity and infrastructure trap heat.
+
+We want to help planners and the public navigate this complicated science by producing actionable data that will make it easier to understand the risks. As part of a [collaborative project with _The Washington Post_](TK), we developed a new dataset modeling extreme heat under a changing climate. We built on a foundation of academic work, aiming to combine the best pieces of several existing methods and datasets to produce something new, albeit with several assumptions and approximations. And we’re making all of our data, methods, and code [fully public](https://github.com/carbonplan/extreme-heat).
+
+In this explainer we describe how and why we developed the new dataset, and provide details on our methods, assumptions, and results.
+
+## Choosing a metric
+
+Many different metrics capture how we experience the combined effects of temperature, humidity, and other factors, and [they all say something slightly different](https://fivethirtyeight.com/features/heat-index-temperature/) about the risks. Among them, the wet-bulb globe temperature (WBGT) is probably the most widely used, especially in international contexts. Originally developed under the US military to monitor heat illness, WBGT combines four key variables — temperature, humidity, solar radiation,Solar radiation captures the warming effects of sunlight. It typically ranges from 300 W/m² (noon on a sunny day in the winter) to 600 W/m² (noon on a cloudy day in the summer) to 900 W/m² (noon on a sunny day in the summer). and wind — into a single number. Safety thresholds for that number have been developed in the context of athletic training, strenuous work (particularly outdoors), and other activities.
+
+While the units of WBGT are familiar (degrees Celsius or Fahrenheit), the scale may not be, with even moderately high values still posing substantial risk. The [Occupational Safety and Health Administration](https://www.osha.gov/heat-exposure/hazards) (OSHA), for example, says that strenuous work at a WBGT of 25 ºC (77 ºF) or higher poses a risk of heat-related illness. When WBGT is over 32 ºC (89.6 ºF) a short period of outdoor work, even by a healthy individual, risks illness or death.In 2021 President Biden [instructed OSHA](https://www.whitehouse.gov/briefing-room/statements-releases/2021/09/20/fact-sheet-biden-administration-mobilizes-to-protect-workers-and-communities-from-extreme-heat) to draft an extreme heat standard, but it is [taking years to develop](https://www.washingtonpost.com/business/2023/07/14/heat-workers-osha-protections/). The calculator below (Figure 1) can help build intuition for how different variables together determine WBGT. Use it to confirm, for example, that with low humidity (10%), light wind (1 m/s), and moderate radiation (500 W/m²), a WBGT of 32 ºC (89.6 ºF) requires a blazingly hot temperature of 45 ºC (113 ºF). Despite the unintuitive scale, WBGT accounts for more physical variables than alternatives like the [heat index](https://www.weather.gov/ama/heatindex) or [Humidex](https://www.ccohs.ca/oshanswers/phys_agents/humidex.html), and therefore provides a more detailed window into the risks of heat stress. There is much discussion in the public health literature about the [relative importance](https://ehp.niehs.nih.gov/doi/10.1289/EHP11807) of humidity in determining health outcomes. We hope that our research can help support that work.
+
+
+
+Measuring WBGT directly requires [specialized instrumentation](https://en.wikipedia.org/wiki/Wet-bulb_globe_temperature#/media/File:Bio-environmental_prepares_for_summer_110411-F-BQ904-001.jpg), but approximations to WBGT can be derived from meteorological or climate model data. The most complete, physically realistic calculations require data with high temporal resolution — ideally hourly or at least sub-daily — for several climate variables. Various approximations are possible, as we’ll discuss more below, and their efficacy has been systematically compared.
+
+## Assumptions and approximations
+
+We wanted to work at a high spatial resolution to capture fine-scale geographic details. For that reason, we turned to a [downscaled](https://carbonplan.org/research/cmip6-downscaling-explainer) dataset for our historical and future climate simulations, specifically the [Global Daily Downscaled CMIP6 Projections from NASA Earth Exchange](https://www.nccs.nasa.gov/services/data-collections/land-based-products/nex-gddp-cmip6) (hereafter “NEX-GDDP”). This dataset is based on the latest generation of climate models, and covers land areas over the globe at ~25 km resolution with projections for multiple emissions scenarios and ~30 global climate models (GCMs).Although we recently generated a collection of [downscaled CMIP6 datasets](https://carbonplan.org/research/cmip6-donwscaling) ourselves, we did not use those datasets here because they do not include some of the necessary variables required for modeling WBGT
+
+While it has a high spatial resolution, the NEX-GDDP climate projections are only available at a daily time step, and only include a subset of the variables required for estimating WBGT. Thus, we had to make three key simplifying assumptions.
+
+First, as in some other literature, we started by calculating “WBGT in the shade” by assuming mean radiant temperature is equal to air temperature, neglecting any solar radiation components and using a fixed 0.5 m/s wind speed. This “in the shade” version underestimates the full impacts of extreme heat, especially for people who cannot access shade, but it greatly simplifies the calculations. To capture those impacts, we developed a post-hoc adjustment to approximate conditions in the sun (see below).
+
+Second, we assumed that maximum daily WBGT occurs at the same time as maximum daily temperature, which is built into the Stull formulation that we used for our calculations. In past work this assumption was found to hold reasonably well when calculating a different humid heat metric, maximum daily heat index, at a variety of weather stations across the continental United States.
+
+Third, we used specific humidity, daily maximum temperature, and pressure to approximate minimum daily relative humidity, and assumed that it co-occurs with maximum temperature. This avoids what would otherwise be a substantial bias were we to combine mean daily relative humidity, as is available directly in the NEX-GDDP product, with daily maximum temperature.
+
+## Incorporating heat island effects
+
+Climate models on their own do not account for the [“urban heat island” effect](https://www.epa.gov/heatislands), whereby human activity and infrastructure in urban areas increases temperatures and traps heat for longer compared to outlying areas. To incorporate these effects and other fine-scale features not present in climate model data even after downscaling, we used a bias-correction procedure. This procedure resolves differences between the climate model data and data from a more detailed reference historical time series, and then makes sure that future projections also reflect that level of detail.
+
+As a target for bias-correction, we used the UHE-Daily dataset. The UHE-Daily dataset includes city-level daily estimates of maximum WBGT in the shade, based on CHIRTS-Daily, a ~5 km gridded product which is a combination of reanalysis, station observations, and infrared satellite-based temperature.Note that the UHE-Daily WBGT in the shade values were estimated via a formulation from Bernard and Iheanacho (2015) that calculates it from the heat index; while technically a different calculation, it essentially estimates the same quantity. To perform the bias-correction, we used the quantile delta method as implemented in the [xclim](https://xclim.readthedocs.io/en/stable/) package. Across all cities, the bias-correction was robust (see Extended Methods); Figure 2 shows data from four example cities with and without bias-correction.
+
+
+
+## Adjusting for the sun
+
+As mentioned above, due to limitations in data availability and temporal resolution, we calculated an “in the shade” version of WBGT that does not account for solar radiation and wind. Some previous studies have approximated WBGT in the sun by adding a constant (e.g., 3 ºC), but this adjustment is an oversimplification.
+
+Although we could not do a complete calculation of WBGT in the sun, we were able to leverage existing comparisons to make a more fine-grained adjustment. Helpfully, Kong and Huber systematically compared the differences between WBGT in the sun and shade across a range of weather conditions. We used their results to develop a simple linear model that takes as input daily surface downwelling shortwave radiation (hereafter “solar radiation”) and wind speed and returns an adjustment. For example, given a solar radiation of 500 W/m² and a wind speed of 2 m/s, the model returns an adjustment of +2.8 ºC (+5.0 ºF).
+
+We obtained daily estimates of solar radiation and wind from the same downscaled climate data used in the rest of our analysis, and we used them as inputs into the linear model to produce a time- and space-varying adjustment. In practice, the adjustment ranged from +1.1 to +4.5 ºC (+2.0 to +8.1 ºF), depending on the location and the day.Range reported as 1st to 99th percentile across locations and days of the adjustments made within the month of the year with the highest WBGT in the shade for each location.
+
+
+
+Our estimated values for WBGT in the sun are always higher, so for any given threshold temperature, more days a year will exceed that threshold when considering values in the sun compared to values in the shade. Using Figure 3, you can build intuition for the difference by exploring how changing the threshold influences the exceedances for both the shade and sun. It's particularly concerning when WBGT exceeds a dangerously high threshold even in the shade, because finding shade is often the first line of defense in escaping dangerous heat.
+
+## Comparison to other results
+
+To evaluate and validate our analytical choices, we compared our results to two other datasets that also estimated WBGT globally during a historical period.
+
+First we compared our estimates to those reported by Kong and Huber, which used more detailed WBGT calculations based on hourly data, though that data was too coarse to account for heat island effects. Our results were broadly similar in both spatial pattern and magnitude. Quantitatively, across all cities in our dataset, our estimates were on average +0.80 ºC higher (50% confidence interval: +0.07 to +1.62). The differences likely reflect in part the assumptions and approximations we made in calculating WBGT compared to a more complete, physically realistic implementation. They may also reflect the fact that we bias-corrected to UHE-Daily, whereas the Kong and Huber estimates were based on ERA5, which has been shown to underestimate temperature compared to the data underlying UHE-Daily.
+
+We also compared our results to those from the [Climate Change Heat Impact and Prevention (CHIP)](http://climatechip.org/your-area-climate-data) project, which estimates WBGT using [CRU observational data](https://crudata.uea.ac.uk/cru/data/hrg/index.htm#current), and includes estimates both in the shade and in the sun. Across 18 example cities, our estimates in the shade were on average +0.21 ºC higher (50% confidence interval: -0.45 to +0.49) and our estimates in the sun were higher by +0.56 ºC (50% confidence interval: -0.18 to +1.25). Here as well, the differences could be due both to our different assumptions as well as the use of UHE-Daily as a reference dataset.
+
+It’s reassuring that these results are all generally similar within ±1 ºC, despite substantial differences in methods and approximations. In many locations, we think it’s likely that our estimates are slightly higher because they appropriately reflect the role of urban heat islands as captured by the CHIRTS-Daily and UHE-Daily products, but further work will be required to tease these factors apart.
+
+## What we’re releasing
+
+We applied the above analysis to projections from 26 global climate models (GCMs) for both a historical period (1985-2014) and two future periods (2020-2039 and 2040-2059) under one moderate emissions scenario (SSP2-4.5).By only using one future scenario, our analysis does not account for any activities that might either more effectively mitigate, or further aggravate, human-caused climate change. Also, our analysis maintains historical urban heat island effects into the future, and so does not account for any adaptive measures that cities or neighborhoods might implement to reduce heat island effects, such as improving tree cover. It would have been computationally prohibitive to run the analysis on a full 5 km raster grid, so we instead extracted time series of all variables from within ~15,300 urban centers (primarily derived from the [Global Human Settlement](https://ghsl.jrc.ec.europa.eu/datasets.php) project) and also from within ~24,000 climatically-similar geographic regions covering the globe from the Climate Impact Lab.
+
+The map in Figure 4 shows the urban centers. One immediately clear, and worrisome, trend is that historically only South Asia and the Middle East experienced many days exceeding 32 ºC (89.6 ºF) in the sun, but by 2050 that level of heat will become even more frequent there, and increasingly commonplace elsewhere.
+
+
+
+We’ve made all the input and output datasets public, alongside documented open source code that implements the analysis. For convenience, we’ve made the results available in multiple formats with varying levels of summarization, including: full time series of WBGT in the shade and in the sun over historical and future periods for each GCM; medians across climate models; and summary tables showing, for each city, the number of days likely to exceed a range of different WBGT thresholds.Our use of medians across an ensemble of climate models makes our results more robust to the
+“[hot model problem](https://www.nature.com/articles/d41586-022-01192-2)” See our [GitHub](https://github.com/carbonplan/extreme-heat) repository for links to all datasets and documented code.
+
+## Looking forward
+
+There’s an active community working on this problem — both on how to estimate heat from climate data, and how to understand and plan for the impacts extreme heat will have on public health and societal infrastructure. This new dataset is just one contribution to that broad effort.
+
+The ideal heat dataset would have high resolution in both space and time, capturing fine-scale geographic detail and heat island effects, and also fully accounting for all variables on a sub-daily time scale. As discussed above, we made advances on spatial resolution, but as a trade off, we relied on daily data and had to make several assumptions and approximations. New downscaled datasets with more input variables, combined with further computational efficiency and scale, could help avoid some of these approximations, and improve resolution on both dimensions. We would be excited to pursue future work in these directions, ideally in collaboration with others.
+
+Even estimating WBGT at high resolution doesn’t tell the full story about the risks of extreme heat to human health. Factors like access to cooling, ability to shift outdoor work hours to avoid heat, cardiac health, age, and clothing all affect the experience of heat — and all vary by socioeconomic status — so the same WBGT value will be experienced differently by different people. Fully characterizing these risks remains an active area of work at the intersection of climate science and public health.
+
+We hope this explainer revealed some of the complexity in modeling extreme heat, and how [choices around datasets and analyses](https://carbonplan.org/blog/climate-risk-metadata) matter. As this kind of data increasingly becomes the basis for decisions — which could impact millions of people’s lives and trillions of dollars — we need full transparency to enable public accountability. Alongside the dataset itself, we hope this work provides an example for how to model climate impacts in the open.
+
+
+
+Thanks to Drew Shindell, Matthew Huber, Luke Parsons, Joe Hamman, Zeke Hausfather, Robert Rohde, Jared Rennie, Shruti Nath, Tord Kjellstrom, Shouro Dasgupta, and Benjamin Zaitchik for providing informal review and feedback on the methods used to approximate wet-bulb globe temperatures. Thanks to Joshua Batson for figure suggestions. Thanks to Cascade Tuholske and Pete Peterson for providing access to the gridded fields from Tuholske et al. (2021). Thanks to Qinqin Kong and Matthew Huber for providing access to data from Kong and Huber (2022) for validation. Climate scenarios used were from the NEX-GDDP-CMIP6 dataset, prepared by the Climate Analytics Group and NASA Ames Research Center using the NASA Earth Exchange and distributed by the NASA Center for Climate Simulation (NCCS).
+
+
+
+
+
+Oriana led the research and implemented the analysis, which was also shaped by _Washington Post_ climate journalist Niko Kommenda and colleagues. Oriana and Jeremy together developed the methods, reviewed and analyzed the data, designed the figures, and wrote this article.
+
+Please cite this web article as:
+
+O Chegwidden & J Freeman (2023) “Modeling extreme heat in a changing climate” CarbonPlan [https://carbonplan.org/research/modeling-extreme-heat](https://carbonplan.org/research/extreme-heat-explainer)
+
+
+
+
+
+CarbonPlan received [no specific financial support](https://carbonplan.org/funding) for this work.
+
+Article text and figures made available under a [CC-BY 4.0 International license](https://creativecommons.org/licenses/by/4.0/).
+
+
+
+## Extended methods
+
+Here we provide further details on some of our methods.
+
+_Bias-correction implementation._ We constructed a unique bias-correction
+ model for every region of analysis and GCM, training each model on the
+ 1985-2014 period (“historical”) and then applying each model to the full
+ 1985-2059 time series. Bias-correction forces modeled projections to inherit
+ the distribution of the UHE-Daily dataset, including both desirable features
+ (e.g., that it captures urban heat island effects and fine-scale geographic
+ features) and any limitations (e.g., that it also estimates “WBGT in the
+ shade” at a daily time resolution, and that its derivation relies on dew point
+ and surface pressure estimates from different reanalysis products). We used a
+ rolling monthly QDM approach to capture the variance in biases and climate
+ sensitivities throughout the year, and avoid artifacts due to month
+ boundaries. We used 100 quantiles in developing each bias-correction model to
+ better account for changes at the extremes.
+
+_Bias-correction validation._ To quantify the stability of the
+ bias-correction, for a sample GCM (ACCESS-CM2) we compared the change in
+ annual maximum WBGT — future (2020-2039) minus historical (1984-2015) —
+ between the raw WBGT estimates and the bias-corrected versions. In other
+ words, we compared the climate change signal before and after bias-correction.
+ Bias-correction has the potential to significantly disrupt or distort these
+ change signals, given its flexibility in fitting a different model for each
+ city and potential for extrapolation near the tails of the distribution, but
+ change signals were stable for the 80th, 90th, and 95th percentiles, and for
+ the annual maximum (coefficients of determination when using the raw change to
+ predict the bias-corrected change were 0.87, 0.87, 0.84, 0.45). This stability
+ further indicates that our results are robust to artifacts potentially arising
+ from the application of bias-correction to quantities derived from the
+ NEX-GDDP climate dataset which has also been bias-corrected.
+
+_Sun adjustment model._ We leveraged Figure S12 from Kong and Huber (2022),
+ which reports the difference between shade and sun WBGT across a range of
+ weather conditions. From this figure, we excluded the leftmost (0 W/m²)
+ and top (0.13 m/s) bins, and within each of the four remaining bins
+ of solar radiation and wind speed we selected the contour corresponding to an
+ air temperature of 35 ºC (95 ºF) and 50% relative humidity. We then
+ used those 16 points to develop a linear model that produces an
+ adjustment for any given solar radiation and wind speed value. For example,
+ given a solar radiation of 500 W/m² and a wind speed of 2 m/s, the
+ model returns an adjustment of +2.5 ºC (+4.5 ºF). Input values
+ beyond the ranges of 300-900 W/m² or 0.5-3 m/s were clipped to the
+ minimum and maximum adjustment.
+
+_Sun adjustment application._ We accessed daily estimates of solar radiation
+ and wind from the same downscaled GCM data used in the rest of our analysis.
+ For wind speed, we used the daily average wind speed to represent the wind
+ speed at the time of maximum WBGT. For solar radiation, we first disaggregated
+ the average daily solar radiation data into hourly data using the
+ [metsim](https://metsim.readthedocs.io/en/develop/) package to identify a
+ daily maximum. Note that the disaggregation process assumes that whatever
+ cloud cover has reduced the solar radiation on each day is constant throughout
+ the day; in other words, we do not capture any sub-daily variation in cloud
+ cover. To account for the fact that temperatures typically peak a few hours
+ after midday's maximum sunlight,
+ we scaled the maximum solar radiation by 75% to get
+ a daily value. Finally, we used these daily values of solar radiation and wind
+ speed as inputs into the linear model described above to produce a time- and space-varying
+ adjustment for each day and location. We added that adjustment to the WBGT in the
+ shade to yield a WBGT in the sun. Note that this sun adjustment uses coarser (~25 km)
+ data compared to the finer-scale (~5 km) UHE-Daily data that we used for bias-correction.
+ While solar radiation values would likely be similar at both scales, we acknowledge
+ that finer-scale wind data could capture variations in roughness that are not captured
+ as well at ~25 km.
+
+_Pressure calculations._ As in Raymond et al (2020) we assumed standard
+ pressure, though we adjusted it according to elevation
+ and mean daily temperature following Chavaillaz et al
+ (2019).
+
+_Calendars._ Many GCMs do not use the standard Gregorian calendar (e.g., leap
+ years) for their modeling. Instead, some follow a 365-day calendar, ignoring
+ leap years, and some use a 360 day calendar, which is reflected in the
+ NEX-GDDP-CMIP6 downscaled projections. For all of our analysis, we converted
+ projections from any non-standard calendar to the Gregorian calendar using the
+ `convert_calendar` method in `xarray`, and linearly interpolated to “gap fill”
+ the additional days. This gap-filling was performed after spatial aggregation.
+
+_Regional analysis._ While our primary analysis focused on cities, we also
+ applied the same analysis to data averaged within
+ ~24,000 climatically-similar geographic regions covering the globe.
+ Within these regions, we weighted the averaging by 2030 population
+ estimates
+ to emphasize inhabited areas, and we excluded urban
+ centers so we could separately estimate impacts on urban and non-urban populations.
+ For computational feasibility, we performed spatial averaging on the input data
+ before all other calculations (including bias-correction) rather than after; especially
+ in larger regions with dispersed populations this order of operations may cause
+ statistical biases, but those biases are likely to be small given that the regions
+ were defined to be climatically uniform.
+
+_Validation implementation_. For comparisons with Kong and Huber (2022), we
+ used one sample GCM (CanESM5) for the historical period, and averaged our data
+ to exactly match their results, which reported the average daily maximum WBGT
+ in the sun within the hottest month for every location. Note that the time
+ ranges between the two analyses differ slightly: our historical data span
+ 1984-2014 whereas the data from Kong and Huber (2022) span 1990-2019. For
+ comparisons with the [Climate Change Heat Impact and Prevention
+ (CHIP)](http://climatechip.org/your-area-climate-data) website, the full
+ dataset was not available, so we manually extracted monthly average time
+ series of daily maximum WBGT in the shade and in the sun for 18 example
+ cities, and compared them to our results, again using one sample GCM
+ (CanESM5). Here too the time ranges differ slightly; our historical data span
+ 1984-2014, whereas the data from CHIP span 1980-2020.
diff --git a/articles/extreme-heat-explainer/references.json b/articles/extreme-heat-explainer/references.json
new file mode 100644
index 00000000..01685176
--- /dev/null
+++ b/articles/extreme-heat-explainer/references.json
@@ -0,0 +1,156 @@
+{
+ "yaglou.1957": {
+ "authors": "C P Yaglou & D Minard",
+ "year": 1957,
+ "title": "Control of heat casualties at military training centers",
+ "journal": "AMA Arch Ind Health",
+ "url": "https://pubmed.ncbi.nlm.nih.gov/13457450/"
+ },
+ "liljegren.2008": {
+ "authors": "J C Liljegren et al.",
+ "year": 2008,
+ "title": "Modeling the wet bulb globe temperature using standard meteorological measurements",
+ "journal": "J Occup Environ Hyg",
+ "url": "https://doi.org/10.1080/15459620802310770"
+ },
+ "lemke.2012": {
+ "authors": "B Lemke & T Kjellstrom",
+ "year": 2012,
+ "title": "Calculating workplace WBGT from meteorological data: a tool for climate change assessment",
+ "journal": "Ind Health",
+ "url": "https://doi.org/10.2486/indhealth.ms1352"
+ },
+ "kong.2022": {
+ "authors": "Q Kong & M Huber",
+ "year": 2022,
+ "title": "Explicit Calculations of Wet-Bulb Globe Temperature Compared With Approximations and Why It Matters for Labor Productivity",
+ "journal": "Earth's Future",
+ "url": "https://doi.org/10.1029/2021EF002334"
+ },
+ "thrasher.2022": {
+ "authors": "B Thrasher et al.",
+ "year": 2022,
+ "title": "NASA Global Daily Downscaled Projections, CMIP6",
+ "journal": "Scientific Data",
+ "url": "https://doi.org/10.1038/s41597-022-01393-4"
+ },
+ "eyring.2016": {
+ "authors": "V Eyring et al.",
+ "year": 2016,
+ "title": "Overview of the Coupled Model Intercomparison Project Phase 6 (CMIP6) experimental design and organization",
+ "journal": "Europeon Geosciences Union",
+ "url": "https://doi.org/10.5194/gmd-9-1937-2016"
+ },
+ "schwingshackl.2021": {
+ "authors": "L Schwingshackl et al.",
+ "year": 2021,
+ "title": "Evaluating agreement between bodies of evidence from randomised controlled trials and cohort studies in nutrition research: meta-epidemiological study",
+ "journal": "BMJ",
+ "url": "https://doi.org/10.1136/bmj.n1864"
+ },
+ "stull.2011": {
+ "authors": "R Stull",
+ "year": 2011,
+ "title": "Wet-Bulb Temperature from Relative Humidity and Air Temperature",
+ "journal": "Journal of Applied Meteorology and Climatology",
+ "url": "https://doi.org/10.1175/JAMC-D-11-0143.1"
+ },
+ "brimicombe.2022": {
+ "authors": "C Brimicombe et al.",
+ "year": 2022,
+ "title": "Thermofeel: A python thermal comfort indices library",
+ "journal": "SoftwareX",
+ "url": "https://doi.org/10.1016/j.softx.2022.101005"
+ },
+ "dahl.2019": {
+ "authors": "K Dahl et al.",
+ "year": 2019,
+ "title": "Increased frequency of and population exposure to extreme heat index days in the United States during the 21st century",
+ "journal": "Environmental Research Communications",
+ "url": "https://doi.org/10.1088/2515-7620/ab27cf"
+ },
+ "raymond.2020": {
+ "authors": "C Raymond et al.",
+ "year": 2021,
+ "title": "The emergence of heat and humidity too severe for human tolerance",
+ "journal": "Science Advances",
+ "url": "https://doi.org/10.1126/sciadv.aaw1838"
+ },
+ "tuholske.2021": {
+ "authors": "C Tuholske et al.",
+ "year": 2020,
+ "title": "Global urban population exposure to extreme heat",
+ "journal": "PNAS",
+ "url": "https://doi.org/10.1073/pnas.2024792118"
+ },
+ "funk.2019": {
+ "authors": "C Funk et al.",
+ "year": 2019,
+ "title": "A High-Resolution 1983–2016 Tmax Climate Data Record Based on Infrared Temperatures and Stations by the Climate Hazard Center",
+ "journal": "Journal of Climate",
+ "url": "https://doi.org/10.1175/JCLI-D-18-0698.1"
+ },
+ "cannon.2015": {
+ "authors": "A J Cannon et al.",
+ "year": 2015,
+ "title": "Bias Correction of GCM Precipitation by Quantile Mapping: How Well Do Methods Preserve Changes in Quantiles and Extremes?",
+ "journal": "Journal of Climate",
+ "url": "https://doi.org/10.1175/JCLI-D-14-00754.1"
+ },
+ "verdin.2020": {
+ "authors": "A Verdin et al.",
+ "year": 2020,
+ "title": "Development and validation of the CHIRTS-daily quasi-global high-resolution daily temperature data set",
+ "journal": "Scientific Data",
+ "url": "https://doi.org/10.1038/s41597-020-00643-7"
+ },
+ "rode.2021": {
+ "authors": "A Rode et al.",
+ "year": 2021,
+ "title": "Estimating a social cost of carbon for global energy consumption",
+ "journal": "Nature",
+ "url": "https://doi.org/10.1038/s41586-021-03883-8"
+ },
+ "thrasher.2021": {
+ "authors": "B Thrasher et al.",
+ "year": 2021,
+ "title": "NEX-GDDP-CMIP6",
+ "journal": "NASA Center for Climate Simulation",
+ "url": "https://doi.org/10.7917/OFSG3345"
+ },
+ "florczyk.2019": {
+ "authors": "A Florczyk et al.",
+ "year": 2019,
+ "title": "GHS Urban Centre Database 2015, multitemporal and multidimensional attributes, R2019A",
+ "journal": "European Commission, Joint Research Centre (JRC)",
+ "url": "https://data.jrc.ec.europa.eu/dataset/53473144-b88c-44bc-b4a3-4583ed1f547e"
+ },
+ "tozer.2019": {
+ "authors": "B Tozer et al.",
+ "year": 2019,
+ "title": "Global bathymetry and topography at 15 arc sec: Srtm15+",
+ "journal": "Earth and Space Science",
+ "url": "https://doi.org/10.1029/2019EA000658"
+ },
+ "schiavina.2023": {
+ "authors": "B Tozer et al.",
+ "year": 2023,
+ "title": "GHS-POP R2023A - GHS population grid multitemporal (1975-2030)",
+ "journal": "European Commission, Joint Research Centre (JRC)",
+ "url": "https://data.europa.eu/89h/2ff68a52-5b5b-4a22-8f40-c41da8332cfe"
+ },
+ "chavaillaz.2019": {
+ "authors": "Y Chavaillaz et al.",
+ "year": 2019,
+ "title": "Exposure to excessive heat and impacts on labour productivity linked to cumulative CO2 emissions",
+ "journal": "Sci Rep",
+ "url": "https://doi.org/10.1038/s41598-019-50047-w"
+ },
+ "parsons.2021": {
+ "authors": "L Parsons et al.",
+ "year": 2021,
+ "title": "Increased labor losses and decreased adaptation potential in a warmer world",
+ "journal": "Nat Commun",
+ "url": "https://doi.org/10.1038/s41467-021-27328-y"
+ }
+}
diff --git a/components/mdx/page-components.js b/components/mdx/page-components.js
index f639d8ca..5bcdd8f4 100644
--- a/components/mdx/page-components.js
+++ b/components/mdx/page-components.js
@@ -3,6 +3,33 @@ import dynamic from 'next/dynamic'
// NOTE: This is a dynamically generated file based on the config specified under the
// `components` key in each article's frontmatter.
const components = {
+ 'extreme-heat-explainer': {
+ Small: dynamic(() =>
+ import('../../articles/extreme-heat-explainer/components/small.js').then(
+ (mod) => mod.Small || mod.default
+ )
+ ),
+ HeatCalculator: dynamic(() =>
+ import(
+ '../../articles/extreme-heat-explainer/components/heat-calculator.js'
+ ).then((mod) => mod.HeatCalculator || mod.default)
+ ),
+ BiasCorrection: dynamic(() =>
+ import(
+ '../../articles/extreme-heat-explainer/components/bias-correction.js'
+ ).then((mod) => mod.BiasCorrection || mod.default)
+ ),
+ DaysOver: dynamic(() =>
+ import(
+ '../../articles/extreme-heat-explainer/components/days-over.js'
+ ).then((mod) => mod.DaysOver || mod.default)
+ ),
+ CityMap: dynamic(() =>
+ import(
+ '../../articles/extreme-heat-explainer/components/city-map.js'
+ ).then((mod) => mod.CityMap || mod.default)
+ ),
+ },
'climate-risk-assessments': {
DataSources: dynamic(() =>
import(
diff --git a/package-lock.json b/package-lock.json
index fa66ffd9..1d82e646 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,7 @@
"@carbonplan/emoji": "^2.0.0",
"@carbonplan/icons": "^2.0.0",
"@carbonplan/layouts": "^4.0.0",
- "@carbonplan/minimaps": "^2.3.0",
+ "@carbonplan/minimaps": "^2.4.1",
"@carbonplan/seaweed-farming-model": "^1.0.0",
"@carbonplan/theme": "^7.0.0",
"@emotion/react": "^11.7.1",
@@ -705,12 +705,12 @@
}
},
"node_modules/@carbonplan/minimaps": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@carbonplan/minimaps/-/minimaps-2.3.0.tgz",
- "integrity": "sha512-pnNlvaEVs0KBwrfA4ZhOo18rm4xLhn9d3HjYAdIm+6f1SuewmVU3Rg/tkMfgRaKbby69/bIwVC7uVnfYlzkVcw==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@carbonplan/minimaps/-/minimaps-2.4.1.tgz",
+ "integrity": "sha512-VH9zLDE/ew7F3PxJjJrrB8NBJzF7CMH4Xf8m5QN/lEnqhPEGf7JtWi0A7k3cM8L1hmxmprby8XR0ceq/IcTZjw==",
"dependencies": {
"d3-geo": "^2.0.1",
- "glsl-geo-projection": "^1.0.1",
+ "glsl-geo-projection": "^1.1.1",
"regl": "^2.1.0",
"topojson-client": "^3.1.0",
"zarr-js": "^2.1.3"
@@ -4479,9 +4479,9 @@
}
},
"node_modules/glsl-geo-projection": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/glsl-geo-projection/-/glsl-geo-projection-1.0.1.tgz",
- "integrity": "sha512-6r118GFi+q5lzF3N0Jevv15eveVwLeIqqFe4XRnnYnU/Y/Bp38XpySQaGBO0EzXNhzesUik7c2uSEuYUwa+ogw=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/glsl-geo-projection/-/glsl-geo-projection-1.1.1.tgz",
+ "integrity": "sha512-kUGHDkxReo/GwUINUkWe+e5Up9vWVxx2JaZ3guZwdXMinOD2JFzVSBjz7XYqqOGWS8NoYYqCAVPJN3AuQWsORw=="
},
"node_modules/graceful-fs": {
"version": "4.2.10",
@@ -10781,12 +10781,12 @@
}
},
"@carbonplan/minimaps": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@carbonplan/minimaps/-/minimaps-2.3.0.tgz",
- "integrity": "sha512-pnNlvaEVs0KBwrfA4ZhOo18rm4xLhn9d3HjYAdIm+6f1SuewmVU3Rg/tkMfgRaKbby69/bIwVC7uVnfYlzkVcw==",
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@carbonplan/minimaps/-/minimaps-2.4.1.tgz",
+ "integrity": "sha512-VH9zLDE/ew7F3PxJjJrrB8NBJzF7CMH4Xf8m5QN/lEnqhPEGf7JtWi0A7k3cM8L1hmxmprby8XR0ceq/IcTZjw==",
"requires": {
"d3-geo": "^2.0.1",
- "glsl-geo-projection": "^1.0.1",
+ "glsl-geo-projection": "^1.1.1",
"regl": "^2.1.0",
"topojson-client": "^3.1.0",
"zarr-js": "^2.1.3"
@@ -13699,9 +13699,9 @@
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
},
"glsl-geo-projection": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/glsl-geo-projection/-/glsl-geo-projection-1.0.1.tgz",
- "integrity": "sha512-6r118GFi+q5lzF3N0Jevv15eveVwLeIqqFe4XRnnYnU/Y/Bp38XpySQaGBO0EzXNhzesUik7c2uSEuYUwa+ogw=="
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/glsl-geo-projection/-/glsl-geo-projection-1.1.1.tgz",
+ "integrity": "sha512-kUGHDkxReo/GwUINUkWe+e5Up9vWVxx2JaZ3guZwdXMinOD2JFzVSBjz7XYqqOGWS8NoYYqCAVPJN3AuQWsORw=="
},
"graceful-fs": {
"version": "4.2.10",
diff --git a/package.json b/package.json
index e26b0b9e..3789d37e 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"@carbonplan/emoji": "^2.0.0",
"@carbonplan/icons": "^2.0.0",
"@carbonplan/layouts": "^4.0.0",
- "@carbonplan/minimaps": "^2.3.0",
+ "@carbonplan/minimaps": "^2.4.1",
"@carbonplan/seaweed-farming-model": "^1.0.0",
"@carbonplan/theme": "^7.0.0",
"@emotion/react": "^11.7.1",