micro = require("micromodal@0.4.10")
micro.init({
awaitOpenAnimation: true,
awaitCloseAnimation: true,
// hide the area controls when a modal is open
onShow: () =>
document.getElementById("area-search-controls").classList.add("hide"),
onClose: () =>
document.getElementById("area-search-controls").classList.remove("hide")
});
r = require.alias({
maplibregl: "maplibre-gl@2.1.9/dist/maplibre-gl.js",
});
maplibregl = r("maplibregl").catch(() => window["maplibregl"]);
/* this is a bit different to regular mapbox/maplibre instantiation
it lets have the map react to other values in the document, like
a button or a timer, without reinstantiating!
(based on https://observablehq.com/@tmcw/using-mapbox-gl-js) */
viewof map = {
let container = html`<div style="position: absolute; left: 0; top: 0; height: 100vh; width: 100%;" />`;
// Give the container dimensions.
yield container;
// Create the \`map\` object with the mapboxgl.Map constructor, referencing
// the container div
let map = new maplibregl.Map({
container,
bounds: [[111, -46], [155, -9]],
fitBoundsOptions: {
padding: {top: 130, bottom: 70, left: 5, right: 5}
},
maxZoom: 14,
antialias: true,
style: "style.json",
attributionControl: false,
// very loose to accomodate all of aus on portrait screens
maxBounds: [[95, -70], [170, 15]],
});
// on map load:
// - dispatch its value back to ojs
// - add a prop to the layer that adds/removes a popup from the map
// (we can't do this on initial layer def because the map isn't ready yet)
map.on("load", () => {
container.value = map;
container.dispatchEvent(new CustomEvent("input"));
map.addSource("postcodes", {
"url": "tiles/postcodes/tiles.json",
"type": "vector",
"promoteId": "POA_NAME21"
});
// this layer shades all postcodes
map.addLayer({
id: "postcode-colors",
source: "postcodes",
"source-layer": "postcodeswgs84",
type: "fill"
});
// add pattern for focused postcodes
map.loadImage("stripes.png", function(err, image) {
if (err) throw err;
map.addImage("focusStripes", image);
});
/* popup */
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: false,
className: "map-popup"
});
map.on("click", "postcode-colors", function(e) {
// change the cursor style as a ui indicator.
console.log("Mouse entering...")
map.getCanvas().style.cursor = "pointer";
// display a warning if projections aren't available, or show them for
// this layer if they are
const noProjectionsMessage =
"We don't have projections for postcode " +
e.features[0].properties.POA_NAME21 +
", but try another nearby.";
var projectionMessage =
"<h3>Postcode " + e.features[0].properties.POA_NAME21 + "</h3>" +
"<p>Up to <strong>" + e.features[0].state.ensmin + " to " +
e.features[0].state.ensmax +
(selectedPeriod == "1995" ? "" : " more") +
"</strong> 35°C+ days in a typical year around " +
selectedPeriod + "</p>"
var description =
(e.features[0].state.ensmin === undefined
| e.features[0].state.ensmax === undefined) ?
noProjectionsMessage : projectionMessage;
// populate popup; locate based on mouse's back-projected position
popup.setLngLat(e.lngLat).setHTML(description).addTo(map);
});
});
}
viewof selectedPeriod = Inputs.radio(
new Map([
["1995", "1995"],
["Change in 2030", "2030"],
["Change in 2050", "2050"]
]),
{ value: "1995" });
viewof selectedScenario = Inputs.radio(
new Map([
["Medium emissions", "rcp45diff"],
["High emissions", "rcp85diff"]
]),
{ value: "rcp45diff", disabled: selectedPeriod == "1995" })
// override scenario with historical if 1995 is selected
actualScenario = (selectedPeriod == "1995" ? "historical" : selectedScenario)
viewof areaSearch = Inputs.search(postcodeSuburbMap, {
placeholder: "Enter your postcode or suburb",
// don't show the number of results
format: () => ``
});
// display a menu of results and a search button when we're down to 30 results
viewof selectedPostcode =
areaSearch.length > 0 && areaSearch.length < 100 ?
Inputs.select(areaSearch, {
format: d => `${d.SAL_NAME21} (${d.POA_NAME21})`
}) :
md``
viewof goBtn =
areaSearch.length > 0 && areaSearch.length < 100 ?
Inputs.button(`🔍 Find`, {
reduce: () => zoomToPostcode(selectedPostcode.POA_NAME21)
}) :
md``
// need a switch for when our tile search fails due to a postcode being too
// small (bit of a workaround for not having the time to re-do the tiles!)
mutable tileSearchFailed = false;
areaWarn =
tileSearchFailed ?
md`🔍 This postcode's either very small or not in view — zoom in or out a bit and search again.` :
areaSearch.length == 0 ? md`❌ No areas matched this search` : md``
projections = FileAttachment("/data/news-stats-postcodes-short.csv")
.csv({ typed: true });
postcodeSuburbMap = FileAttachment("/data/postcode-suburb-map.csv")
.csv();
// postcodeList = FileAttachment("/data/postcode-list.csv").csv();
// filter data here (note postcodes need to be re-padded b/c observable's type
// inference makes them numeric)
filteredProjections = projections
.filter(
d =>
(d.file_period == selectedPeriod) &&
(d.file_scenario == actualScenario))
.map(d => ({ ...d, postcode: String(d.geo_name).padStart(4, "0") }));
yellowRedFill = [
0, "#ffffb2",
5, "#fed976",
10, "#feb24c",
20, "#fd8d3c",
40, "#f03b20",
80, "#bd0026"
]
yellowRedStroke = [
0, "#ffff46",
5, "#fdbf1a",
10, "#f68e01",
20, "#e86302",
40, "#bf240d",
80, "#8e001c"
]
rainbowFill = [
0, "#3288bd",
7, "#99d594",
14, "#e6f598",
30, "#ffffbf",
90, "#fee08b",
180, "#fc8d59",
366, "#d53e4f"
]
rainbowStroke = [
0, "#26668e",
7, "#5bbc53",
14, "#d0ec3e",
30, "#ffff4f",
90, "#fdc62a",
180, "#fa5305",
366, "#a92534"
]
// when the filtered data changes, rejoin it to the map tiles and update the
// colour scheme
updateMapData = {
filteredProjections.forEach(row => {
map.setFeatureState(
{
source: "postcodes",
sourceLayer: "postcodeswgs84",
id: row.postcode,
},
{
ensmean: row.ensmean,
ensmax: row.ensmax,
ensmin: row.ensmin
}
);
});
if (actualScenario.endsWith("diff")) {
// extra days in future: yellow-red
console.log(actualScenario + ": switching to yellow-red")
map.setPaintProperty("postcode-colors", "fill-color",
["case", ["==", ["feature-state", "ensmean"], null], "#666666",
["interpolate", ["linear"], ["feature-state", "ensmean"],
...yellowRedFill]
]);
map.setPaintProperty("postcode-colors", "fill-outline-color",
["case", ["==", ["feature-state", "ensmean"], null], "#666666",
["interpolate", ["linear"], ["feature-state", "ensmean"],
...yellowRedStroke]
]);
} else {
// historical: diverging rainbow
console.log(actualScenario + ": switching to rainbow")
map.setPaintProperty("postcode-colors", "fill-color",
["case", ["==", ["feature-state", "ensmean"], null], "#666666",
["interpolate", ["linear"], ["feature-state", "ensmean"],
...rainbowFill]
]);
map.setPaintProperty("postcode-colors", "fill-outline-color",
["case", ["==", ["feature-state", "ensmean"], null], "#666666",
["interpolate", ["linear"], ["feature-state", "ensmean"],
...rainbowStroke]
]);
}
// TODO - also set fill pattern if highlighted
// map.setPaintProperty("postcode-colors", "fill-patterm",
// ["case", ["==", ["feature-state", "isFocus"], true],
// "focusStripes",
// // null?
// ]);
}
function zoomToPostcode(postcode) {
// TODO - stop highlighting previously selected postcode
console.log("Starting postcode search for " + postcode)
// get the postcode's feature
const targetPostcodeFeature = map.querySourceFeatures("postcodes", {
sourceLayer: "postcodeswgs84",
filter: ["==", "POA_NAME21", postcode]
});
if (targetPostcodeFeature.length != 1) {
console.error("Can only zoom to 1 postcode at a time, not ",
targetPostcodeFeature.length);
mutable tileSearchFailed = true;
}
// get the feature's geometry
const geom = targetPostcodeFeature[0].geometry.coordinates[0];
// reduce the geometry to bounds
const postcodeBounds = geom.reduce(
(accum, current) => accum.extend(current),
new maplibregl.LngLatBounds(geom[0], geom[0]));
// zoom to postcode
map.fitBounds(postcodeBounds, {
padding: {top: 40, bottom:30, left: 10, right: 10},
maxZoom: 12
});
// highlight postcode?
map.setFeatureState(
{
source: "postcodes",
sourceLayer: "postcodeswgs84",
id: postcode,
},
{ inFocus: true }
);
// display info panel
// (hide the area controls when a modal is open)
setTimeout(
micro.show("modal-focused-postcode", {
onShow: () =>
document.getElementById("area-search-controls").classList.add("hide"),
onClose: () =>
document.getElementById("area-search-controls").classList.remove("hide")
}),
1000);
}
focusedPostcodeProjections = projections
.filter(
d => d.geo_name == selectedPostcode.POA_NAME21)
// .map(d => ({ ...d, postcode: String(d.geo_name).padStart(4, "0") }));
anyData = focusedPostcodeProjections.length > 0;
figures = anyData ?
{
figNow: focusedPostcodeProjections
.filter(d => d.file_scenario == "historical"),
fig2030Med: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp45" && d.file_period == "2030"),
fig2030MedDiff: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp45diff" && d.file_period == "2030"),
fig2050Med: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp45" && d.file_period == "2050"),
fig2050MedDiff: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp45diff" && d.file_period == "2050"),
fig2030High: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp85" && d.file_period == "2030"),
fig2030HighDiff: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp85diff" && d.file_period == "2030"),
fig2050High: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp85" && d.file_period == "2050"),
fig2050HighDiff: focusedPostcodeProjections
.filter(d => d.file_scenario == "rcp85diff" && d.file_period == "2050")
} :
{
};
dataSwitch = {
console.log("Any data? ", anyData)
if (anyData) {
// show .projection-content; hide .no-projections
document.getElementById("no-projections").classList.remove("show");
document.getElementById("projection-content").classList.add("show");
} else {
// hide .projection-content; show .no-projections
document.getElementById("no-projections").classList.add("show");
document.getElementById("projection-content").classList.remove("show");
}
}
❌ We don’t have any projections for this postcode, but try another one nearby.
These charts, as well as the analyses that underpin them, are available under a Creative Commons Attribution 4.0 licence.
Please acknowledge 360info and our data sources when you use these charts and data:
Copy and paste the following code:
<iframe allow="fullscreen; clipboard-write self https://feb2023.360info-heatmaps-narclim.pages.dev/" allowfullscreen="true" src="https://feb2023.360info-heatmaps-narclim.pages.dev/news/" title="Interactive: future heat" style="width:100%; height:500px; border:none; background-color: #eeeeee;" scrolling="no"></iframe>
This content is subject to 360info’s Terms of Use.
Visit the GitHub repository to:
This map shows how a set of climate models estimate 35°C+ days will change across Australia in the coming decades. You can narrow in on a particular postcode.
The data comes from NARCliM1.5, an experiment undertaking by the NSW Department of Planning and Environment and the Climate Change Research Centre.
Because more than one climate models is used, we tell you the range of estimates among them.
NARCLiM1.5 is used by scientists to compare against an earlier version of NARCliM, which had slightly less warming. These estimates should therefore be considered an upper bound.