Sonnenseite des Lebens (Linux-Magazin, Oktober 2026)

Wer eine neue Wohnung sucht, sollte darauf achten, dass die zukünftige Bleibe in einem sonnigen Stadtteil liegt. Mike Schilli holt mit Go historische Wetterdaten beliebter Städte vom Wunderground-Server und trägt sie auf der Landkarte ein.

Niemand will in einem Nebelloch wohnen, oder in einer Gegend, in der der Himmel dauernd grau in grau ist. Blauer Himmel und Sonnenschein, das ist Lebensqualität! Auch Makler-Listings betonen oft "helle" Wohnungen, aber ob die Lage überhaupt viel Sonne hergibt, darüber finden Interessenten oft nichts. Ja, es soll sogar schon vorgekommen sein, dass manche Käufer Immobilien ungesehen, nur aufgrund von Internet-Listings erwarben! Das ist regelrecht frevelhaft, denn eine kurze Anfrage an den Wetter-Server des Weather-Underground-Netzwerks fördert historische Wetterdaten zutage, und stellt kompromisslos fest, ob die Immobilie in einem Nebelloch oder einem sonnigen Stadtteil liegt.

Wetter-Untergrund

Der Weather-Underground ([3]) aggregiert laut seiner Homepage Live-Wetterdaten von mehr als 250.000 privaten Wetterstationen (Personal Weather Station, PWS). Wie meine eigene PWS auf unserem Hausdach in San Francisco montiert ist, habe ich in der Mai-Ausgabe des Snapshots [2] erläutert. Ein Wetterfühler vom Typ EcoWitt misst die aktuellen Wetterdaten und leitet sie stetig durchs Internet an den Wetterserver weiter. Der archiviert sie, und stellt aktuelle und historische Daten für jedermann ins Netz.

Nun hat der Weather Underground eine interessante Firmengeschichte hinter sich: Gegründet als Uni-Projekt im Jahr 1993, 1995 als kommerzielles Projekt ausgekoppelt, verkauft im Jahr 2012 an die Firma The Weather Channel, 2015 gekauft von IBM, bevor schließlich die Private-Equity-Firma Francisco Partners 2023 die Firma ausschlachtete und nur die Wetter-Bereiche übernahm.

Abbildung 1: Aktuelle Messdaten von Mike Schillis Wetterstation in San Francisco

Kommerz und Community

Auch heute noch antwortet wunderground.com (allerdings leicht schleppend) auf Web-Anfragen zu aktuellen Wetterdaten angeschlossener Stationen (Abbildung 1). Historische Daten von Stationen rund um den Globus gibt es hingegen vom kostenpflichtigen Server auf api.weather.com per API-Zugriff. Einen kostenlosen API-Key für Entwickler stellt dabei weather.com zeitbegrenzt für 30 Tage (Abbildung 2), und wer selbst eine Wetterstation im Weather-Underground-Netzwerk betreibt, bekommt auf wunderground.com ebenfalls einen API-Key, der für 100 Requests pro Minute und 50.000 pro Tag gut ist, und, das Beste, zeitlich nicht begrenzt ist. Für einen Enterprise-Key ruft weather.com absurde $500/Monat ab, was sich für private Zwecke verbietet.

Abbildung 2: Unterschiedliche Abos für die Wetterdaten

Die über 250.000 Wetterstationen im Verbund pumpen nun kontinuierlich ihre Messdaten durchs Internet an den Server auf weather.com und der speichert sie für die Ewigkeit. Historische Wetterdaten lassen sich später tagesgenau für alle Stationen abrufen, und wer wissen will, aus welcher Richtung der Wind am 17. März 2026 in San Franciscos Viertel Noe Valley gepfiffen hat, ob es an dem Tag regnete oder die Sonne prall vom Himmel schien, der fördert die Daten mit einer simplen API-Abfrage an den Server zutage.

Die API bietet eine Vielzahl von Endpoints. Für die aktuelle Ausgabe des Programmier-Snapshots sehen wir uns zwei an: /near liefert die nächsten 10 Wetterstationen um eine Geolocation aus geografischer Länge und Breite, und /daily gibt zu einer bestimmten Wetterstation die Tagesmessdaten innerhalb eines vorgegebenen Zeitraums zurück.

Historische Wetterdaten

Abhängig von geografischen Besonderheiten herrscht in manchen Städten nicht nur ein einziges Klima, sondern eine Vielzahl sogenannter Micro-Climates. San Francisco ist so eine Stadt, mit dem Mini-Gebirge Twin Peaks in der Mitte, das den Nebel vom Pazifischen Ozean abblockt, sodass die östlichen Stadtteile durchgehend mehr Sonne und damit angenehmere Temperaturen bekommen.

Da kommt es gerade recht, dass in solchen Ballungsgebieten typischerweise eine Vielzahl von Wetterstationen quer über die Stadt verteilt stehen, die ihre ganz spezifischen Stadtteilmessdaten auf dem Wetterserver ablegen. Wie lässt sich nun aus den Wetterdaten ableiten, ob eine Gegend ein Nebelloch oder eine sonnenverwöhnte Wellness-Oase ist? Als Beispielanwendung holt die heute vorgestellte App die Wetterdaten des Monats November ein, und fischt sich aus allen verfügbaren Messwerten (Kasten 1) die höchste gemessene Sonneneinstrahlung (solarRadiationHigh) heraus. Ein Wert über 500 bedeutet zumeist, dass am Tag der Messung ordentlich die Sonne schien. Ausgelesene Werte während der 30 Tage des Monats November werden anschließend gemittelt und das Ergebnis als Score interpretiert, zur Ermittlung eines Scores der Wetter-Bonität des Stadtteils.

Aber nicht jede Wetterstation liefert alle Werte korrekt. Bei der Sonneneinstrahlung kann es sein, dass der Fühler im Schatten steht, oder dass die Hardware keine UV-Strahlung messen kann. Kommt aus diesem Grund ein Null-Wert zurück, sollte die App die Station schlicht ignorieren und nur die mit sinnvollen Werten zur Score-Ermittlung heranziehen. Im Kasten 1 stehen noch eine Vielzahl weiterer vom API-Server eingeholter historischer Messwerte, aber um den Code kurz zu halten, nutzt die vorgestellte App nur den solarRadiationHigh-Wert. Am Ende dieser Ausgabe stehen noch ein paar Ideen zur genaueren Bestimmung.

Kasten 1: Felder des "Daily"-Endpoints der weather.com-API

solarRadiationHigh - Höchste gemessene Solarstrahlung
uvHigh - Höchster gemessener UV-Index
winddirAvg - Durchschnittliche Windrichtung in Grad.
humidityHigh - Höchste relative Luftfeuchte
humidityLow - Niedrigste relative Luftfeuchte
humidityAvg - Durchschnittliche relative Luftfeuchte
tempHigh - Höchste Temperatur
tempLow - Niedrigste Temperatur
tempAvg - Durchschnittliche Temperatur
windspeedHigh - Höchste Windgeschwindigkeit
windspeedLow - Niedrigste Windgeschwindigkeit
windspeedAvg - Durchschnittliche Windgeschwindigkeit
windgustHigh - Stärkste Windbö
windgustLow - Schwächste Windbö
windgustAvg - Durchschnittliche Windbö
dewptHigh - Höchster Taupunkt
dewptLow - Niedrigster Taupunkt
dewptAvg - Durchschnittlicher Taupunkt
windchillHigh - Höchste gefühlte Kälte
windchillLow - Niedrigste gefühlte Kälte
windchillAvg - Durchschnittliche gefühlte Kälte
heatindexHigh - Höchster Hitzeindex
heatindexLow - Niedrigster Hitzeindex
heatindexAvg - Durchschnittlicher Hitzeindex
pressureMax - Höchster Luftdruck
pressureMin - Niedrigster Luftdruck
pressureTrend - Trend des Luftdrucks im Tagesverlauf
precipRate - Niederschlagsrate des Tages
precipTotal - Gesamtniederschlag des Tages

Wetter pro Stadtteil

Das erste vorgestellte Go-Programm aus Listing 1 landet im Binary near. Es nimmt auf der Kommandozeile einen Punkt auf der Erdkugel als geografische Länge und Breite entgegen, sowie ein Startdatum, ab dem der Code die historischen Wetterdaten des darauf anschließenden Monats abholt. Der Aufruf in Abbildung 3 holt zum Beispiel die Wetterdaten der in Berlin Mitte ansässigen Wetterstationen für den schon etwas zurückliegenden Juni 2026 ein.

Die Ergebnisse schreibt das Programm nach Stdout, im Spaltenformat mit der ID des Messfühlers, dessen geografische Länge und Breite, sowie dem Messwert für die mittlere Sonneneinstrahlung während des angeforderten Monats (Abbildung 3). Die Ausgabe meldet durchgehend sonnige Tage und blauen Himmel mit Werten um die 800. Zum Vergleich zeigt Abbildung 4 die gemittelten Messdaten sieben Monate früher, im Monat November. Drastisch niedrigere Mittelwerte um die 100 zeigen an, dass der Schlagersong "Wenn es dunkel und kalt wird in Berlin" der bekannten Combo "Element Of Crime" ([5]) durchaus seine Berechtigung hat.

Abbildung 3: Mittlere Sonneneinstrahlung im Juni 2026 für Stationen in Berlin Mitte

Abbildung 4: Im November wird es kalt und dunkel in Berlin

Erstmal liest dazu die Hauptfunktion main() ab Zeile 20 in Listing 1 die Kommandozeilenoptionen -lat, -lon und -date mit dem Standardpaket flag ein. Den geheimen API-Key sucht und findet das Murmur-Paket in der Datei ~/.murmur unter dem Schlüssel wu-api. So muss die Applikation die sensiblen Daten nicht in den öffentlich verfügbaren Code einzimmern.

Listing 1: near.go

    001 package main
    002 import (
    003   "flag"
    004   "fmt"
    005   "time"
    006   "github.com/mschilli/go-murmur"
    007   "github.com/tidwall/gjson"
    008   "github.com/valyala/fasthttp"
    009 )
    010 const (
    011   BaseUrl = "https://api.weather.com/"
    012   NearEP  = BaseUrl + "v3/location/near"
    013   DailyEP = BaseUrl + "v2/pws/history/daily"
    014 )
    015 type station struct {
    016   stationID string
    017   lat       float64
    018   lon       float64
    019 }
    020 func main() {
    021   latFloat := flag.Float64("lat", 0, "latitude")
    022   lonFloat := flag.Float64("lon", 0, "longitude")
    023   dateArg := flag.String("date", "", "start date")
    024   flag.Parse()
    025   m := murmur.NewMurmur()
    026   apiKey, err := m.Lookup("wu-api")
    027   if err != nil {
    028     panic("no api key")
    029   }
    030   stations, err := near(*latFloat, *lonFloat, apiKey)
    031   if err != nil {
    032     panic(err)
    033   }
    034   startDate, endDate, err := dateRange(*dateArg)
    035   if err != nil {
    036     panic(err)
    037   }
    038   for _, station := range stations {
    039     avg, err := sun(station.stationID, startDate, endDate, apiKey)
    040     if err != nil {
    041       panic(err)
    042     }
    043     if avg == 0 {
    044       continue
    045     }
    046     fmt.Printf("%s\t%.6f\t%.6f\t%.2f\n", station.stationID, station.lat, station.lon, avg)
    047   }
    048 }
    049 func dateRange(start string) (time.Time, time.Time, error) {
    050   if start == "" {
    051     endDate := time.Now().AddDate(0, 0, -1)
    052     return endDate.AddDate(0, 0, -29), endDate, nil
    053   }
    054   startDate, err := time.ParseInLocation("20060102", start, time.Local)
    055   if err != nil {
    056     return time.Time{}, time.Time{}, fmt.Errorf("parse --date as yyyymmdd: %w", err)
    057   }
    058   return startDate, startDate.AddDate(0, 0, 29), nil
    059 }
    060 func near(lat, lon float64, apiKey string) ([]station, error) {
    061   body, err := apiGet(NearEP, map[string]string{
    062     "geocode": fmt.Sprintf("%.6f,%.6f", lat, lon),
    063     "product": "pws", "format": "json", "apiKey": apiKey,
    064   })
    065   if err != nil {
    066     return nil, err
    067   }
    068   loc := gjson.GetBytes(body, "location")
    069   ids, lats, lons := loc.Get("stationId").Array(), loc.Get("latitude").Array(), loc.Get("longitude").Array()
    070   stations := make([]station, 0, len(ids))
    071   for i, id := range ids {
    072     if i < len(lats) && i < len(lons) {
    073       stations = append(stations, station{id.String(), lats[i].Float(), lons[i].Float()})
    074     }
    075   }
    076   return stations, nil
    077 }
    078 func sun(stationID string, startDate, endDate time.Time, apiKey string) (float64, error) {
    079   body, err := apiGet(DailyEP, map[string]string{
    080     "stationId": stationID, "format": "json", "units": "m",
    081     "startDate": startDate.Format("20060102"), "endDate": endDate.Format("20060102"),
    082     "numericPrecision": "decimal", "apiKey": apiKey,
    083   })
    084   if err != nil {
    085     return 0, err
    086   }
    087   var total float64
    088   count := 0
    089   for _, summary := range gjson.GetBytes(body, "observations").Array() {
    090     if solar := summary.Get("solarRadiationHigh"); solar.Exists() {
    091       total += solar.Float()
    092       count++
    093     }
    094   }
    095   if count == 0 {
    096     return 0, nil
    097   }
    098   return total / float64(count), nil
    099 }
    100 func apiGet(ep string, params map[string]string) ([]byte, error) {
    101   req := fasthttp.AcquireRequest()
    102   defer fasthttp.ReleaseRequest(req)
    103   req.SetRequestURI(ep)
    104   for key, value := range params {
    105     req.URI().QueryArgs().Set(key, value)
    106   }
    107   status, body, err := fasthttp.GetTimeout(nil, req.URI().String(), 15*time.Second)
    108   if err != nil {
    109     return nil, err
    110   }
    111   if status < 200 || status > 299 {
    112     return nil, fmt.Errorf("HTTP %d: %s", status, body)
    113   }
    114   return body, nil
    115 }

Der Aufruf der Funktion near() in Zeile 30 verzweigt zur Implementierung in Zeile 60, wo ein API-Call an den /near-Endpoint auf weather.com zehn Wetterstationen in der näheren Umgebung als JSON zurückliefert. Das Hauptprogramm fieselt später mit dem Paket gjson von Github deren Station-IDs heraus. Es handelt sich dabei um kurze Strings zur Identifizierung einzelner Wetterstationen im Wunderground-Netzwerk. Die Werte der ersten Spalten der Ausgabe in Abbildung 3 zeigen einige Beispiele.

Für jede dieser Wetterfühler-IDs ruft das Hauptprogramm dann in Zeile 39 die Funktion sun() auf, deren Körper ab Zeile 78 in Listing 1 steht. Jede Anfrage an den /daily-Endpoint der API mit der Station-ID und einem Zeitrahmen aus Start- und Enddatum liefert einen Wust von Wetterdaten im JSON-Format zurück. Die Namen der Felder und ihre Bedeutung illustriert der Kasten 1. Auch hier hilft gjson, interessierende Werte ohne Umschweife zu extrahieren.

Auf die Landkarte damit

Nun sind Zahlenreihen ja gut und schön, aber nur die wenigsten Menschen können sich ausmalen, in welchem Stadtviertel sich eine Wetterstation einer vorgegebenen geografischen Länge und Breite befindet. Zu Hilfe kommt hier Listing 2, das die Zahlenreihen aufschnappt und in eine Landkarte des OpenStreetMap-Projekts einzeichnet.

Dazu ruft der User die beiden aus Listing 1 und Listing 2 mittels go mod init/tidy generierten Binaries in einer Pipe auf:

$ ./near | ./heatmap

Heraus kommt eine Bilddatei im .png-Format, mit Markern, die den gemittelten Messwert der Sonneneinstrahlung am Ort der Wetterstation anzeigen. So illustriert zum Beispiel Abbildung 5 im November glorreiches Wetter in meiner Wahlheimat San Francisco. In unserem Stadtteil Noe Valley, etwa in der geografischen Mitte der Halbinsel der Stadt wurden letzten November Werte von etwa 600 für die Sonneneinstrahlung gemessen. Abbildung 6 zeigt im selben Zeitraum für Berlin Mitte das erwartete Grau-in-Grau mit Messwerten um 300. Ähnlich verhält es sich mit London, das ebenfalls nicht für sonniges Novemberwetter bekannt ist. Eine Stadt mit extrem hoher Sonnenqualität ist nach Abbildung 8 Honolulu auf der Hauptinsel Oahu des Hawaii-Archipels: Messwerte um 950 im November spiegeln das perfekte Klima der Insel wider, und aus eigener Erfahrung kann ich sagen, dass dies ganzjährig der Fall ist.

Abbildung 5: Blauer Himmel im November in San Francisco

Abbildung 6: Wenn es dunkel und kalt wird in Berlin

Abbildung 7: Nebelloch London: Bedeckter Himmel im November

Abbildung 8: In Honolulu auf Hawaii ist es durchgehend sonnig.

Kürze statt Würze

Listing 2 nimmt in readPoints() ab Zeile 46 die Messdaten einzelner Stationen zeilenweise entgegen und fieselt die Einzelwerte mit Sscanf() aus der Standardbibliothek heraus. Da diese Daten vorher eigenhändig von Listing 1 produziert wurden (und um Platz zu sparen), validiert readPoints() die Eingabe nicht, andernfalls wäre zu prüfen, ob Messwerte im erwarteten Bereich ankommen oder auch die geografische Breite und Länge einzelner Messstationen in einem sinnvollen Rahmen liegen.

Listing 2: heatmap.go

    001 package main
    002 import (
    003   "bufio"
    004   "bytes"
    005   "flag"
    006   "fmt"
    007   "image"
    008   "image/color"
    009   "image/png"
    010   "io"
    011   "log"
    012   "math"
    013   "os"
    014   "strconv"
    015   sm "github.com/flopp/go-staticmaps"
    016   "github.com/golang/geo/s2"
    017   "github.com/mazznoer/csscolorparser"
    018 )
    019 type point struct {
    020   Name  string
    021   Lat   float64
    022   Lon   float64
    023   Value float64
    024 }
    025 func main() {
    026   out := flag.String("out", "heatmap.png", "PNG path")
    027   width := flag.Int("width", 1000, "map width")
    028   height := flag.Int("height", 800, "map height")
    029   flag.Parse()
    030   points, err := readPoints(os.Stdin)
    031   if err != nil {
    032     log.Fatal(err)
    033   }
    034   if len(points) == 0 {
    035     log.Fatal("no input rows")
    036   }
    037   rendered, err := renderMap(points, *width, *height)
    038   if err != nil {
    039     log.Fatal(err)
    040   }
    041   if err := os.WriteFile(*out, rendered, 0644); err != nil {
    042     log.Fatal(err)
    043   }
    044   log.Printf("%s ready\n", *out)
    045 }
    046 func readPoints(input io.Reader) ([]point, error) {
    047   scanner := bufio.NewScanner(input)
    048   var points []point
    049   for scanner.Scan() {
    050     var p point
    051     if _, err := fmt.Sscanf(scanner.Text(), "%s %f %f %f", &p.Name, &p.Lat, &p.Lon, &p.Value); err != nil {
    052       return nil, err
    053     }
    054     points = append(points, p)
    055   }
    056   if err := scanner.Err(); err != nil {
    057     return nil, err
    058   }
    059   return points, nil
    060 }
    061 func valueRange(points []point) (minVal, maxVal float64) {
    062   minVal = math.Inf(1)
    063   maxVal = math.Inf(-1)
    064   for _, p := range points {
    065     minVal = math.Min(minVal, p.Value)
    066     maxVal = math.Max(maxVal, p.Value)
    067   }
    068   return minVal, maxVal
    069 }
    070 func renderMap(points []point, width, height int) ([]byte, error) {
    071   if width <= 0 || height <= 0 {
    072     return nil, strconv.ErrSyntax
    073   }
    074   minVal, maxVal := valueRange(points)
    075   ctx := sm.NewContext()
    076   ctx.SetSize(width, height)
    077   ctx.SetTileProvider(sm.NewTileProviderOpenStreetMaps())
    078   ctx.SetUserAgent("heatmap-example/1.0")
    079   for _, p := range points {
    080     pos := s2.LatLngFromDegrees(p.Lat, p.Lon)
    081     weight := normalized(p.Value, minVal, maxVal)
    082     marker := sm.NewMarker(pos, markerColor(weight), 42)
    083     marker.Label = fmt.Sprintf("%.0f", p.Value)
    084     marker.SetLabelColor(labelColor(weight))
    085     ctx.AddMarker(marker)
    086   }
    087   img, err := ctx.Render()
    088   if err != nil {
    089     return nil, err
    090   }
    091   return encodePNG(img)
    092 }
    093 func normalized(value, minVal, maxVal float64) float64 {
    094   if maxVal <= minVal {
    095     return 1
    096   }
    097   return (value - minVal) / (maxVal - minVal)
    098 }
    099 func markerColor(weight float64) color.Color {
    100   name := "deepskyblue"
    101   if weight >= 0.75 {
    102     name = "crimson"
    103   } else if weight >= 0.50 {
    104     name = "darkorange"
    105   } else if weight >= 0.25 {
    106     name = "gold"
    107   }
    108   col, _ := csscolorparser.Parse(name)
    109   return col
    110 }
    111 func labelColor(weight float64) color.Color {
    112   if weight >= 0.25 && weight < 0.75 {
    113     return color.Black
    114   }
    115   return color.White
    116 }
    117 func encodePNG(img image.Image) ([]byte, error) {
    118   var buf bytes.Buffer
    119   if err := png.Encode(&buf, img); err != nil {
    120     return nil, err
    121   }
    122   return buf.Bytes(), nil
    123 }

Maximum und Minimum

Wer genau hinschaut, sieht, dass die Stationsmarker in Abbildung 5 bis Abbildung 8 farblich unterlegt sind, hohe Messwerte erscheinen in Rot, niedrigere in Orange oder gar Blau. Hierzu bestimmt valueRange() ab Zeile 61 den minimalen und den maximalen Messwert der Reihe. Um nun zu prüfen, ob ein Wert sich als globales Maximum qualifiziert, muss die for-Schleife ab Zeile 64 erst einmal abfragen, ob die Variable maxVal bereits gesetzt wurde, denn falls nicht, ist der aktuelle Wert automatisch das Maximum. In Skriptsprachen wie Perl nehmen ungesetzte Variablen den Wert undef an, aber Go setzt jede deklarierte Variable automatisch auf den Null-Value, was bei Floats "0.0" ist. Nun ist aber 0.0 ein Wert, der tatsächlich in der Messreihe vorkommen könnte, und deshalb setzt Zeile 63 das Maximum auf negativ unendlich. Damit startet maxVal als unmöglicher Wert, und Zeile 66 kann mit Max() ohne Fallunterscheidung prüfen, ob der aktuelle Messwert als Maximum taugt. Für das Minimum gilt Analoges.

Die Darstellung der Landkarte mit den Markern übernimmt das Paket go-staticmaps, das Zeile 15 unter dem Kürzel sm importiert. Die Funktion zum Setzen eines Markers, NewMarker() in Zeile 82, benötigt die Geo-Position des Ballons als Variable vom Typ LatLng, die das Paket golang/geo/s2 von Github ohne viel Federlesens aus den Floats aus der Eingabe produziert.

Quantile einfärben

Messwerte können stark schwanken, zwischen 0 und Werten um die tausend oder höher. Für eine sinnvolle farbliche Zuordnung bestimmt normalized() ab Zeile 93 für jeden Messwert eine Abbildung in den Bereich zwischen 0 und 1. So kann markerColor() ab Zeile 99 später Werte im obersten Quantil (über 0.75) rot einfärben und Werte in den unteren Quantilen farblich kühler mit mehr Blauanteil unterlegen.

Zahlenwerte auf rotem Hintergrund sollten in einem weißen Font erscheinen, aber auf einem helleren Marker lesen sich schwarze Zahlen besser mit mehr Kontrast. Die Funktion labelColor() zieht hierzu die entsprechenden Register. Die fertige Landkarte liegt nach dem Aufruf von Render() in Zeile 87 als image vor, und encodePNG() ab Zeile 117 macht eine PNG-Datei daraus, die Wohnungssuchende mit einem Image-Viewer begutachten können, bevor sie eine Immobilie in Betracht ziehen.

Nicht nur Sonne

Angenehmes Wetter an einem zukünftigen Wohnort kann man auch mit weiteren Messdaten ermitteln. Der in Kasten 1 beschriebene /daily-Endpoint bietet weitere Möglichkeiten, die Wetter-Bonität zu ermitteln. Herrscht Wind? Wenn es nicht gerade stürmt wie im Klondike, ist Wind eine gute Sache, da er frische Luft produziert und unangenehme Übertemperaturen wegbläst. Regen? Der Wert precipTotal gibt die Regenmenge in Millimetern an, und der ideale Wohnort sollte auch mal eine Woche Sonnenschein ohne Niederschlag bieten. Zu hohe Luftfeuchtigkeit gilt es ebenfalls zu meiden, wer schon mal im Sommer in Florida war, kann ein Lied davon singen.

Das vorgestellte Tool lässt sich nun einfach um diese Werte erweitern und der Pipe-Mechanismus stellt alle erdenklichen Messwerte geografisch auf der Landkarte dar. Wohl dem, der vorher abcheckt, ob ein neuer Wohnort alle Vorgaben erfüllt!

Infos

[1]

Listings zu diesem Artikel: http://www.linux-magazin.de/static/listings/magazin/2026/10/snapshot/

[2]

Michael Schilli, "Daumen im Wind": Linux-Magazin 05/26, S.XXX, https://www.linux-magazin.de/ausgaben/2026/05/snapshot/

[3]

Geschichte des Wetter-Projekts "Weather Underground", https://en.wikipedia.org/wiki/Weather_Underground_(weather_service)

[4]

Preise für API-Services auf api.weather.com: https://www.weathercompany.com/weather-data-apis/weather-data-apis-packages-pricing/

[5]

Element Of Crime - "Wenn es dunkel und kalt wird in Berlin", https://www.youtube.com/watch?v=CdonEISrnFE

Michael Schilli

arbeitet als Software-Engineer in der San Francisco Bay Area in Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen verschiedener Programmiersprachen. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.