Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fba5a7936d
|
||
|
|
be1514bd83
|
||
|
|
81544d73e6
|
@@ -1,76 +0,0 @@
|
|||||||
name: Energirapporter
|
|
||||||
|
|
||||||
# Lager energirapport for hver bygning i `bygninger/` og legger PDF, JSON og CSV ved
|
|
||||||
# utgivelsen. Se `bygninger/LES-MEG.md` for hvilke filtyper som kan ligge der.
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
rapporter:
|
|
||||||
runs-on: fish
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: fish {0}
|
|
||||||
steps:
|
|
||||||
- name: Hent koden
|
|
||||||
uses: https://gitea.com/actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Sørg for en Rust-verktøykjede
|
|
||||||
run: |
|
|
||||||
# Kjørebrukeren har rustup, men ingen standardkjede er satt. Uten dette svarer
|
|
||||||
# cargo med at den ikke vet hvilken versjon den skal bruke.
|
|
||||||
if not cargo --version >/dev/null 2>&1
|
|
||||||
rustup default stable
|
|
||||||
end
|
|
||||||
cargo --version
|
|
||||||
|
|
||||||
- name: Bygg enok
|
|
||||||
run: cargo build --release --locked
|
|
||||||
|
|
||||||
- name: Importer BIM-filer som ikke har en TOML fra før
|
|
||||||
run: |
|
|
||||||
for f in (find bygninger -type f \( -iname '*.ifc' -o -iname '*.gbxml' -o -iname '*.csv' \) 2>/dev/null)
|
|
||||||
set toml (string replace -r '\.[^.]+$' '.toml' $f)
|
|
||||||
if not test -f $toml
|
|
||||||
echo "Importerer $f"
|
|
||||||
./target/release/enok import $f --ut $toml
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
- name: Beregn og lag rapporter
|
|
||||||
run: |
|
|
||||||
mkdir -p dist
|
|
||||||
set feil 0
|
|
||||||
for t in (find bygninger -type f -name '*.toml' 2>/dev/null)
|
|
||||||
set navn (basename $t .toml)
|
|
||||||
echo "::group::$navn"
|
|
||||||
if ./target/release/enok beregn $t --ut ut/$navn
|
|
||||||
cp ut/$navn/rapport.pdf dist/$navn-energirapport.pdf
|
|
||||||
cp ut/$navn/resultat.json dist/$navn-resultat.json
|
|
||||||
cp ut/$navn/maaned.csv dist/$navn-maaned.csv
|
|
||||||
gzip -c ut/$navn/timeserie.csv > dist/$navn-timeserie.csv.gz
|
|
||||||
else
|
|
||||||
echo "Beregningen av $navn feilet"
|
|
||||||
set feil 1
|
|
||||||
end
|
|
||||||
echo "::endgroup::"
|
|
||||||
end
|
|
||||||
ls -la dist
|
|
||||||
exit $feil
|
|
||||||
|
|
||||||
- name: Legg ved utgivelsen
|
|
||||||
if: github.event_name == 'release'
|
|
||||||
env:
|
|
||||||
TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
set REL_ID (curl --silent --fail --header "Authorization: token $TOKEN" \
|
|
||||||
"$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/tags/$GITHUB_REF_NAME" | jq -r '.id')
|
|
||||||
for f in (find dist -type f)
|
|
||||||
echo "Laster opp "(basename $f)
|
|
||||||
curl --silent --show-error --fail --header "Authorization: token $TOKEN" \
|
|
||||||
--form "attachment=@$f" \
|
|
||||||
"$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/$REL_ID/assets?name="(basename $f)
|
|
||||||
end
|
|
||||||
Generated
+1
-1
@@ -222,7 +222,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enok"
|
name = "enok"
|
||||||
version = "0.1.1"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "enok"
|
name = "enok"
|
||||||
version = "0.1.1"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Årlig energiberegning og energirapport for boliger etter NS 3031 (timesmetode, ISO 52016-1)"
|
description = "Årlig energiberegning og energirapport for boliger etter NS 3031 (timesmetode, ISO 52016-1)"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ varmekilder og drift — og et typisk klimaår, og gir 8760 timesverdier, et ful
|
|||||||
fra netto energibehov til vektet levert energi, en energikarakter og en ferdig rapport i PDF.
|
fra netto energibehov til vektet levert energi, en energikarakter og en ferdig rapport i PDF.
|
||||||
|
|
||||||
```
|
```
|
||||||
enok beregn bygninger/nordhusstykket-5.toml
|
enok beregn bygning.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -117,7 +117,7 @@ byggeaar = 1948
|
|||||||
oppvarmet_bra = 140.0
|
oppvarmet_bra = 140.0
|
||||||
|
|
||||||
[klima]
|
[klima]
|
||||||
kommune = "Indre Østfold"
|
kommune = "Indre Østfold" # eller posisjon = [59.63, 11.01] for nærmeste klimasted
|
||||||
|
|
||||||
[[flate]]
|
[[flate]]
|
||||||
navn = "Yttervegg sør"
|
navn = "Yttervegg sør"
|
||||||
@@ -178,12 +178,72 @@ Ferdige eksempler ligger i `eksempler/`.
|
|||||||
| gbXML | Verktøy som eksporterer gbXML | Rom, flater, åpninger, konstruksjoner med lag |
|
| gbXML | Verktøy som eksporterer gbXML | Rom, flater, åpninger, konstruksjoner med lag |
|
||||||
| Regneark | *Fil → Eksporter → Export Worksheet* | Enkel CSV med én rad per bygningsdel |
|
| Regneark | *Fil → Eksporter → Export Worksheet* | Enkel CSV med én rad per bygningsdel |
|
||||||
|
|
||||||
Himmelretningene i IFC utledes av elementenes plassering: veggens lokale akse gir planet, og
|
### Hva enok henter ut av en IFC-modell
|
||||||
ytre normal velges som den som peker bort fra bygningens tyngdepunkt, korrigert for prosjektets
|
|
||||||
sanne nord. Kontroller dem mot tegningen etter import.
|
|
||||||
|
|
||||||
Ingen av formatene beskriver ventilasjon, lekkasjetall eller varmekilder. Importen skriver
|
| Opplysning | Hvor den kommer fra |
|
||||||
merknader øverst i TOML-filen om hva som må fylles ut.
|
| --- | --- |
|
||||||
|
| Areal for vegger, tak og dekker | Geometrien. Trekantene grupperes etter retning, og den største gruppen er delens hovedflate. Vinduer som er skåret ut av veggen kommer riktig fram, siden hullene mangler trekanter. Materialsjikt som ligger oppå hverandre telles én gang |
|
||||||
|
| Himmelretning og helning | Flatenormalen, dreid med elementets plassering og prosjektets sanne nord |
|
||||||
|
| U-verdi | `ThermalTransmittance` i egenskapssettene, for både konstruksjoner og vinduer |
|
||||||
|
| Ytre eller innvendig | `IsExternal` |
|
||||||
|
| Masseklasse | Materialnavnene. Betong, tegl og mur gir tung konstruksjon, bindingsverk og isolasjon lett |
|
||||||
|
| Etasjer og hva som ligger i dem | `IfcBuildingStorey` med høyde, og tilknytningen av rom og bygningsdeler |
|
||||||
|
| Uoppvarmet kjeller | En etasje under referansenivået blir satt opp som egen sone, med sine egne vegger og gulv, og dekket over som grense mot den oppvarmede delen |
|
||||||
|
| Oppvarmet BRA | Rommene i etasjene over terreng. Rom som heter kott, bod, loft eller garasje blir flagget |
|
||||||
|
| Vindus- og dørareal | `OverallHeight` og `OverallWidth`, og hvilken vegg de sitter i |
|
||||||
|
| Posisjon | `IfcMapConversion` med `IfcProjectedCRS`, ellers `IfcSite`. Uten oppgitt kommune velger beregningen nærmeste klimasted. EUREF89 UTM og Web Mercator forstås |
|
||||||
|
| Grunnmur over terreng | `ElevationOfRefHeight` og `ElevationOfTerrain` på bygningen |
|
||||||
|
| Adresse | `BuildingAddress` på bygningen, eller `SiteAddress` på tomta. Adressen i forfatteropplysningene er firmaets og brukes ikke |
|
||||||
|
| Energirådgiver | Person og organisasjon i forfatteropplysningene |
|
||||||
|
| Byggets navn | Bygningens navn, eller prosjektets når bygningen heter «Default Building» |
|
||||||
|
| Vernet bygning | `IsLandmarked`. Vernede bygninger er unntatt plikten til energiattest |
|
||||||
|
|
||||||
|
Himmelretningene og arealene bør likevel kontrolleres mot tegningen. Importen skriver merknader
|
||||||
|
øverst i TOML-filen om hva den måtte anta.
|
||||||
|
|
||||||
|
### Egenskaper enok leser
|
||||||
|
|
||||||
|
IFC beskriver geometri og konstruksjon, men har ingen fast plass til byggeår, tetthet,
|
||||||
|
ventilasjon eller hvordan huset varmes opp. Det er nettopp de opplysningene som skiller en god
|
||||||
|
energiberegning fra en middelmådig: for et hus fra 1948 er forskjellen mellom lekkasjetall 0,6
|
||||||
|
og 8 langt større enn alt annet i modellen.
|
||||||
|
|
||||||
|
De kan likevel følge med. I Vectorworks lager du et postformat, fester det på bygningen og
|
||||||
|
knytter feltene til et IFC-egenskapssett. enok leser da disse navnene fra et hvilket som helst
|
||||||
|
egenskapssett på `IfcBuilding`, `IfcSite` eller `IfcProject`:
|
||||||
|
|
||||||
|
| Felt | Godtar også | Eksempel |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Byggeaar` | `Byggeår`, `YearOfConstruction` | `1948` |
|
||||||
|
| `Bygningskategori` | `Kategori`, `BuildingCategory` | `Småhus` |
|
||||||
|
| `Kommune` | `Municipality`, `Klimasted` | `Indre Østfold` |
|
||||||
|
| `Lekkasjetall` | `n50`, `AirPermeability` | `8.0` |
|
||||||
|
| `Ventilasjonstype` | `Ventilasjon`, `VentilationType` | `Balansert` |
|
||||||
|
| `Varmegjenvinning` | `Gjenvinning`, `HeatRecoveryEfficiency` | `80` |
|
||||||
|
| `Varmekilde` | `Oppvarming`, `HeatingSource` | `Bergvarmepumpe` |
|
||||||
|
| `OppvarmetBRA` | `HeatedFloorArea` | `120` |
|
||||||
|
|
||||||
|
Med de åtte feltene på plass er importen fra IFC nær nok komplett til at beregningen står på
|
||||||
|
modellen alene.
|
||||||
|
|
||||||
|
Dette i selve modellen er verdt å gjøre riktig, omtrent i rekkefølge etter hva det betyr:
|
||||||
|
|
||||||
|
- **Ta med designlagene i eksporten.** I «Export IFC Project» må lagene være knyttet til
|
||||||
|
etasjer under «Layer Mapping». Mangler de, blir filen bare prosjektstruktur uten vegger,
|
||||||
|
dekker, vinduer eller rom. enok stopper da med en feilmelding i stedet for å skrive en tom
|
||||||
|
bygning.
|
||||||
|
- **Georeferer tegningen, eller sett prosjektstedet.** Da får modellen koordinater, og
|
||||||
|
beregningen velger selv nærmeste av de 358 klimastedene. Uten det står Vectorworks'
|
||||||
|
standardverdi i Washington, og den blir oversett.
|
||||||
|
- **Merk yttervegger med `IsExternal`.** I modellen for Nordhusstykket 5 er bare tolv av
|
||||||
|
hundre og fem vegger merket, og kjellermurene er ikke blant dem.
|
||||||
|
- **Fyll inn U-verdi for tak og vinduer.** Veggene har den, takflatene og vinduene ikke.
|
||||||
|
- **Gi etasjene navn.** «Kjeller», «1. etg.» og «2. etg.» er nok til at enok finner den
|
||||||
|
uoppvarmede kjelleren og regner oppvarmet areal riktig.
|
||||||
|
- **Sett referansehøyde og terrenghøyde på bygningen.** Da regner enok ut hvor mye av
|
||||||
|
grunnmuren som står over bakken. Uten dem regnes hele muren som nedgravd.
|
||||||
|
- **Legg byggets adresse på bygningen.** Adressen i forfatteropplysningene er firmaets, og
|
||||||
|
brukes ikke som byggets adresse.
|
||||||
|
|
||||||
Regnearkformatet er bevisst enkelt, slik at det kan settes opp som en rapport i BIM-verktøyet:
|
Regnearkformatet er bevisst enkelt, slik at det kan settes opp som en rapport i BIM-verktøyet:
|
||||||
|
|
||||||
@@ -197,22 +257,18 @@ vindu,Stue sør,3.0,2.8,180,90,,0.76,2
|
|||||||
|
|
||||||
## Rapporter fra byggeserveren
|
## Rapporter fra byggeserveren
|
||||||
|
|
||||||
Arbeidsflytene i `.gitea/workflows/` kjører på vertskjøreren `fish` og utløses når en
|
Rapportene lages i hvert prosjektlager, ikke i enok. enoks egne arbeidsflyter kjører på
|
||||||
utgivelse publiseres, på samme måte som de andre prosjektene.
|
vertskjøreren `fish` på samme måte som de andre prosjektene:
|
||||||
|
|
||||||
| Fil | Hva den gjør |
|
| Fil | Hva den gjør |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `test.yml` | Formatering, clippy og tester på hver push |
|
| `test.yml` | Formatering, clippy og tester på hver push |
|
||||||
| `release.yml` | Bygger enok og legger binærfilen ved utgivelsen |
|
| `release.yml` | Bygger enok og legger binærfilen ved utgivelsen |
|
||||||
| `rapport.yml` | Lager energirapport for hver bygning i `bygninger/` og legger PDF, JSON og CSV ved utgivelsen |
|
|
||||||
|
|
||||||
IFC-, gbXML- og CSV-filer i `bygninger/` importeres automatisk til TOML dersom det ikke
|
|
||||||
allerede finnes en TOML-fil med samme navn. Se `bygninger/LES-MEG.md`.
|
|
||||||
|
|
||||||
### Energirapport fra et byggeprosjekt
|
### Energirapport fra et byggeprosjekt
|
||||||
|
|
||||||
Et prosjektlager med sin egen IFC-modell og `bygning.toml` trenger ikke Rust. Det henter
|
Et prosjektlager med sin egen IFC-modell og `bygning.toml` trenger ikke Rust. Det henter
|
||||||
binærfilen fra enoks utgivelse:
|
binærfilen fra enoks siste utgivelse:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: Energirapport
|
name: Energirapport
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# Bygninger
|
|
||||||
|
|
||||||
Legg én fil per bygning her. Byggeserveren regner ut energirapporten og legger PDF, JSON og
|
|
||||||
CSV ved utgivelsen når du setter en tagg som begynner med `v` eller `rapport-`.
|
|
||||||
|
|
||||||
| Filtype | Kommer fra |
|
|
||||||
| -------- | --------------------------------------------------- |
|
|
||||||
| `.toml` | Bygningsbeskrivelse skrevet for hånd, eller importert |
|
|
||||||
| `.ifc` | Vectorworks: *Fil → Eksporter → Export IFC Project* |
|
|
||||||
| `.gbxml` | BIM-verktøy som eksporterer gbXML |
|
|
||||||
| `.csv` | Vectorworks: *Fil → Eksporter → Export Worksheet* |
|
|
||||||
|
|
||||||
IFC-, gbXML- og CSV-filer blir importert til TOML automatisk dersom det ikke allerede finnes
|
|
||||||
en TOML-fil med samme navn. Importen får med geometri, U-verdier og himmelretninger, men ikke
|
|
||||||
ventilasjon, lekkasjetall eller varmekilder. Åpne den importerte TOML-filen, fyll ut det som
|
|
||||||
mangler, og sjekk den inn — da brukes den i stedet for importen neste gang.
|
|
||||||
|
|
||||||
Eksempler på ferdige bygningsbeskrivelser ligger i `../eksempler/`.
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,392 +0,0 @@
|
|||||||
# Nordhusstykket 5, 1825 Tomter — enebolig i tre fra 1948, 1,5 etasje på betong grunnmur.
|
|
||||||
#
|
|
||||||
# Geometri, U-verdier og himmelretninger er importert fra byggets IFC-modell
|
|
||||||
# (Vectorworks Architect 2026, «Nordhusstykket 5.ifc»). Arealene er regnet av geometrien,
|
|
||||||
# og oppføringene er slått sammen per konstruksjon og himmelretning.
|
|
||||||
# Vurderingene av isolasjon og lekkasjer er hentet fra termografirapporten av 10.3.2016.
|
|
||||||
#
|
|
||||||
# Verdier som ikke står i modellen og må kontrolleres på stedet er merket KONTROLLER.
|
|
||||||
|
|
||||||
[bygning]
|
|
||||||
navn = "Nordhusstykket 5"
|
|
||||||
kategori = "smaahus"
|
|
||||||
byggeaar = 1948
|
|
||||||
# Oppvarmet BRA er summen av rommene i 1. og 2. etasje i IFC-modellen:
|
|
||||||
# stue 39,6 + kjøkken 22,6 + entré 10,6 + vindfang 1,2 = 74,0 m² i 1. etasje,
|
|
||||||
# tre soverom 33,9 + gang/trapperom 11,2 + toalett 0,9 = 46,0 m² i 2. etasje.
|
|
||||||
# Kjeller (74,9 m²) og kott (19,6 m²) er uoppvarmede og regnes som egne soner.
|
|
||||||
oppvarmet_bra = 120.0
|
|
||||||
oppvarmet_volum = 288.0
|
|
||||||
adresse = "Nordhusstykket 5, 1825 Tomter"
|
|
||||||
oppdragsgiver = "Ellen Brødholt"
|
|
||||||
beskrivelse = "Enebolig i tre, 1,5 etasje, bebygget areal 99,6 m², uoppvarmet kjeller på betong grunnmur"
|
|
||||||
|
|
||||||
[klima]
|
|
||||||
kommune = "Indre Østfold"
|
|
||||||
|
|
||||||
[grunn]
|
|
||||||
ledningsevne = 2.0
|
|
||||||
|
|
||||||
# ── klimaskjerm for den oppvarmede delen ───────────────────────────────
|
|
||||||
# Ytterveggene er stenderverk med panel ute og porøs huntonitt inne. IFC-modellen oppgir
|
|
||||||
# U-verdi 0,93 W/(m²K), som stemmer med termografirapportens vurdering av at veggen har
|
|
||||||
# noe, men lite isolasjon.
|
|
||||||
|
|
||||||
[[flate]]
|
|
||||||
navn = "Yttervegg nord"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 41.8
|
|
||||||
orientering = 0
|
|
||||||
u_verdi = 0.93
|
|
||||||
masseklasse = "lett"
|
|
||||||
|
|
||||||
[[flate]]
|
|
||||||
navn = "Yttervegg øst"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 54.2
|
|
||||||
orientering = 90
|
|
||||||
u_verdi = 0.93
|
|
||||||
masseklasse = "lett"
|
|
||||||
|
|
||||||
[[flate]]
|
|
||||||
navn = "Yttervegg sør"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 55.7
|
|
||||||
orientering = 180
|
|
||||||
u_verdi = 0.93
|
|
||||||
masseklasse = "lett"
|
|
||||||
|
|
||||||
[[flate]]
|
|
||||||
navn = "Yttervegg vest"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 59.5
|
|
||||||
orientering = 270
|
|
||||||
u_verdi = 0.93
|
|
||||||
masseklasse = "lett"
|
|
||||||
|
|
||||||
# KONTROLLER: IFC-modellen oppgir ingen U-verdi for takflatene. Termografien viser
|
|
||||||
# luftlekkasje til loftet og ut gjennom pipen, og lite isolasjon i skråtaket.
|
|
||||||
[[flate]]
|
|
||||||
navn = "Skråtak"
|
|
||||||
type = "tak"
|
|
||||||
areal = 130.1
|
|
||||||
helning = 40
|
|
||||||
u_verdi = 0.55
|
|
||||||
masseklasse = "lett"
|
|
||||||
kommentar = "Areal fra IFC; U-verdi anslått etter termografirapporten"
|
|
||||||
|
|
||||||
[[flate]]
|
|
||||||
navn = "Himling mot kaldt loft"
|
|
||||||
type = "tak"
|
|
||||||
areal = 32.6
|
|
||||||
helning = 0
|
|
||||||
u_verdi = 0.45
|
|
||||||
mot = "uoppvarmet"
|
|
||||||
b_faktor = 0.9
|
|
||||||
masseklasse = "lett"
|
|
||||||
|
|
||||||
# Etasjeskilleren mot kjelleren. IFC-modellen har 150,4 m² «Etasjeskille», som dekker både
|
|
||||||
# gulvet over kjelleren og skillet mellom 1. og 2. etasje. Halvparten ligger over kjelleren,
|
|
||||||
# og stemmer med kjellergulvets 78,9 m².
|
|
||||||
[[flate]]
|
|
||||||
navn = "Etasjeskiller mot kjeller"
|
|
||||||
type = "gulv"
|
|
||||||
areal = 75.0
|
|
||||||
u_verdi = 0.31
|
|
||||||
mot = "uoppvarmet"
|
|
||||||
sone = "Kjeller"
|
|
||||||
masseklasse = "middels"
|
|
||||||
|
|
||||||
# ── vinduer og dører ───────────────────────────────────────────────────
|
|
||||||
# Areal og himmelretning fra IFC. KONTROLLER: modellen oppgir ingen U-verdi for vinduene.
|
|
||||||
# Termografirapporten beskriver dem som dårlig isolerte, og 2,8 W/(m²K) svarer til
|
|
||||||
# eldre tolagsglass. Med originale enkle glass ville verdien vært nærmere 5.
|
|
||||||
|
|
||||||
[[vindu]]
|
|
||||||
navn = "Vinduer nord"
|
|
||||||
areal = 5.58
|
|
||||||
orientering = 0
|
|
||||||
u_verdi = 2.80
|
|
||||||
g_verdi = 0.76
|
|
||||||
|
|
||||||
[[vindu]]
|
|
||||||
navn = "Vinduer øst"
|
|
||||||
areal = 2.86
|
|
||||||
orientering = 90
|
|
||||||
u_verdi = 2.80
|
|
||||||
g_verdi = 0.76
|
|
||||||
|
|
||||||
[[vindu]]
|
|
||||||
navn = "Vinduer sør"
|
|
||||||
areal = 3.96
|
|
||||||
orientering = 180
|
|
||||||
u_verdi = 2.80
|
|
||||||
g_verdi = 0.76
|
|
||||||
|
|
||||||
[[vindu]]
|
|
||||||
navn = "Vinduer vest"
|
|
||||||
areal = 1.05
|
|
||||||
orientering = 270
|
|
||||||
u_verdi = 2.80
|
|
||||||
g_verdi = 0.76
|
|
||||||
|
|
||||||
[[vindu]]
|
|
||||||
navn = "Inngangsdør med vindfang"
|
|
||||||
areal = 1.46
|
|
||||||
orientering = 270
|
|
||||||
u_verdi = 2.00
|
|
||||||
dor = true
|
|
||||||
|
|
||||||
[[vindu]]
|
|
||||||
navn = "Verandadør øst"
|
|
||||||
areal = 2.94
|
|
||||||
orientering = 90
|
|
||||||
u_verdi = 2.00
|
|
||||||
dor = true
|
|
||||||
|
|
||||||
[[vindu]]
|
|
||||||
navn = "Balkongdør sør"
|
|
||||||
areal = 1.91
|
|
||||||
orientering = 180
|
|
||||||
u_verdi = 2.00
|
|
||||||
dor = true
|
|
||||||
|
|
||||||
# Utkragede bjelker som bærer balkongen er en tydelig kuldebro i termografien.
|
|
||||||
[kuldebroer]
|
|
||||||
normalisert = 0.15
|
|
||||||
|
|
||||||
# KONTROLLER: lekkasjetallet er ikke målt. Termografien viser luftlekkasje til loft og
|
|
||||||
# gjennom pipen, som er typisk for et utett hus fra 1948.
|
|
||||||
[infiltrasjon]
|
|
||||||
n50 = 8.0
|
|
||||||
skjerming = "moderat"
|
|
||||||
|
|
||||||
[ventilasjon]
|
|
||||||
type = "naturlig"
|
|
||||||
luftmengde = 1.2
|
|
||||||
|
|
||||||
[tappevann]
|
|
||||||
system = "aapent"
|
|
||||||
|
|
||||||
# ── uoppvarmet kjeller ─────────────────────────────────────────────────
|
|
||||||
# Kjelleren brukes ikke til opphold, men varmes av uisolerte varmerør og av kjelen som står
|
|
||||||
# i det tekniske rommet. Temperaturen regnes ut time for time av kjellerens varmebalanse,
|
|
||||||
# slik at rørtapet både teller som tap fra varmeanlegget og som varme som holder
|
|
||||||
# etasjeskilleren varm. Det var dette termografirapporten pekte på som det største funnet.
|
|
||||||
|
|
||||||
[[sone]]
|
|
||||||
navn = "Kjeller"
|
|
||||||
volum = 165.0
|
|
||||||
luftveksling = 0.4
|
|
||||||
# Alt distribusjonstapet fra det vannbårne anlegget avgis her, og berederen står her.
|
|
||||||
andel_distribusjonstap = 1.0
|
|
||||||
andel_tappevannstap = 1.0
|
|
||||||
# KONTROLLER: anslått kappetap fra kjelen.
|
|
||||||
fast_effekt = 100.0
|
|
||||||
kommentar = "Rom 21,1 + gang 11,1 + vaskerom 9,9 + 11,1 + boder 10,4 + 9,3 + teknisk rom 2,2 = 74,9 m²"
|
|
||||||
|
|
||||||
# Grunnmuren. KONTROLLER: høyden over terreng er anslått til 0,6 m av fasadebildene i
|
|
||||||
# termografirapporten, og kjellerhøyden til 2,2 m. Disse to tallene avgjør hvor mye av
|
|
||||||
# muren som taper varme rett til uteluft, og er derfor verdt å måle på stedet.
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellermur 30 cm uisolert, nord"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 21.2
|
|
||||||
orientering = 0
|
|
||||||
u_verdi = 3.13
|
|
||||||
mot = "grunn"
|
|
||||||
dybde = 1.6
|
|
||||||
over_terreng = 0.6
|
|
||||||
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellermur 30 cm uisolert, øst"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 9.7
|
|
||||||
orientering = 90
|
|
||||||
u_verdi = 3.13
|
|
||||||
mot = "grunn"
|
|
||||||
dybde = 1.6
|
|
||||||
over_terreng = 0.6
|
|
||||||
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellermur 30 cm uisolert, sør"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 22.7
|
|
||||||
orientering = 180
|
|
||||||
u_verdi = 3.13
|
|
||||||
mot = "grunn"
|
|
||||||
dybde = 1.6
|
|
||||||
over_terreng = 0.6
|
|
||||||
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellermur 30 cm uisolert, vest"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 15.0
|
|
||||||
orientering = 270
|
|
||||||
u_verdi = 3.13
|
|
||||||
mot = "grunn"
|
|
||||||
dybde = 1.6
|
|
||||||
over_terreng = 0.6
|
|
||||||
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellermur 25 cm uisolert"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 40.7
|
|
||||||
orientering = 90
|
|
||||||
u_verdi = 3.39
|
|
||||||
mot = "grunn"
|
|
||||||
dybde = 1.6
|
|
||||||
over_terreng = 0.6
|
|
||||||
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellermur med treullsement"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 2.2
|
|
||||||
orientering = 90
|
|
||||||
u_verdi = 0.46
|
|
||||||
mot = "grunn"
|
|
||||||
dybde = 1.6
|
|
||||||
over_terreng = 0.6
|
|
||||||
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellergulv"
|
|
||||||
type = "gulv"
|
|
||||||
areal = 78.9
|
|
||||||
u_verdi = 3.85
|
|
||||||
mot = "grunn"
|
|
||||||
omkrets = 40.0
|
|
||||||
dybde = 2.2
|
|
||||||
|
|
||||||
# Kjellervinduene. Termografien viser stort varmetap gjennom kjellervinduet.
|
|
||||||
[[sone.flate]]
|
|
||||||
navn = "Kjellervinduer"
|
|
||||||
type = "vegg"
|
|
||||||
areal = 1.8
|
|
||||||
orientering = 180
|
|
||||||
u_verdi = 4.50
|
|
||||||
mot = "ute"
|
|
||||||
kommentar = "KONTROLLER: antall og størrelse; enkle glass i støpt karm"
|
|
||||||
|
|
||||||
# ── varmeforsyning ─────────────────────────────────────────────────────
|
|
||||||
# Vannbårent radiatoranlegg med elektrokjel i kjelleren, supplert med vedovn.
|
|
||||||
|
|
||||||
[[oppvarming.kilde]]
|
|
||||||
type = "vedovn"
|
|
||||||
andel_rom = 0.25
|
|
||||||
lukket = false
|
|
||||||
kommentar = "Åpent ildsted; energimerkeforskriften regner inntil 10 % av romoppvarmingen"
|
|
||||||
|
|
||||||
[[oppvarming.kilde]]
|
|
||||||
type = "el_kjel"
|
|
||||||
andel_rom = 0.75
|
|
||||||
andel_tappevann = 1.0
|
|
||||||
|
|
||||||
# Uisolerte varmerør gjennom den uoppvarmede kjelleren er hovedgrunnen til at kjelleren
|
|
||||||
# er varm. Termografirapporten anbefaler å isolere dem.
|
|
||||||
[distribusjon]
|
|
||||||
type = "radiator"
|
|
||||||
turtemperatur = 60
|
|
||||||
tap_andel = 0.25
|
|
||||||
roer_uoppvarmet = 1.0
|
|
||||||
|
|
||||||
# ── tiltak ─────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
[[scenario]]
|
|
||||||
navn = "Isolerte varmerør"
|
|
||||||
beskrivelse = "Isolering av varmerørene gjennom kjelleren, som termografirapporten anbefaler. Rørtapet faller, men kjelleren blir kaldere, så noe av gevinsten spises opp av økt varmetap gjennom etasjeskilleren."
|
|
||||||
endringer.distribusjon = { tap_andel = 0.06 }
|
|
||||||
|
|
||||||
[[scenario]]
|
|
||||||
navn = "Isolerte kjellervegger"
|
|
||||||
beskrivelse = "Innvendig isolering av grunnmuren til U-verdi 0,35 W/(m²K)."
|
|
||||||
endringer.sone = [{ navn = "Kjeller", flate = [
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, nord", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, øst", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, sør", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, vest", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 25 cm uisolert", u_verdi = 0.35 },
|
|
||||||
] }]
|
|
||||||
|
|
||||||
[[scenario]]
|
|
||||||
navn = "Isolerte rør og kjellervegger"
|
|
||||||
beskrivelse = "Begge kjellertiltakene samlet, slik termografirapporten anbefaler dem."
|
|
||||||
endringer.distribusjon = { tap_andel = 0.06 }
|
|
||||||
endringer.sone = [{ navn = "Kjeller", flate = [
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, nord", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, øst", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, sør", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, vest", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 25 cm uisolert", u_verdi = 0.35 },
|
|
||||||
] }]
|
|
||||||
|
|
||||||
[[scenario]]
|
|
||||||
navn = "Nye vinduer"
|
|
||||||
beskrivelse = "Utskifting til trelags energiglass med U-verdi 0,8 W/(m²K)."
|
|
||||||
endringer.vindu = [
|
|
||||||
{ navn = "Vinduer nord", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Vinduer øst", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Vinduer sør", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Vinduer vest", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Balkongdør sør", u_verdi = 1.20 },
|
|
||||||
{ navn = "Verandadør øst", u_verdi = 1.20 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[scenario]]
|
|
||||||
navn = "Etterisolert klimaskjerm"
|
|
||||||
beskrivelse = "Etterisolering av yttervegger til 20 cm og loft til 30 cm, med tetting av luftlekkasjer."
|
|
||||||
endringer.flate = [
|
|
||||||
{ navn = "Yttervegg nord", u_verdi = 0.22 },
|
|
||||||
{ navn = "Yttervegg øst", u_verdi = 0.22 },
|
|
||||||
{ navn = "Yttervegg sør", u_verdi = 0.22 },
|
|
||||||
{ navn = "Yttervegg vest", u_verdi = 0.22 },
|
|
||||||
{ navn = "Skråtak", u_verdi = 0.18 },
|
|
||||||
{ navn = "Himling mot kaldt loft", u_verdi = 0.15 },
|
|
||||||
]
|
|
||||||
endringer.infiltrasjon = { n50 = 4.0 }
|
|
||||||
endringer.kuldebroer = { normalisert = 0.10 }
|
|
||||||
|
|
||||||
[[scenario]]
|
|
||||||
navn = "Væske-vann-varmepumpe"
|
|
||||||
beskrivelse = "Bergvarmepumpe på det vannbårne anlegget, med elektrokjel som spisslast."
|
|
||||||
endringer.oppvarming.kilde = [
|
|
||||||
{ type = "vedovn", slett = true },
|
|
||||||
{ type = "vaeske_vann_vp", andel_rom = 0.9, andel_tappevann = 0.8, turtemperatur = 55 },
|
|
||||||
{ type = "el_kjel", andel_rom = 0.1, andel_tappevann = 0.2 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[scenario]]
|
|
||||||
navn = "Full pakke"
|
|
||||||
beskrivelse = "Alle tiltakene samlet: isolerte rør og kjellervegger, nye vinduer, etterisolert klimaskjerm, balansert ventilasjon med varmegjenvinning og bergvarmepumpe."
|
|
||||||
endringer.distribusjon = { tap_andel = 0.06, turtemperatur = 45 }
|
|
||||||
endringer.infiltrasjon = { n50 = 2.5 }
|
|
||||||
endringer.kuldebroer = { normalisert = 0.09 }
|
|
||||||
endringer.ventilasjon = { type = "balansert", gjenvinning = 0.80, sfp = 1.5 }
|
|
||||||
endringer.flate = [
|
|
||||||
{ navn = "Yttervegg nord", u_verdi = 0.22 },
|
|
||||||
{ navn = "Yttervegg øst", u_verdi = 0.22 },
|
|
||||||
{ navn = "Yttervegg sør", u_verdi = 0.22 },
|
|
||||||
{ navn = "Yttervegg vest", u_verdi = 0.22 },
|
|
||||||
{ navn = "Skråtak", u_verdi = 0.18 },
|
|
||||||
{ navn = "Himling mot kaldt loft", u_verdi = 0.15 },
|
|
||||||
{ navn = "Etasjeskiller mot kjeller", u_verdi = 0.18 },
|
|
||||||
]
|
|
||||||
endringer.vindu = [
|
|
||||||
{ navn = "Vinduer nord", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Vinduer øst", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Vinduer sør", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Vinduer vest", u_verdi = 0.80, g_verdi = 0.50 },
|
|
||||||
{ navn = "Balkongdør sør", u_verdi = 1.20 },
|
|
||||||
{ navn = "Verandadør øst", u_verdi = 1.20 },
|
|
||||||
]
|
|
||||||
endringer.sone = [{ navn = "Kjeller", flate = [
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, nord", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, øst", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, sør", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 30 cm uisolert, vest", u_verdi = 0.35 },
|
|
||||||
{ navn = "Kjellermur 25 cm uisolert", u_verdi = 0.35 },
|
|
||||||
] }]
|
|
||||||
endringer.oppvarming.kilde = [
|
|
||||||
{ type = "vedovn", slett = true },
|
|
||||||
{ type = "vaeske_vann_vp", andel_rom = 0.9, andel_tappevann = 0.8, turtemperatur = 45 },
|
|
||||||
{ type = "el_kjel", andel_rom = 0.1, andel_tappevann = 0.2 },
|
|
||||||
]
|
|
||||||
+952
-43
File diff suppressed because it is too large
Load Diff
+22
-5
@@ -112,11 +112,14 @@ pub fn hent_klima(b: &Bygning) -> Result<(Vaerdata, String, bool)> {
|
|||||||
== crate::data::normaliser_stedsnavn(REFERANSEKLIMA);
|
== crate::data::normaliser_stedsnavn(REFERANSEKLIMA);
|
||||||
return Ok((v, navn, er_referanse));
|
return Ok((v, navn, er_referanse));
|
||||||
}
|
}
|
||||||
let sted = b
|
// Uten kommune, men med posisjon, velges klimastedet nærmest posisjonen.
|
||||||
.klima
|
let sted = match (&b.klima.kommune, b.klima.posisjon) {
|
||||||
.kommune
|
(Some(k), _) => k.clone(),
|
||||||
.clone()
|
(None, Some([bredde, lengde])) => {
|
||||||
.unwrap_or_else(|| REFERANSEKLIMA.to_string());
|
tmy::naermeste_kommune(&crate::klima::geo::Posisjon { bredde, lengde })?.0
|
||||||
|
}
|
||||||
|
(None, None) => REFERANSEKLIMA.to_string(),
|
||||||
|
};
|
||||||
let er_referanse = crate::data::normaliser_stedsnavn(&sted)
|
let er_referanse = crate::data::normaliser_stedsnavn(&sted)
|
||||||
== crate::data::normaliser_stedsnavn(REFERANSEKLIMA);
|
== crate::data::normaliser_stedsnavn(REFERANSEKLIMA);
|
||||||
let v = tmy::for_kommune(&sted)?;
|
let v = tmy::for_kommune(&sted)?;
|
||||||
@@ -140,6 +143,20 @@ pub fn kjor_med_klima(
|
|||||||
let varmetap = Varmetapsbudsjett::beregn(&lost);
|
let varmetap = Varmetapsbudsjett::beregn(&lost);
|
||||||
let systemer_oppsett = systemer::sett_opp_systemer(b, &lost, kilde)?;
|
let systemer_oppsett = systemer::sett_opp_systemer(b, &lost, kilde)?;
|
||||||
let mut merknader = systemer_oppsett.merknader.clone();
|
let mut merknader = systemer_oppsett.merknader.clone();
|
||||||
|
if b.klima.kommune.is_none() && b.klima.epw.is_none() {
|
||||||
|
if let Some([bredde, lengde]) = b.klima.posisjon {
|
||||||
|
let bygg = crate::klima::geo::Posisjon { bredde, lengde };
|
||||||
|
let stasjon = crate::klima::geo::Posisjon {
|
||||||
|
bredde: vaerdata.bredde,
|
||||||
|
lengde: vaerdata.lengde,
|
||||||
|
};
|
||||||
|
merknader.push(format!(
|
||||||
|
"Klimastedet {klimanavn} er valgt ut fra posisjonen {bredde:.4}° nord, {lengde:.4}° øst, \
|
||||||
|
{:.0} km unna. Sett `kommune` under [klima] for å velge et annet.",
|
||||||
|
bygg.avstand_km(&stasjon)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
merknader.extend(
|
merknader.extend(
|
||||||
crate::modell::kontroll::kontroller(b)
|
crate::modell::kontroll::kontroller(b)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
//! Posisjon fra BIM-modellen, og valg av klimasted ut fra den.
|
||||||
|
//!
|
||||||
|
//! En IFC-modell kan oppgi hvor bygningen står på to måter: som breddegrad og lengdegrad på
|
||||||
|
//! `IfcSite`, eller som prosjiserte koordinater gjennom `IfcMapConversion` og
|
||||||
|
//! `IfcProjectedCRS`. I Norge er det siste normalt EUREF89 UTM sone 32, 33 eller 35. Denne
|
||||||
|
//! modulen gjør begge om til breddegrad og lengdegrad, slik at nærmeste klimasted kan velges
|
||||||
|
//! automatisk.
|
||||||
|
|
||||||
|
use std::f64::consts::FRAC_PI_2;
|
||||||
|
|
||||||
|
/// Store halvakse for GRS80, som EUREF89 bygger på, m.
|
||||||
|
const A: f64 = 6_378_137.0;
|
||||||
|
/// Flattrykning for GRS80.
|
||||||
|
const F: f64 = 1.0 / 298.257_222_101;
|
||||||
|
/// Målestokkfaktor langs sentralmeridianen i UTM.
|
||||||
|
const K0: f64 = 0.9996;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct Posisjon {
|
||||||
|
/// Breddegrad, grader nord.
|
||||||
|
pub bredde: f64,
|
||||||
|
/// Lengdegrad, grader øst.
|
||||||
|
pub lengde: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Posisjon {
|
||||||
|
/// Avstand langs jordoverflaten, km.
|
||||||
|
pub fn avstand_km(&self, annen: &Posisjon) -> f64 {
|
||||||
|
let (b1, b2) = (self.bredde.to_radians(), annen.bredde.to_radians());
|
||||||
|
let db = b2 - b1;
|
||||||
|
let dl = (annen.lengde - self.lengde).to_radians();
|
||||||
|
let h = (db / 2.0).sin().powi(2) + b1.cos() * b2.cos() * (dl / 2.0).sin().powi(2);
|
||||||
|
2.0 * 6371.0 * h.sqrt().asin()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sant for plasseringen Vectorworks setter når prosjektstedet ikke er angitt, og for
|
||||||
|
/// nullpunktet. Begge betyder at modellen i praksis ikke oppgir noen posisjon.
|
||||||
|
pub fn er_standardplassering(&self) -> bool {
|
||||||
|
let washington =
|
||||||
|
(self.bredde - 38.8895).abs() < 0.05 && (self.lengde + 77.0353).abs() < 0.05;
|
||||||
|
let null = self.bredde.abs() < 1e-6 && self.lengde.abs() < 1e-6;
|
||||||
|
washington || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Grovt: innenfor fastlands-Norge og Svalbard.
|
||||||
|
pub fn i_norge(&self) -> bool {
|
||||||
|
(57.5..=81.5).contains(&self.bredde) && (4.0..=32.0).contains(&self.lengde)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fra Web Mercator (EPSG:3857), som Vectorworks bruker når ingen annen projeksjon er valgt.
|
||||||
|
pub fn fra_web_mercator(x: f64, y: f64) -> Posisjon {
|
||||||
|
Posisjon {
|
||||||
|
bredde: (2.0 * (y / A).exp().atan() - FRAC_PI_2).to_degrees(),
|
||||||
|
lengde: (x / A).to_degrees(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fra UTM på nordlige halvkule, med GRS80-ellipsoiden som EUREF89 bruker.
|
||||||
|
///
|
||||||
|
/// Inversformlene er de vanlige seriene etter Snyder, «Map Projections – A Working Manual»,
|
||||||
|
/// som gir centimeternøyaktighet innenfor en sone.
|
||||||
|
pub fn fra_utm(sone: u32, x: f64, y: f64) -> Posisjon {
|
||||||
|
let e2 = F * (2.0 - F);
|
||||||
|
let ep2 = e2 / (1.0 - e2);
|
||||||
|
let e1 = (1.0 - (1.0 - e2).sqrt()) / (1.0 + (1.0 - e2).sqrt());
|
||||||
|
|
||||||
|
let m = y / K0;
|
||||||
|
let mu = m / (A * (1.0 - e2 / 4.0 - 3.0 * e2 * e2 / 64.0 - 5.0 * e2.powi(3) / 256.0));
|
||||||
|
let fi1 = mu
|
||||||
|
+ (3.0 * e1 / 2.0 - 27.0 * e1.powi(3) / 32.0) * (2.0 * mu).sin()
|
||||||
|
+ (21.0 * e1 * e1 / 16.0 - 55.0 * e1.powi(4) / 32.0) * (4.0 * mu).sin()
|
||||||
|
+ (151.0 * e1.powi(3) / 96.0) * (6.0 * mu).sin()
|
||||||
|
+ (1097.0 * e1.powi(4) / 512.0) * (8.0 * mu).sin();
|
||||||
|
|
||||||
|
let (s, c, t) = (fi1.sin(), fi1.cos(), fi1.tan());
|
||||||
|
let n1 = A / (1.0 - e2 * s * s).sqrt();
|
||||||
|
let t1 = t * t;
|
||||||
|
let c1 = ep2 * c * c;
|
||||||
|
let r1 = A * (1.0 - e2) / (1.0 - e2 * s * s).powf(1.5);
|
||||||
|
let d = (x - 500_000.0) / (n1 * K0);
|
||||||
|
|
||||||
|
let bredde = fi1
|
||||||
|
- (n1 * t / r1)
|
||||||
|
* (d * d / 2.0
|
||||||
|
- (5.0 + 3.0 * t1 + 10.0 * c1 - 4.0 * c1 * c1 - 9.0 * ep2) * d.powi(4) / 24.0
|
||||||
|
+ (61.0 + 90.0 * t1 + 298.0 * c1 + 45.0 * t1 * t1 - 252.0 * ep2 - 3.0 * c1 * c1)
|
||||||
|
* d.powi(6)
|
||||||
|
/ 720.0);
|
||||||
|
let sentralmeridian = (sone as f64 - 1.0) * 6.0 - 180.0 + 3.0;
|
||||||
|
let lengde = sentralmeridian
|
||||||
|
+ ((d - (1.0 + 2.0 * t1 + c1) * d.powi(3) / 6.0
|
||||||
|
+ (5.0 - 2.0 * c1 + 28.0 * t1 - 3.0 * c1 * c1 + 8.0 * ep2 + 24.0 * t1 * t1)
|
||||||
|
* d.powi(5)
|
||||||
|
/ 120.0)
|
||||||
|
/ c)
|
||||||
|
.to_degrees();
|
||||||
|
Posisjon {
|
||||||
|
bredde: bredde.to_degrees(),
|
||||||
|
lengde,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gjør koordinater i et navngitt koordinatsystem om til posisjon.
|
||||||
|
///
|
||||||
|
/// `navn` er slik det står i `IfcProjectedCRS`, for eksempel «EPSG:25832». Koordinatsystemer
|
||||||
|
/// enok ikke kjenner gir `None`, og da brukes ikke posisjonen.
|
||||||
|
pub fn fra_koordinatsystem(navn: &str, x: f64, y: f64) -> Option<Posisjon> {
|
||||||
|
let stor = navn.to_uppercase();
|
||||||
|
let kode: Option<u32> = stor.find("EPSG").and_then(|i| {
|
||||||
|
let siffer: String = stor[i + 4..]
|
||||||
|
.chars()
|
||||||
|
.skip_while(|c| !c.is_ascii_digit())
|
||||||
|
.take_while(|c| c.is_ascii_digit())
|
||||||
|
.collect();
|
||||||
|
siffer.parse().ok()
|
||||||
|
});
|
||||||
|
match kode {
|
||||||
|
Some(3857) | Some(900_913) => Some(fra_web_mercator(x, y)),
|
||||||
|
// Geografiske koordinater: østverdien er lengdegrad og nordverdien breddegrad.
|
||||||
|
Some(4326) | Some(4258) => Some(Posisjon {
|
||||||
|
bredde: y,
|
||||||
|
lengde: x,
|
||||||
|
}),
|
||||||
|
// ETRS89 / EUREF89 UTM sone 28–38.
|
||||||
|
Some(k @ 25828..=25838) => Some(fra_utm(k - 25800, x, y)),
|
||||||
|
// WGS 84 UTM nord, sone 28–38.
|
||||||
|
Some(k @ 32628..=32638) => Some(fra_utm(k - 32600, x, y)),
|
||||||
|
// ETRS89 / UTM med NN2000-høyder, som Kartverket bruker: 5972 er sone 32, 5973 sone 33,
|
||||||
|
// 5975 sone 35.
|
||||||
|
Some(k @ 5972..=5976) => Some(fra_utm(k - 5940, x, y)),
|
||||||
|
_ => {
|
||||||
|
// Uten EPSG-kode: kjenn igjen «UTM32», «UTM 33N» og lignende.
|
||||||
|
let i = stor.find("UTM")?;
|
||||||
|
let sone: String = stor[i + 3..]
|
||||||
|
.chars()
|
||||||
|
.skip_while(|c| !c.is_ascii_digit())
|
||||||
|
.take_while(|c| c.is_ascii_digit())
|
||||||
|
.collect();
|
||||||
|
let sone: u32 = sone.parse().ok()?;
|
||||||
|
(1..=60).contains(&sone).then(|| fra_utm(sone, x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tester {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn web_mercator_fra_vectorworks_gir_washington() {
|
||||||
|
// Verdiene i IfcMapConversion når prosjektstedet ikke er satt i Vectorworks.
|
||||||
|
let p = fra_web_mercator(-8_575_524.352_288_59, 4_705_852.257_469_73);
|
||||||
|
assert!((p.bredde - 38.8895).abs() < 1e-3, "bredde {}", p.bredde);
|
||||||
|
assert!((p.lengde + 77.0352).abs() < 1e-3, "lengde {}", p.lengde);
|
||||||
|
assert!(p.er_standardplassering());
|
||||||
|
assert!(!p.i_norge());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Referanseverdier fra pyproj (PROJ), transformert fra EPSG:4326.
|
||||||
|
#[test]
|
||||||
|
fn utm_og_web_mercator_stemmer_med_proj() {
|
||||||
|
// (sone, øst, nord, breddegrad, lengdegrad, toleranse i grader)
|
||||||
|
//
|
||||||
|
// Innenfor sonen og et stykke utenfor er avviket under 1e-5 grader, om lag én meter.
|
||||||
|
// Tromsø i sone 32 ligger nesten 10 grader utenfor sonen, der rekkeutviklingen gir
|
||||||
|
// om lag 1,3 m avvik. Det er langt innenfor hva valg av klimasted trenger, men viser
|
||||||
|
// hvor grensen går.
|
||||||
|
let utm = [
|
||||||
|
(32, 613_351.291, 6_611_921.980, 59.6300, 11.0100, 1e-5),
|
||||||
|
(32, 596_946.981, 6_642_824.393, 59.9115, 10.7336, 1e-5),
|
||||||
|
(33, 275_055.615, 6_616_968.071, 59.6300, 11.0100, 1e-5),
|
||||||
|
(33, 653_421.188, 7_731_721.083, 69.6492, 18.9553, 1e-5),
|
||||||
|
(35, 188_546.727, 7_747_295.774, 69.6492, 18.9553, 1e-5),
|
||||||
|
(32, 884_909.622, 7_758_204.184, 69.6492, 18.9553, 1e-4),
|
||||||
|
];
|
||||||
|
for (sone, x, y, b, l, toleranse) in utm {
|
||||||
|
let p = fra_utm(sone, x, y);
|
||||||
|
assert!(
|
||||||
|
(p.bredde - b).abs() < toleranse && (p.lengde - l).abs() < toleranse,
|
||||||
|
"sone {sone} ({x}, {y}) ga {p:?}, forventet {b}, {l}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let p = fra_web_mercator(1_225_627.594, 8_317_818.189);
|
||||||
|
assert!(
|
||||||
|
(p.bredde - 59.63).abs() < 1e-6 && (p.lengde - 11.01).abs() < 1e-6,
|
||||||
|
"{p:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sentralmeridianen_gir_eksakt_lengdegrad() {
|
||||||
|
// Østverdi 500 000 ligger på sonens sentralmeridian, 9° øst for sone 32.
|
||||||
|
let p = fra_utm(32, 500_000.0, 6_600_000.0);
|
||||||
|
assert!((p.lengde - 9.0).abs() < 1e-9, "lengde {}", p.lengde);
|
||||||
|
let q = fra_utm(33, 500_000.0, 6_600_000.0);
|
||||||
|
assert!((q.lengde - 15.0).abs() < 1e-9, "lengde {}", q.lengde);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ekvator_paa_sentralmeridianen() {
|
||||||
|
let p = fra_utm(32, 500_000.0, 0.0);
|
||||||
|
assert!(p.bredde.abs() < 1e-9 && (p.lengde - 9.0).abs() < 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ukjent_koordinatsystem_gir_ingen_posisjon() {
|
||||||
|
assert!(fra_koordinatsystem("EPSG:2154", 650_000.0, 6_860_000.0).is_none());
|
||||||
|
assert!(fra_koordinatsystem("lokalt", 1.0, 2.0).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epsg_koder_tolkes() {
|
||||||
|
let a = fra_koordinatsystem("EPSG:25832", 500_000.0, 6_600_000.0).unwrap();
|
||||||
|
let b = fra_koordinatsystem("ETRS89 / UTM zone 32N", 500_000.0, 6_600_000.0).unwrap();
|
||||||
|
assert!((a.lengde - 9.0).abs() < 1e-9 && (b.lengde - 9.0).abs() < 1e-9);
|
||||||
|
let c = fra_koordinatsystem("EPSG:5973", 500_000.0, 6_600_000.0).unwrap();
|
||||||
|
assert!(
|
||||||
|
(c.lengde - 15.0).abs() < 1e-9,
|
||||||
|
"5973 er sone 33, fikk {}",
|
||||||
|
c.lengde
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Klimadata: TMY-filer (EPW) for norske kommuner, solberegning og klimakorrigering.
|
//! Klimadata: TMY-filer (EPW) for norske kommuner, solberegning og klimakorrigering.
|
||||||
|
|
||||||
pub mod epw;
|
pub mod epw;
|
||||||
|
pub mod geo;
|
||||||
pub mod korreksjon;
|
pub mod korreksjon;
|
||||||
pub mod sol;
|
pub mod sol;
|
||||||
pub mod tmy;
|
pub mod tmy;
|
||||||
|
|||||||
@@ -76,6 +76,36 @@ fn kommune_av_filnavn(navn: &str) -> Option<String> {
|
|||||||
Some(base.to_string())
|
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
|
/// Henter værdata for en kommune. Navnet sammenlignes normalisert, slik at både
|
||||||
/// «Indre Østfold» og «Indre Ostfold» treffer samme fil.
|
/// «Indre Østfold» og «Indre Ostfold» treffer samme fil.
|
||||||
pub fn for_kommune(kommune: &str) -> Result<Vaerdata> {
|
pub fn for_kommune(kommune: &str) -> Result<Vaerdata> {
|
||||||
|
|||||||
+6
-1
@@ -152,7 +152,12 @@ fn beregn(a: Beregn, kilde: &Datakilde) -> Result<()> {
|
|||||||
eprintln!("Rapport: {}", pdf.display());
|
eprintln!("Rapport: {}", pdf.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
for m in &resultat.merknader {
|
// Manglene er allerede skrevet ut før beregningen; her kommer bare det som kom i tillegg.
|
||||||
|
let alt_sagt: Vec<String> = enok::modell::kontroll::kontroller(&bygning)
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| m.melding)
|
||||||
|
.collect();
|
||||||
|
for m in resultat.merknader.iter().filter(|m| !alt_sagt.contains(m)) {
|
||||||
eprintln!(" ! {m}");
|
eprintln!(" ! {m}");
|
||||||
}
|
}
|
||||||
skriv_sammendrag(&resultat, &scenarier);
|
skriv_sammendrag(&resultat, &scenarier);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ pub fn kontroller(b: &Bygning) -> Vec<Mangel> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.klima.kommune.is_none() && b.klima.epw.is_none() {
|
if b.klima.kommune.is_none() && b.klima.epw.is_none() && b.klima.posisjon.is_none() {
|
||||||
ut.push(Mangel {
|
ut.push(Mangel {
|
||||||
felt: "klima.kommune",
|
felt: "klima.kommune",
|
||||||
melding: "Kommune er ikke oppgitt, så beregningen bruker referanseklimaet Oslo. \
|
melding: "Kommune er ikke oppgitt, så beregningen bruker referanseklimaet Oslo. \
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ fn en() -> u32 {
|
|||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
pub struct Klima {
|
pub struct Klima {
|
||||||
|
/// Byggets posisjon som [breddegrad, lengdegrad]. Er kommune ikke oppgitt, velges
|
||||||
|
/// klimastedet nærmest denne posisjonen.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub posisjon: Option<[f64; 2]>,
|
||||||
/// Kommune for lokalt klima (TMY-fil fra Zenodo 14779899). Utelatt: bare Oslo.
|
/// Kommune for lokalt klima (TMY-fil fra Zenodo 14779899). Utelatt: bare Oslo.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub kommune: Option<String>,
|
pub kommune: Option<String>,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//! Kontroll av valg av klimasted. Testene bruker TMY-arkivet og laster det ned ved første kjøring.
|
||||||
|
|
||||||
|
use enok::data::normaliser_stedsnavn;
|
||||||
|
use enok::klima::geo::Posisjon;
|
||||||
|
use enok::klima::tmy::naermeste_kommune;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn naermeste_klimasted_for_tomter_er_indre_ostfold() {
|
||||||
|
let (kommune, km) = naermeste_kommune(&Posisjon {
|
||||||
|
bredde: 59.63,
|
||||||
|
lengde: 11.01,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
normaliser_stedsnavn(&kommune),
|
||||||
|
"indreostfold",
|
||||||
|
"fikk {kommune}"
|
||||||
|
);
|
||||||
|
assert!(km < 25.0, "{km} km");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn naermeste_klimasted_for_oslo_raadhus_er_oslo() {
|
||||||
|
let (kommune, _) = naermeste_kommune(&Posisjon {
|
||||||
|
bredde: 59.9115,
|
||||||
|
lengde: 10.7336,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(normaliser_stedsnavn(&kommune), "oslo", "fikk {kommune}");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user