Entwicklung - SchwapTobi/WeatherNet GitHub Wiki

Datenbeschaffung:

Vorarbeit:

Nachdem wir uns geeinigt haben, welche Daten benötigt werden, begann die Suche nach geeigneten Herausgebern der Daten. Nach einiger Internetrecherche hatten wir die Daten beisammen.
Passende APIs sind im Unterkapitel „Wetter APIs“ gelistet. Wichtig waren auch die Postleitzahlen von Österreich und die Namen der Gemeinden. Jedoch werden größere Gemeinden noch einmal in kleinere Gebiete unterteilt. Ein Beispiel in Linz: Auwiesen und Ebelsberg gehören beide zur Gemeinde Linz, haben beide die PLZ 4030 (Zentralraum Linz hätte 4020), aber es sind unterschiedliche Bezeichnung für jeweilige Gebiete. Also war es wichtig alle Bezeichnungen für eine PLZ zu wissen. Diese Unterteilung war uns wichtig, da dies oft in modernen Wetter Apps nicht beachtet wird.

Geodaten Österreich:

Auf der Suche nach den Grunddaten haben wir folgende Quellen gefunden:

  • Detailierte PLZ: secure.umweltbundesamt.at TODO ERKLÄRUNG, VERÖFFENTLICHUNG & LIZENZ
  • Koordinaten zu den (meisten) Postleitzahlen csv-geodaten.de, laut Website freie Nutzung
  • genauere Koordinaten für Linz und Linz-Umgebung geocode.xyz
  • Postleitzahlen & Bundesländer: data.gv.at Zur Verfügung gestellt von der RTR-GmbH unter der Creative Commons 4.0 Lizenz. Liefert folgende Daten:
   {
      "plz": 1000,
      "ort": "Wien",
      "bundesland": "W",
      "gueltigab": "2009-08-01",
      "gueltigbis": null,
      "plztyp": "PLZ-Postfach",
      "internextern": "extern",
      "adressierbar": "Nein",
      "postfach": "Ja"
   }

Parser:

Die ursprünglich als im .csv-Format verfügbaren Daten (Koordinaten für PLZ) von csv-geodaten.de wurden zwecks einfacherer Handhabung in das .txt-Format konvertiert. Alle anderen Dateien sind im .json-Format, mit Ausnahme der Daten des Umweltbundesamtes (XML) gehalten.

Der Parser dient dazu all diese Datensätze, nach für uns relevante Datenpunkte zu filtern und in ein eigenes .json-Objekt zu bringen, welche kombiniert in einer .json-Datei als Array gesammelt werden. Unser Objekt beinhaltet folgendes (hier das Java-Äquivalent):

public final class Location {

    private String id;
    private String name;
    private String state;
    private final String country = "AT";
    private int zipCode;
    private double latitude = 0;
    private double longitude = 0;

…
getter & setter
…

}

Daten

Ortschaft parsen:

Die Daten des Umweltbundesamtes sehen so aus:

<row>
<column>1010</column>
<column>17223</column>
<column>Wien,Innere Stadt</column>
</row>
<row>
<column>1020</column>
<column>17224</column>
<column>Wien,Leopoldstadt</column>
</row>
<row>
<column>1030</column>
<column>17225</column>
<column>Wien,Landstra&#223;e</column>
</row>

Für uns nicht von Relevanz war die Gemeindenummer, der Datensatz zwischen PLZ und Name

Koordinaten parsen:

Die Daten von csv-geodaten.de waren im .json-Fromat, und haben so ausgesehen:

Plz;Ort;Latitude;Longitude
9913;"Abfaltersbach";46.7603;12.5419
6067;"Absam";47.3;11.5
3462;"Absdorf";48.3167;15.6167
5441;"Abtenau";47.55;13.35
5122;"Ach";47.5178;9.865
2481;"Achau";48.0667;16.3833
6215;"Achenkirch";47.5167;11.7
4541;"Adlwang";47.9833;14.2167
...

Wir verglichen diese Daten mit denen aus dem Umweltbundesamt anhand der PLZ und ordneten so jeder Location die Koordinaten zu.

Geodaten Parsen:

Aus den zuvor vorgestellten Quellen ergeben sich so einerseits eine Liste mit über 19.000 Einträgen aller Ortschaften in Österreich. Dabei wurde auch zwischen den verschiedenen Ortschaften unterschieden, die sich eine Postleitzahl teilen (wie z.B 4040 Linz, 4040 Plesching, 4040 Pöstlingberg, ...). Leide sind in der Quelle dieser Daten (Umweltbundesamt.at) keine Geolocations und Bundesländer vermerkt. Also mussten wir kreativ werden: Über die API der Seite geocode.xyz kann man Anfragen mit gewissen Parametern stellen.

let url = 'https://geocode.xyz/' + this._zip + '+' + this._city + '?region=AT;json=1&auth=##########################';

In unserem Fall übergeben wir die Postleitzahl, sowie die Ortsbezeichnung. bei "4040", "Pöstlingberg" erfolgt die Response:

  ...
  "alt": {
    "loc": [
      {
        "longt": "14.25648",
        "latt": "48.32726",
        "prov": "AT",
        "city": "Pöstlingberg",
        "countryname": "Austria",
        "postal": "4040",
        "region": {},
      },
      {
        "longt": "14.23469",
        "latt": "48.31580",
        "prov": "AT",
        "city": "Pöstlingberg",
        "countryname": "Austria",
        "postal": "4048",
        "region": {},
      }
    ]
  },
  ...
}

Ein gutes Beispiel um die Probleme zu zeigen, die bei den meisten Adressen vorkommen. 4040 Pöstlingberg und 4048 Pöstlingberg sind zwar nur wenige Meter entfernt, doch die Koordinaten des einen Eintrags zeigen wie erwartet auf den Pöstlingberg, die anderen auf eine Nebenstraße in Oberpuchenau. Diese Ungenauigkeiten der API bzw. Inkonsistenzen im Grunddatensatz müssen wir irgendwie ausgleichen. Dazu prüfen wir sowohl alle PLZ, als auch Ortsbezeichnungen der Response ab und nehmen das für uns passendste Ergebnis. Eine Abfrage nach den Koordinaten für unseren Ort läuft dann so ab. Der DataManager ist eine Hilfsklasse zum Zusammenbasteln der Parser und zum Laden aller Ortsdaten die dann in einer Liste gespeichert werden soll, die dann einzelne location Elemente enthält. Eine location hält folgende Felder:

export class Location {
  private lat: number;
  private long: number;
  private zip: string;
  private city: string;
  private state: string;
  private country: string;
 
 constructor(zip: string, city: string) {
    this.zip = zip;
    this.city = city;
    this.country = "AT";
    this.setPositions();
  }
  ...
  ...
  ...
  ...
}

Uns fehlen jedoch noch die Lat und Long Koordinaten, sowie das Bundesland state. Die Geolocations holen wir über die vorher beschriebene Technik und wählen das passendste Ergebnis aus. Im Konstruktor wird die Methode setPositions() aufgerufen, die über die Funktion getJSON mittels Callback aufruft, durch einen einfachen XMLHttpRequest eine Verbindung zur URL der API öffnet und das Ergebnis zurückliefert. Dann werden einfach die gewonnenen Daten verarbeitet und die entsprechenden Felder gesetzt:

setPositions() {
    let url = 'https://geocode.xyz/' + this._zip + '+' + this._city + '?region=AT;json=1&auth=##########################';
    var _this = this;
    return this.getJSON(url, function (err, data) {
        if (err !== null) {
          console.log('Something went wrong: ' + err);
        } else {
          //if match is not deterministic
          if (data.alt != null && data.alt.loc != null) {
            console.log('[WARNING] several geo data found.. choosing the closest..')
            //sometimes confidence is too low, look for the right postal address
            console.log(data.alt);
            for (let item of data.alt.loc) {
              if (item.postal == _this._zip) {
                console.log('[SUCCESS] found data')
                _this._long = item.longt;
                _this._lat = item.latt;
                _this.setState(_this._zip);
                console.log(_this.toString());
                return;
              }
            }

          } else {  //if there is only one city / zip code in the response
            ...
          }
        }
      });
  }

Dann wird das andere JSON File von www.data.gv.at, welches alle österreichischen Postleitzahlen und die dazugehörigen Bundesländer beinhaltet benötigt. So suchen wir einfach in welchem Bundesland sich die aktuelle Postleitzahl befindet (dies ist nämlich leider nicht immer Über die erste Zahl bestimmbar) und speichern die neu gewonnen Daten ebenfalls ab und erhalten folgende Ausgabe: 4040 Pöstlingberg: lat=48.32726, long=14.25648 (Oberösterreich, AT)

Das wiederholen wir 19.000 mal und erhalten so unsere vollständige Liste aller österreichischen Städte, Gemeinden und Ortschaften mit eignen Postleitzahlen, deren Bundesland, sowie Geolocation. Die Ergebnisse speichern wir in einem JSON-File, welches alle Locations Österreichs mit deren Postleitzahl, Ortsbezeichnung, Geokoordinaten und Bundesland anzeigt. Diese Informationen werden später entweder direkt aus dem File gelesen, oder wenn genug Zeit bleibt durch eine REST-API per GET Request zugänglich gemacht.

Funktion:

Der Parser extrahiert alle Postleitzahlen aus den Daten des Umweltbundesamtes, wobei die Ortschaften hier jeweils dann eine „Location“ ergeben. Diese Daten stellten also die Grundlage dar. Jede Location enthält einen Namen, den sie aus den Daten des Umweltbundesamtes erhält, ebenso wie die Postleitzahl. Bei letzterer kann es vorkommen, das mehrere Locations sich ein und dieselbe PLZ quasi teilen. Eine eindeutige ID, besthend aus PLZ_fortlaufendeNummerInnerhalbGleicherPLZ trennt die Locations untereinander. Die Koordinaten wurden aus den Datensätzen von csv-geodaten.de entnommen. Da wir allerdings genauere Daten für Linz bzw. Linz-Umgebung hatten, wurden diese genaueren Daten verwendet, ansonsten eben jene von csv-geodaten.de. Da allerdings auch hier noch immer nicht alle PLZ’s abgedeckt waren, hatten wir uns entschlossen jene mit 0.0 Werten zu füllen. Das Land hatte immer den gleichen Wert, nämlich AT, das Länderkürzel für Österreich. Die Bundesländer wurden aus den Daten von data.gv.at entommen.

Verwendete Third-Party-Library: GSON:

Die Library GSON, von Google entwickelt, und unter Apache 2.0 license veröffentlicht, wurde dazu genutzt, aus den Location-Objekten, JSON-Location-Objekte zu erzeugen.

Wetter APIs:

Bei der Suche nach einer passenden Wetter API, haben wir darauf geachtet, dass der Service kostenlos zumindest einen Basis-Zugang bietet. Unter den vielen Wetter API’s haben wir diese 3 in die engere Auswahl genommen. Alle 3 bieten kostenlosen Zugang zu ihrer API. Kommerzielle Modelle bieten selbstverständlich mehr und genauere Daten.

Name: Weatherbit.io Open Weather Map Apixu
Link zur Homepage https://www.weatherbit.io/ https://openweathermap.org/ https://www.apixu.com/
Doku Link https://www.weatherbit.io/api https://openweathermap.org/api https://github.com/apixu/apixu-nodejs
Leistungen: -1000 calls per day
-16 day forecast (daily)
-48h forecast (hourly)
-60 calls/minute
-5 days forecast / 3 hour update
-UV index
-Weather Alerts
1000 calls / month
7 day forecast
7 day history

Fazit: Die mit Abstand meisten Abfragen lässt OpenWeatherMap zu, mit 60 in der Minute. Bezüglich Vorschau sind alle einigermaßen ident, wobei 16 Tage (Weatherbit.io) das Maximum ist. Apixu hat den Vorteil, dass es die Möglichkeit bietet 7 Tage zurückzublicken, allerdings sind 10.000 Anfragen im Monat im Vergleich sehr wenig. Wir haben uns, auch aufgrund der guten Dokumentation, für OpenWeatherMap entschieden. Neben den Vorteil seitens der Dokumentation, hat uns die hohe Anzahl der Abfragen überzeugt.

OpenWeatherAPI

OpenWeatherMap verlangt die Benutzung eines API-Keys. Dieser wird jeder Anfrage angehängt:

Diese Abfrage liefert die Vorschau für die Stadt mit der ID 524901.

Diese Abfrage liefert das Wetter für die Stadt mit der ID 2172797

Beispiel-Antwort :

{"coord":{"lon":139,"lat":35},
"sys":{"country":"JP","sunrise":1369769524,"sunset":1369821049},
"weather":[{"id":804,"main":"clouds","description":"overcast clouds","icon":"04n"}],
"main":{"temp":289.5,"humidity":89,"pressure":1013,"temp_min":287.04,"temp_max":292.04},
"wind":{"speed":7.31,"deg":187.002},
"rain":{"3h":0},
"clouds":{"all":92},
"dt":1369824698,
"id":1851632,
"name":"Shuzenji",
"cod":200}
 

Diese Abfrage liefert das Wetter nach Koordinaten (Latitude & Longitude)

Um nach PLZ zu suchen, ist zsätzlich der Ländercode notwenidg

Die Antwort ist standardmäßig im JSON-Format, weiters werden unterstützt

- HTML: &mode=html

- XML: &mode=xml

beim Anhängen der Anfrage.

App-Entwicklung:

Framework:

Das Ionic-Framework basiert auf Angular und bietet viele Module, die speziell für die Entwicklung für mobile Endgeräte optimiert sind. So stehen einem eine Vielzahl an Components zur Verfügung, die dank Ionic einfach in HTML verwendet werden können. Programmiert wird in Typescript, das das Entwickeln viel angenehmer gestaltet als plain Javascript (in Hinsicht auf Deklarationen, Typsicherheit, Laufzeitfehler, Modularität, usw..). Pro page wird je mindestens eine HTML und eine TS Datei benötigt, die Styles werden in einer SCSS bzw CSS Datei gespeichert.

Pages:

Dann haben wir uns an den Mockups orientiert und die benötigten Pages erstellt. Eine Page besteht aus einer .ts-Datei, .html-Datei und einer .css- oder .scss-Datei. In der TypeScript-Datei wird beispielsweise das Verhalten von Klick-Events festgelegt und Server-Abfragen durchgeführt. Die HTML-Datei bestimmt das Aussehen der Seite, sie beschreibt welche Komponenten angezeigt werden (kann auch in TypeScript geschehen). Es wird hier quasi das Grundgerüst definiert. Der Header ist wie folgt definiert:

<ion-header>
  <ion-navbar>
    <ion-title>Titel</ion-title>
  </ion-navbar>
</ion-header>

Der eigentliche Inhalt ist hier zu bestimmen:

<ion-content>
eigentlicher Inhalt
</ion-content>

Das Design wird durch die (S)CSS-Datei festgelegt.

Daten:

Die Daten der Nodes werden auf einen Firebase-Server übertragen und dort gesammelt. Dies erlaubt es der WebApp eine Anfrage an diesen Server zu stellen und die Daten dann in der App aufzubereiten. Gespeichert werden die ID der Node, aktuelle Daten zum Wetter und von den Sensoren.

Suche:

Der Tab „Suche“ besteht hauptsächlich aus zwei Komponenten: Suchleiste und einer Liste. Die Suchleiste unterscheidet bei der Eingabe zwischen PLZ und Ortschafts- und Städtenamen. Je nach Eingabe werden die Ergebnisse in der Liste angezeigt. Die Ergebnisse sind links jeweils von einem Pin flankiert.
Sobald ein Char in die Suchleiste getippt wird, werden alle Gemeinden bzw. PLZs, welche an jeweiliger Stelle mit dem eingegeben Char übereinstimmen aufgelistet. Der durchsuchten Datensatz ist der gesamt vorhandene Datensatz.
Nach klick auf einen Listeneintrag werden die jeweiligen Wetterdaten angezeigt.

Kartenansicht:

Um in die Kartenansicht gelangen muss auf den roten Pin geklickt werden, welcher sich in der Tabbar befindet. Standardstandpunkt ist Linz. In die Page wurde die Google Maps Map eingefügt, weil sie die beste Übersicht bietet. In den Landeshauptstädten werden automatisch die Wetterstationen geladen und angezeigt. Zusätzlich werden die Radien der Nodes angezeigt. Bei Klick auf einen Radius werden die ID (PLZ und Bezeichnung) inklusive der Ausstattung der Node angezeigt.

Bei Klick auf die ID wird man auf die Node Page weitergeleitet.

Node-Page:

Wenn auf eine spezifische Wetterstation geklickt wird, werden die Daten Standort, Betreiber, vorhandene Sensoren und die Radius Genauigkeit angezeigt. Ganz oben wird die ID der Wetterstation angezeigt für bessere Übersicht.
Darunter die Graphiken über die Temperatur, Luftfeuchtigkeit, etc. je nachdem welche Sensoren vorhanden sind. Die Graphen können modifiziert werden. Man kann die Datenanzeige auf gewissen Zeitspannen beschränken beziehungsweise ausweiten.

Wetter-Page:

Wird auf den Pin im Home oder auf die Landeshauptstadt in der Kartenansicht geklickt wird auf das Rathaus gepinnt, der Standort mit PLZ, Stadt, Bundesland, und Staat und die Koordinaten angegeben. Darunter die Graphen. In diesem Fall Temperatur in Grad Celsius, Luftfeuchtigkeit in Prozent, Luftdruck in Hektopascal und Niederschlag in cm pro m2.

Die Graphen können jeweils vergrößert werden. Fährt man mit dem Cursor über die gekennzeichneten Punkte werden genauere Daten angegeben mit Datum, Uhrzeit und jeweiliger Temperatur. Zusätzlich können die Graphen modifiziert werden. Zum Beispiel nur die Daten der letzten 24 Stunden im Graphen anzeigen.

Weitere geplante Schritte

  1. Einstellungen
  2. Standort-Einstellungen
  3. eigenen Standort auf Karte auswählen
  4. Preference-Settings
⚠️ **GitHub.com Fallback** ⚠️