En ny eksport av Nordhusstykket 5 viste hva mer modellen kan bære, og avdekket feil. Posisjon og klimasted - IfcMapConversion med IfcProjectedCRS gjøres om til breddegrad og lengdegrad. EUREF89 og WGS 84 UTM, NN2000-varianter og Web Mercator forstås. Omregningen er kontrollert mot PROJ. - Vectorworks' standardplassering i Washington, som står når ingenting er satt, kjennes igjen og brukes ikke. - Ny `posisjon` under [klima]. Uten kommune velger beregningen nærmeste av de 358 klimastedene i TMY-arkivet, og sier i rapporten hvor langt unna det ligger. Opplysninger fra modellen - Adressen hentes fra IfcBuilding eller IfcSite. Før ble den første postadressen i filen brukt, og det var firmaets adresse i forfatteropplysningene. - Energirådgiver fra person og organisasjon i forfatteropplysningene. - Byggets navn fra prosjektet når bygningen heter «Default Building». - Referansehøyde og terrenghøyde på bygningen gir hvor mye av grunnmuren som står over bakken. - IsLandmarked gir en merknad om at vernede bygninger er unntatt energiattestplikten. En eksport uten bygningsdeler stopper nå med en feilmelding som peker på lagtilknytningen i eksportdialogen, i stedet for å skrive en tom bygning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
140 lines
5.0 KiB
Rust
140 lines
5.0 KiB
Rust
//! Henter og hurtiglagrer TMY-filer for norske kommuner.
|
|
//!
|
|
//! Datasettet «TMY files for Norway based on CERRA data from 1991-2020» (Zenodo,
|
|
//! doi 10.5281/zenodo.14779899, CC-BY-4.0) er laget i tilknytning til NS 3031:2025 og dekker
|
|
//! alle norske kommuner med timesverdier. Zip-filen lastes ned én gang til
|
|
//! `$XDG_CACHE_HOME/enok` og pakkes ut derfra.
|
|
|
|
use crate::data::normaliser_stedsnavn;
|
|
use crate::klima::Vaerdata;
|
|
use crate::Result;
|
|
use anyhow::{anyhow, bail, Context};
|
|
use std::io::Read;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
const ZIP_URL: &str =
|
|
"https://zenodo.org/api/records/19566253/files/EPW-TMYNO_CERRA_1991-2020.zip/content";
|
|
const ZIP_NAVN: &str = "EPW-TMYNO_CERRA_1991-2020.zip";
|
|
|
|
pub fn hurtiglager() -> PathBuf {
|
|
std::env::var_os("ENOK_CACHE")
|
|
.map(PathBuf::from)
|
|
.or_else(|| std::env::var_os("XDG_CACHE_HOME").map(|c| PathBuf::from(c).join("enok")))
|
|
.or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".cache/enok")))
|
|
.unwrap_or_else(|| PathBuf::from(".enok-cache"))
|
|
}
|
|
|
|
fn zip_sti() -> PathBuf {
|
|
hurtiglager().join(ZIP_NAVN)
|
|
}
|
|
|
|
/// Laster ned TMY-arkivet dersom det ikke finnes lokalt.
|
|
pub fn sikre_arkiv() -> Result<PathBuf> {
|
|
let sti = zip_sti();
|
|
if sti.exists() && std::fs::metadata(&sti)?.len() > 1_000_000 {
|
|
return Ok(sti);
|
|
}
|
|
std::fs::create_dir_all(sti.parent().unwrap())?;
|
|
eprintln!(
|
|
"Laster ned TMY-klimadata (74 MB) fra Zenodo til {} …",
|
|
sti.display()
|
|
);
|
|
let svar = ureq::get(ZIP_URL)
|
|
.timeout(std::time::Duration::from_secs(900))
|
|
.call()
|
|
.context("nedlasting av TMY-arkivet feilet")?;
|
|
let mut data = Vec::new();
|
|
svar.into_reader().read_to_end(&mut data)?;
|
|
if data.len() < 1_000_000 {
|
|
bail!("nedlastet arkiv er uventet lite ({} byte)", data.len());
|
|
}
|
|
std::fs::write(&sti, &data)?;
|
|
Ok(sti)
|
|
}
|
|
|
|
/// Navnene på alle kommuner i arkivet.
|
|
pub fn kommuner() -> Result<Vec<String>> {
|
|
let sti = sikre_arkiv()?;
|
|
let fil = std::fs::File::open(&sti)?;
|
|
let mut zip = zip::ZipArchive::new(fil)?;
|
|
let mut ut = Vec::new();
|
|
for i in 0..zip.len() {
|
|
let navn = zip.by_index(i)?.name().to_string();
|
|
if let Some(k) = kommune_av_filnavn(&navn) {
|
|
ut.push(k);
|
|
}
|
|
}
|
|
ut.sort();
|
|
Ok(ut)
|
|
}
|
|
|
|
fn kommune_av_filnavn(navn: &str) -> Option<String> {
|
|
let base = Path::new(navn).file_name()?.to_str()?;
|
|
let base = base.strip_suffix(".epw")?;
|
|
let base = base.strip_prefix("TMYNO_")?;
|
|
let base = base.strip_suffix("_CERRA_1991-2020")?;
|
|
Some(base.to_string())
|
|
}
|
|
|
|
/// Klimastedet nærmest en posisjon, og avstanden dit i km.
|
|
///
|
|
/// Hver TMY-fil oppgir i første linje koordinatene for punktet dataene gjelder. Bare starten
|
|
/// av hver fil pakkes ut, så oppslaget går raskt selv om arkivet er stort.
|
|
pub fn naermeste_kommune(p: &crate::klima::geo::Posisjon) -> Result<(String, f64)> {
|
|
let sti = sikre_arkiv()?;
|
|
let fil = std::fs::File::open(&sti)?;
|
|
let mut zip = zip::ZipArchive::new(fil)?;
|
|
let mut best: Option<(String, f64)> = None;
|
|
for i in 0..zip.len() {
|
|
let oppf = zip.by_index(i)?;
|
|
let Some(kommune) = kommune_av_filnavn(oppf.name()) else {
|
|
continue;
|
|
};
|
|
let mut hode = Vec::with_capacity(512);
|
|
oppf.take(512).read_to_end(&mut hode)?;
|
|
let linje = crate::klima::epw::avkod(&hode);
|
|
let felt: Vec<&str> = linje.lines().next().unwrap_or("").split(',').collect();
|
|
let tall = |i: usize| felt.get(i).and_then(|s| s.trim().parse::<f64>().ok());
|
|
let (Some(bredde), Some(lengde)) = (tall(6), tall(7)) else {
|
|
continue;
|
|
};
|
|
let d = p.avstand_km(&crate::klima::geo::Posisjon { bredde, lengde });
|
|
if best.as_ref().is_none_or(|(_, bd)| d < *bd) {
|
|
best = Some((kommune, d));
|
|
}
|
|
}
|
|
best.ok_or_else(|| anyhow!("TMY-arkivet inneholder ingen klimafiler med koordinater"))
|
|
}
|
|
|
|
/// Henter værdata for en kommune. Navnet sammenlignes normalisert, slik at både
|
|
/// «Indre Østfold» og «Indre Ostfold» treffer samme fil.
|
|
pub fn for_kommune(kommune: &str) -> Result<Vaerdata> {
|
|
let sti = sikre_arkiv()?;
|
|
let fil = std::fs::File::open(&sti)?;
|
|
let mut zip = zip::ZipArchive::new(fil)?;
|
|
let mål = normaliser_stedsnavn(kommune);
|
|
let mut treff = None;
|
|
for i in 0..zip.len() {
|
|
let navn = zip.by_index(i)?.name().to_string();
|
|
if let Some(k) = kommune_av_filnavn(&navn) {
|
|
if normaliser_stedsnavn(&k) == mål {
|
|
treff = Some((i, k));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
let (indeks, navn) = treff.ok_or_else(|| {
|
|
anyhow!(
|
|
"fant ingen TMY-fil for kommunen «{}» (bruk `enok klima --liste` for gyldige navn)",
|
|
kommune
|
|
)
|
|
})?;
|
|
let mut oppf = zip.by_index(indeks)?;
|
|
let mut data = Vec::new();
|
|
oppf.read_to_end(&mut data)?;
|
|
let tekst = String::from_utf8_lossy(&data);
|
|
let mut v = Vaerdata::fra_tekst(&tekst).with_context(|| format!("TMY-fil for {navn}"))?;
|
|
v.sted = navn;
|
|
Ok(v)
|
|
}
|