import { aq, op } from "@uwdata/arquero"
bucket_base = "https://d33173dkfxwa5x.cloudfront.net/www/"
FA = FileAttachment
import {aq as aq, op as op} from "@uwdata/arquero"
bucket_base = "https://d33173dkfxwa5x.cloudfront.net/www/"
FA = ƒ(name)
import { aq, op } from "@uwdata/arquero"
bucket_base = "https://d33173dkfxwa5x.cloudfront.net/www/"
FA = FileAttachment
import {aq as aq, op as op} from "@uwdata/arquero"
mutable myLocation = null
mutable geolocateStatus = "default"
getLocation = {
yield null
yield navigator.geolocation.getCurrentPosition(
pos => {
mutable myLocation = {
lat: pos.coords.latitude,
lon: pos.coords.longitude
}
mutable geolocateStatus = "granted"
},
e => {
console.log("Geolocation error", e),
mutable geolocateStatus = "error"
},
{
enableHighAccuracy: false
})
}
geolocateStatus == "default" ?
md`Allow location to see local station` :
geolocateStatus == "error" ?
md`Problem getting location` :
md``
Allow location to see local station
myLocation != null ?
md`Location is lat: ${myLocation.lat}, lon: ${myLocation.lon}` :
md`Location permission not granted`
Location permission not granted
// on the home page, get station id from:
// * _geolocator.qmd's myLocation, plus
// * _map.qmd's statsAll
stationId = {
// fallback to sydney?
if (myLocation == null) {
return "023000"
}
// otherwise, find closest station on statsAll
const closestStation = aq.from(statsAll)
.params({
currentLat: myLocation.lat,
currentLon: myLocation.lon,
})
.derive({
dist: (d, $) => Math.sqrt(
($.currentLat - d.isit_lat) ** 2 + ($.currentLon - d.isit_lon) ** 2)
})
.orderby("dist")
.slice(0, 1)
.array("id")
return closestStation[0]
}
Is it hot in Adelaide right now?
answers = ({
"Hell no!": {color: "white", fill1: "#5287d1", fill2: "#2166ac" },
"No!": {color: "white", fill1: "#a2cdeb", fill2: "#67a9cf" },
"Nope": {color: "#333333", fill1: "#f5f8fa", fill2: "#d1e5f0" },
"Not really": {color: "#333333", fill1: "#f7f7f7", fill2: "#ebebeb" },
"Yup": {color: "#333333", fill1: "#fddcca", fill2: "#f7cfb7" },
"Yeah!": {color: "white", fill1: "#de7b49", fill2: "#fc946a" },
"Hell yeah!": {color: "white", fill1: "#d1231d", fill2: "#b2182b" }
})
html`
<div class="answer-icon-and-label">
<span class="answer-icon" style="background-color: ${answers[statsPlace.isit_answer].fill2};">
</span>
<span>${statsPlace.isit_answer}</span>
</div>`
datefns = import("https://cdn.skypack.dev/date-fns@3.3.1?min")
tmaxWhen = new Date(Date.parse(statsPlace.isit_maximum_dt || null))
tminWhen = new Date(Date.parse(statsPlace.isit_minimum_dt || null))
tmaxWhenFriendly = isFinite(tmaxWhen) ?
datefns.formatRelative(tmaxWhen, new Date()) : ""
tminWhenFriendly = isFinite(tminWhen) ?
datefns.formatRelative(tminWhen, new Date()) : ""
percentRating = statsPlace.isit_average.toFixed(1) + "%"
This is the distribution of temperatures typical for this time of year, which we’ve defined as a seven day window either side of today. Just like a bell curve, the plot is higher at more common temperatures and lower at rare temperatures.
We’ve compared every day this year at Adelaide to similar times of year in the past. Red days were hot compared to records; blue days were cold.
statsAllRaw = FA(bucket_base + "stats/stats_all.json").json()
// statsAllRaw is an object with keys by station id, but we
// need an array with an id field. let's move the ids in
statsAll = {
const statsArray = []
Object.keys(statsAllRaw).map(k => statsArray.push({...statsAllRaw[k], id: k}))
const tidiedStats = aq.from(statsArray)
.derive({
lat: d => op.parse_float(d.isit_lat),
lon: d => op.parse_float(d.isit_lon),
isit_average: d => op.parse_float(d.isit_average),
// current? (that's the actual avg temp)
isit_maximum: d => op.parse_float(d.isit_maximum),
isit_minimum: d => op.parse_float(d.isit_minimum),
max_when: d => op.parse_date(d.isit_maximum_dt),
min_when: d => op.parse_date(d.isit_minimum_dt),
slug: d =>
op.replace(
op.replace(
op.replace(
op.lower(d.isit_label),
/\s-\s/g, "-"),
/\s/g, "-"),
/[()]/g, "")
})
// mark as stale if latest obs are > 24 hours old
.derive({
max_stale:
d => ((d.max_when - op.now()) / (1000 * 60)) / (24 * 60) > 1,
min_stale:
d => ((d.min_when - op.now()) / (1000 * 60)) / (24 * 60) > 1
})
.objects()
return(tidiedStats)
}
currentStats = statsAll.filter(d => (!d.max_stale) && (!d.min_stale))
staleStats = statsAll.filter(d => d.max_stale || d.min_stale)
australia = FileAttachment("/assets/australia.geojson").json()
Plot = import("https://esm.run/@observablehq/plot")
answer_domain = [
"Hell no!",
"No!",
"Nope",
"Not really",
"Yup",
"Yeah!",
"Hell yeah!"]
Plot.plot({
projection: {
type: "equirectangular",
rotate: [-133, 28],
domain: d3.geoCircle().center([133, -28]).radius(18)()
},
marks: [
// Plot.graticule(),
Plot.geo(australia, {
fill: "#00000009",
stroke: "#dddddd"
}),
// replace stale locations with crosses
Plot.dot(staleStats, {
x: "lon",
y: "lat",
stroke: "lightgrey",
r: 6,
symbol: "times"
}),
Plot.dot(currentStats, {
x: "lon",
y: "lat",
fill: "isit_answer",
stroke: d =>
d.isit_answer == "Hell no!" || d.isit_answer == "Hell yeah!" ?
"black" :
"#00000099",
strokeWidth: 0.75,
r: 6,
}),
Plot.tip(currentStats, tipTemplate),
// enlarge dot you're pointing to
Plot.dot(currentStats, Plot.pointer({
x: "lon",
y: "lat",
fill: "isit_answer",
stroke: d =>
d.isit_answer == "Hell no!" || d.isit_answer == "Hell yeah!" ?
"black" :
"#00000099",
strokeWidth: 0.75,
r: 12,
})),
// enlargen clickable areas with invisible voronoi polys
Plot.voronoi(currentStats, {
x: "lon",
y: "lat",
fill: "transparent",
stroke: "transparent",
href: d => "/places/" + d.slug,
}),
],
color: {
type: "ordinal",
domain: answer_domain,
range: ["#2166acff",
"#67a9cfff",
"#d1e5f0ff",
"#f7f7f7ff",
"#fddbc7ff",
"#ef8a62ff",
"#b2182bff"]
},
height: 600
})
// breaking out the pointer layer for readability
// (and in case we decide to customise it later)
tipTemplate = Plot.pointer({
x: "lon",
y: "lat",
channels: {
place: {
label: "",
value: d => d.isit_label,
},
label: {
label: "",
value: d => d.isit_comment
},
separator: {
label: "",
value: d => ""
},
isit_maximum: {
label: "Max:",
value: d => d.isit_maximum + "°C"
},
isit_minimum: {
label: "Min:",
value: d => d.isit_minimum + "°C"
}
},
fontSize: 20,
fontFamily: "Roboto Condensed",
format: {
x: null,
y: null
},
lineWidth: 20,
lineHeight: 1.25,
pointerSize: 0,
fillOpacity: 0.8,
strokeOpacity: 0
})