commit c5e6c56950b3dd47d3a8ef281fc7caa631cbc045 Author: Romain <> Date: Mon May 23 14:08:29 2022 +0200 Snapshot 1c4b68d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..316acf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,154 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +WeatherReport*.json \ No newline at end of file diff --git a/DebugServer.py b/DebugServer.py new file mode 100644 index 0000000..3394e78 --- /dev/null +++ b/DebugServer.py @@ -0,0 +1,4 @@ +from WebServer import app + +if __name__ == '__main__': + app.run(host='0.0.0.0',debug=True) \ No newline at end of file diff --git a/HandleWeatherFile.py b/HandleWeatherFile.py new file mode 100644 index 0000000..d9a5062 --- /dev/null +++ b/HandleWeatherFile.py @@ -0,0 +1,128 @@ + +from datetime import datetime, timedelta +import json +import os.path +import requests +import time + +class HandleWeatherFile: + """ + Fetch weather data from the infoclimat.fr public API. + Save the data into JSON file for future use so as to not overload the + free API. + Provide helper function to retrieve information from the JSON file. + @param latitude Latitude of the point in space to gather weather data from. + @param longitude Latitude of the point in space to gather weather data from. + @param filename Name of the JSON file that host weather data. + """ + def __init__(self,latitude=48.85341,longitude=2.3488,filename="WeatherReport.json"): + self.latitude=latitude + self.longitude=longitude + self.filename=filename + + def fetchWeatherFile(self,fileValidityInSecond=3600): + """ + Fetch the JSON weather report file from the web + Use self.filename file to store the data. If this file already + exist and it's creation date is less than "fileValidityInSecond" + then its content is used instead of an API request. + The self.filename file will be saved/overrode automatically. + @param fileValidityInSecond Maximum time in second for an old file to be valid. + """ + fetchNewFile=True + jsonData=None + + if(os.path.exists(self.filename)): + f=open(self.filename) + try: + jsonData=json.load(f) + if jsonData.get("fetchTime")!=None: + if (jsonData["fetchTime"]+fileValidityInSecond)>time.time(): + fetchNewFile=False + except Exception: + pass + f.close() + + if fetchNewFile: + try: + request=requests.get("https://www.infoclimat.fr/public-api/gfs/json?_ll="+str(self.latitude)+","+str(self.longitude)+"&_auth=VkxeSQ5wVnRQfQQzVCJXflE5ADUNe1dwB3tRMgBlAH1UPwRlBGRUMlI8WyZTfFJkAy5XNAE6BzcEbwZ%2BXS8AYVY8XjIOZVYxUD8EYVR7V3xRfwBhDS1XcAdlUTYAZAB9VDYEaQRnVChSPlsnU2JSbwM2VygBIQc%2BBGEGYl05AGBWPF44Dm5WMlA%2BBHlUe1dlUWMAMg01V20HYFEwAGQAYVQxBGkEYlQxUj1bJ1NqUmcDNFc3ATsHNwRiBmldLwB8VkxeSQ5wVnRQfQQzVCJXflE3AD4NZg%3D%3D&_c=8f0f822a713fe1e38891d301396859ea"); + if(request.status_code==200): + jsonData=request.json() + jsonData["fetchTime"]=time.time() + f=open(self.filename, "w") + f.write(json.dumps(jsonData)) + f.close() + else: + jsonData=request.status_code + except Exception: + pass + return jsonData + + def getFormattedDateTimeArray(self,dayCount,includeToday=False,hour=[]): + """ + Return an array composed of string representing dates and times formatted like the weather JSON file + @param dayCount Number of days of measure (counting up from today) + @param includeToday Indicate if dayCount include today or not + @param hour Hours to include, can be empty: then all possible hour will be returned. + """ + dayDateTime=[] + currentDateTime=datetime.now() + for i in range(dayCount): + if includeToday: + if i==0: + iDateTime=currentDateTime + else: + iDateTime=currentDateTime+timedelta(days=i) + else: + iDateTime=currentDateTime+timedelta(days=i+1) + + if len(hour)==0: + for h in range(0,24): + dayDateTime.append(self._getFormattedDateTime(iDateTime,h)) + else: + for h in hour: + dayDateTime.append(self._getFormattedDateTime(iDateTime,h)) + + return dayDateTime + + def _getFormattedDateTime(self,dateTimeObj,hour): + """ + Return a dateTime string formatted like the weather JSON file + @param dateTimeObj dateTime object used to get year/month and day. + @param hour Hour to be used instead of the dateTimeObj value + """ + return( + str(dateTimeObj.year) + +"-"+str(dateTimeObj.month).rjust(2,"0") + +"-"+str(dateTimeObj.day).rjust(2,"0") + +" "+str(hour).rjust(2,"0") + +":00:00" + ) + + def getWeatherFileMeasureHours(self,integerValue=False): + """ + Return an array with all the possible measured hours within the weather report file. + Return empty array if file empty or fail to open. + @param integerValue Return only the integer value of the hour instead of "HH:MM:SS" by default. + """ + validHours=[] + + measureDateTime=datetime.now()+timedelta(days=1) + measureDay=self._getFormattedDateTime(measureDateTime,0)[:-9] + + if(os.path.exists(self.filename)): + f=open(self.filename) + try: + jsonData=json.load(f) + for k in jsonData.keys(): + if k.__contains__(measureDay): + validHours.append(k.replace(measureDay+" ","")) + except Exception: + pass + f.close() + + if(integerValue): + for i in range(0,len(validHours)): + validHours[i]=int(validHours[i].replace(":00:00","")) + + return validHours \ No newline at end of file diff --git a/Licence note b/Licence note new file mode 100644 index 0000000..01464de --- /dev/null +++ b/Licence note @@ -0,0 +1 @@ +If you find software that doesn’t have a license, that means you have no permission from the creators of the software to use, modify, or share the software. Although a code host such as GitHub may allow you to view and fork the code, this does not imply that you are permitted to use, modify, or share the software for any purpose. diff --git a/README.md b/README.md new file mode 100644 index 0000000..859f229 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# Weather web server + +Web server that fetch and display weather data. + +## Weather data provider + +The weather data are provided by [infoclimat.fr](https://www.infoclimat.fr/) (not affiliated). +*Why them ?* They got weather data accessible from a registration free public API. +*Are they the best ?* Probably not, their API lack modularity and some data that may be critical depending on your application. +*Do they provide data outside France ?* No. + +## Installation + +For debugging/development purpose, you could simply use ```python3 DebugServer.py``` to launch the integrated flask webserver. +To deploy in production, there's multiple possibility depending on your need, see [the official flask documentation](https://flask.palletsprojects.com/en/2.1.x/deploying/) for reference. + +## Usage + +By default the development server is accessible via [127.0.0.1:5000](127.0.0.1:5000) and display the weather of today and the next 6 days at Paris. + +You could navigate to [127.0.0.1:5000/lat;long](127.0.0.1:5000/lat;long) where "lat" and "long" are the latitude and longitude of the place you which to display. + +Note: + +* The freezing point and rain quantity are only displayed when it's snowing and raining respectively. +* The bold number represent the gust of wind and is only displayed when it is greater than the average speed of wind. +* Use the buttons on the top-left of the table to access night-mode and display the help. + +![Illustration of the page](doc/Illustration.png) + +### Optional parameters + +Use the following URL parameters to change the default behavior. + +* **`hour`** +Use one or multiples numerical values separated by a comma to change the default displayed hours. +Note that invalid values will result in empty cells. +Example: [127.0.0.1:5000/?hour=8,10,12,14](http://127.0.0.1:5000/?hour=8,10,12,14) +* **`day`** +Indicate the number of days to display. Invalid values will result in empty cells. +Example: [127.0.0.1:5000/?day=4](http://127.0.0.1:5000/?day=4) +* **`today`** +If set to `0` then the current day won't be displayed. +Example: [127.0.0.1:5000/?today=0](http://127.0.0.1:5000/?today=0) +* **`title`** +Change the title of the web page. +Example: [127.0.0.1:5000/?title=Weather%20at%20Paris](http://127.0.0.1:5000/?title=Weather%20at%20Paris) +* **`ui`** +If set to `0` the icons won't be displayed. +Example: [127.0.0.1:5000/?help=0](http://127.0.0.1:5000/?help=0) +* **`night`** +If set to `1` the page will be displayed using the dark theme. +Example: [127.0.0.1:5000/?night=1](http://127.0.0.1:5000/?night=1) + +Obviously you can use parameters with or without the `lat;long` elements like this: [127.0.0.1:5000/48.51296;2.17402?day=2&today=0&title=Weather%20at%20the%20Eiffel%20Tower](http://127.0.0.1:5000/48.51296;2.17402?day=2&today=0&title=Weather%20at%20the%20Eiffel%20Tower) that will display the weather for the next two days at some random tower in Paris. diff --git a/WebServer.py b/WebServer.py new file mode 100644 index 0000000..7002046 --- /dev/null +++ b/WebServer.py @@ -0,0 +1,145 @@ +from flask import Flask, render_template, request +from HandleWeatherFile import HandleWeatherFile +from datetime import datetime + +app=Flask(__name__) + +def getUniqueDate(dateArray): + date=[] + for i in dateArray: + iDate=i.split(" ")[0] + if iDate not in date: + date.append(iDate) + return date + +def getHumanReadableFetchTime(fetchTime): + """ + Compare fetchTime with current date and time value. + Return the result as "X days Y minutes Z seconds" where days and minutes will only be displayed if relevant. + @param fetchTime datetime.datetime object representing a past point in time + """ + fetchTime=datetime.fromtimestamp(fetchTime) + tDelta=datetime.now()-fetchTime + + returnValue="" + rest=tDelta.seconds + if(tDelta.days>0): + returnValue+=str(int(tDelta.days))+" days " + if(rest>3599): + returnValue+=str(int(rest/3600))+" hours " + rest=rest%3600 + if(rest>59): + returnValue+=str(int(rest/60))+" minutes " + rest=rest%60 + returnValue+=str((rest))+" seconds" + return returnValue + +def getUserArgumentHour(requestHour): + """ + Return a sanitized array from the measurement hour(s) requested by the user. + Return empty array if no valid values. + Removes duplicates, unsure that the values are number and sort the results. + @param requestHour URL parameter relative to the hour in string format, value separated by comma. + """ + hour=[] + requestedHour=requestHour + if requestedHour is None: + return hour + + requestedHour=requestedHour.split(",") + requestedHour=list(dict.fromkeys(requestedHour)) #removes duplicates + + for h in requestedHour: + try: + hi=int(h) + hour.append(hi) + except Exception: + pass + + hour.sort() + return hour + +@app.route("/") +def indexRoute(): + return renderWeatherTemplate(request.args) + +@app.route("/;") +def specifiedCoordinateRoute(latitude,longitude): + try: + flatitude=float(latitude) + except Exception: + return "Error, verify the provided latitude. ("+str(latitude)+")" + + try: + flongitude=float(longitude) + except Exception: + return "Error, verify the provided longitude. ("+str(longitude)+")" + + return renderWeatherTemplate(request.args,latitude,longitude) + +def renderWeatherTemplate(requestArgs,latitude=None,longitude=None): + """ + Render the weather template based on the specified paramaters. + @param request.args Parsed URL parameters. + @param latitude Optional geographic coordinate that specifies the north–south position. + @param longitude Optional geographic coordinate that specifies the east-west position. + """ + title="Weather " + handleWeatherFile=None + + if latitude is not None and longitude is not None: + title+=latitude+"/"+longitude + handleWeatherFile=HandleWeatherFile(latitude=latitude,longitude=longitude,filename="WeatherReport"+latitude+"_"+longitude+".json") + else: + handleWeatherFile=HandleWeatherFile() + title+=" Paris" + + weatherFile=handleWeatherFile.fetchWeatherFile() + + measureHour=getUserArgumentHour(requestArgs.get("hour",type=str)) + validHours=None + if(len(measureHour)==0): + measureHour=handleWeatherFile.getWeatherFileMeasureHours(True) + validHours=measureHour + + includeToday=True + if requestArgs.get("today",type=int) is not None: + if(requestArgs.get("today",type=int)==0): + includeToday=False + + night=False + if requestArgs.get("night",type=int) is not None: + if(requestArgs.get("night",type=int)==1): + night=True + + ui=True + if requestArgs.get("ui",type=int) is not None: + if(requestArgs.get("ui",type=int)==0): + ui=False + if ui and validHours is None: + validHours=handleWeatherFile.getWeatherFileMeasureHours(True) + + dayCount=7 + if requestArgs.get("day",type=int) is not None: + dayCount=requestArgs.get("day",type=int) + + if requestArgs.get("title",type=str) is not None: + title=requestArgs.get("title",type=str) + + dt=handleWeatherFile.getFormattedDateTimeArray(dayCount,includeToday,measureHour) + + return render_template( + "index.html", + date=getUniqueDate(dt), + hour=measureHour, + datetime=dt, + weatherData=weatherFile, + fetchTime=getHumanReadableFetchTime(weatherFile["fetchTime"]), + title=title, + ui=ui, + validHours=str(validHours)[1:-1], + night=night + ) + +#if __name__ == '__main__': +# app.run(host='0.0.0.0',debug=True) \ No newline at end of file diff --git a/doc/Illustration.png b/doc/Illustration.png new file mode 100644 index 0000000..eb64bb4 Binary files /dev/null and b/doc/Illustration.png differ diff --git a/static/img/bkn.svg b/static/img/bkn.svg new file mode 100644 index 0000000..f0be874 --- /dev/null +++ b/static/img/bkn.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/dust.svg b/static/img/dust.svg new file mode 100644 index 0000000..8e9c979 --- /dev/null +++ b/static/img/dust.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/few.svg b/static/img/few.svg new file mode 100644 index 0000000..5b7932b --- /dev/null +++ b/static/img/few.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/fg.svg b/static/img/fg.svg new file mode 100644 index 0000000..528bd59 --- /dev/null +++ b/static/img/fg.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/fzra.svg b/static/img/fzra.svg new file mode 100644 index 0000000..31713fe --- /dev/null +++ b/static/img/fzra.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/fzrara.svg b/static/img/fzrara.svg new file mode 100644 index 0000000..9244793 --- /dev/null +++ b/static/img/fzrara.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/help.svg b/static/img/help.svg new file mode 100644 index 0000000..068e2c2 --- /dev/null +++ b/static/img/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/hi_shwrs.svg b/static/img/hi_shwrs.svg new file mode 100644 index 0000000..18375a3 --- /dev/null +++ b/static/img/hi_shwrs.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/hi_tsra.svg b/static/img/hi_tsra.svg new file mode 100644 index 0000000..96088d7 --- /dev/null +++ b/static/img/hi_tsra.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/ip.svg b/static/img/ip.svg new file mode 100644 index 0000000..92ccaf1 --- /dev/null +++ b/static/img/ip.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/mist.svg b/static/img/mist.svg new file mode 100644 index 0000000..f99affb --- /dev/null +++ b/static/img/mist.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/mix.svg b/static/img/mix.svg new file mode 100644 index 0000000..021166e --- /dev/null +++ b/static/img/mix.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/moon.svg b/static/img/moon.svg new file mode 100644 index 0000000..5b53862 --- /dev/null +++ b/static/img/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/nsurtsra.svg b/static/img/nsurtsra.svg new file mode 100644 index 0000000..a3c2c68 --- /dev/null +++ b/static/img/nsurtsra.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/ovc.svg b/static/img/ovc.svg new file mode 100644 index 0000000..fc32a5e --- /dev/null +++ b/static/img/ovc.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/ra.svg b/static/img/ra.svg new file mode 100644 index 0000000..c6b94f5 --- /dev/null +++ b/static/img/ra.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/ra1.svg b/static/img/ra1.svg new file mode 100644 index 0000000..128b7bc --- /dev/null +++ b/static/img/ra1.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/raip.svg b/static/img/raip.svg new file mode 100644 index 0000000..0d68f0b --- /dev/null +++ b/static/img/raip.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/rasn.svg b/static/img/rasn.svg new file mode 100644 index 0000000..db370c7 --- /dev/null +++ b/static/img/rasn.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/rasn1.svg b/static/img/rasn1.svg new file mode 100644 index 0000000..ddb5157 --- /dev/null +++ b/static/img/rasn1.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/sct.svg b/static/img/sct.svg new file mode 100644 index 0000000..ec315d4 --- /dev/null +++ b/static/img/sct.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/shra.svg b/static/img/shra.svg new file mode 100644 index 0000000..9da99e8 --- /dev/null +++ b/static/img/shra.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/skc.svg b/static/img/skc.svg new file mode 100644 index 0000000..bee83a9 --- /dev/null +++ b/static/img/skc.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/smoke.svg b/static/img/smoke.svg new file mode 100644 index 0000000..9374bed --- /dev/null +++ b/static/img/smoke.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/sn.svg b/static/img/sn.svg new file mode 100644 index 0000000..c8361f0 --- /dev/null +++ b/static/img/sn.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/sun.svg b/static/img/sun.svg new file mode 100644 index 0000000..37bfb5b --- /dev/null +++ b/static/img/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/img/tsra.svg b/static/img/tsra.svg new file mode 100644 index 0000000..cbd96cf --- /dev/null +++ b/static/img/tsra.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/img/wind.svg b/static/img/wind.svg new file mode 100644 index 0000000..6da1b29 --- /dev/null +++ b/static/img/wind.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/js/script.js b/static/js/script.js new file mode 100644 index 0000000..18b14f3 --- /dev/null +++ b/static/js/script.js @@ -0,0 +1,68 @@ +document.addEventListener("DOMContentLoaded",()=>{ + let currentUrlObj=document.getElementsByClassName('currentUrl'); + for (let i=0; i{ + toggleDayNightMode(e.target.checked); + }); + + if(nightModeInputObj.checked){ + toggleDayNightMode(nightModeInputObj.checked); + }else{ + toggleDayNightIcon(nightModeInputObj.checked); + } + + let helpObj=document.getElementById("help"); + let helpImageObj=document.getElementById("helpImage"); + if(helpImageObj){//Used because #helpImage can be hidden by user request. + document.getElementById("helpImage").addEventListener("click", ()=>{ + if(window.getComputedStyle(helpObj).getPropertyValue("display")=="none"){ + helpObj.style.display="block"; + }else{ + helpObj.style.display="none"; + } + }); + } +}); + +function toggleDayNightMode(nightMode){ + document.body.classList.toggle("nightBackground"); + let weatherImageArray=document.querySelectorAll(".weatherImage,#dayMode,#helpImage"); + for (let i=0; i + #help{ + display: none; + margin-left: auto; + margin-right: auto; + } + .argumentURL{ + font-weight: bold; + } + .bold{ + font-weight: bold; + } + +{% endblock %} +{% block help %} +
+

By default, will display the weather at Paris for the next 7 days, including today.

+

Empty cells results from the lack of data for a specific hour/date. Valid hours are: {{validHours}}. May be susceptible to change due to daylight saving time.

+

Use [currentUrl]/latitude;longitude to obtain the weather data for a specific location. +

The display can be altered using one or multiple of the following arguments: +

    +
  • day=X Number of days (X) to display.
  • +
  • hour=X,Y Hours to display, multiple values separated by comma possible.
  • +
  • today=0 Don't display today's weather.
  • +
  • title=X Change the HTML title by X.
  • +
  • ui=0 Hide the UI icons.
  • +
  • night=1 Display the dark theme.
  • +
+

+

Example: [currentUrl]/48.51296;2.17402?day=2&today=0&title=Weather%20at%20the%20Eiffel%20Tower

+
+{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..ed92e2e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,242 @@ +{% extends 'layout.html' %} +{% block title %}{{title}}{% endblock %} +{% block head %} +{{ super() }} + + +{% endblock %} +{% block body %} +{{ super() }} +{% if ui %} + {% include 'help.html' %} + {% block help %}{% endblock %} +{% endif %} + + + + + {% for d in date %} + + {% endfor %} + + + + {% for h in hour %} + + + {% for dt in datetime %} + {% if h|string() in dt.split(" ")[1].split(":")[0] %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
+ {% if ui %} +
+ + + +
+ {% endif %} +
{{d}}
{% if h < 10 %}{% set h="0"+h|string() %}{% endif %}{{h}}H00 + {% if dt in weatherData %} +
+ {% set img="skc.svg" %} + {% if weatherData[dt]["risque_neige"]=="oui" %} + {% if weatherData[dt]["pluie"]>0 %} + {% if weatherData[dt]["pluie"] > 30 %} + {% set img="rasn.svg" %} + {% else %} + {% set img="rasn1.svg" %} + {% endif %} + {% else %} + {% set img="sn.svg" %} + {% endif %} + {% elif weatherData[dt]["pluie"]>0 %} + {% set img="hi_shwrs.svg" %} + {% if weatherData[dt]["nebulosite"]["totale"]>=25 and weatherData[dt]["nebulosite"]["totale"] < 50 %} + {% set img="shra.svg" %} + {% endif %} + {% if weatherData[dt]["nebulosite"]["totale"]>=50 %} + {% if weatherData[dt]["pluie"] > 30 %} + {% set img="ra.svg" %} + {% else %} + {% set img="ra1.svg" %} + {% endif %} + {% endif %} + {% else %} + {% if weatherData[dt]["nebulosite"]["totale"]>=10 and weatherData[dt]["nebulosite"]["totale"] < 25 %} + {% set img="few.svg" %} + {% endif %} + {% if weatherData[dt]["nebulosite"]["totale"]>=25 and weatherData[dt]["nebulosite"]["totale"] < 50 %} + {% set img="sct.svg" %} + {% endif %} + {% if weatherData[dt]["nebulosite"]["totale"]>=50 and weatherData[dt]["nebulosite"]["totale"] < 75 %} + {% set img="bkn.svg" %} + {% endif %} + {% if weatherData[dt]["nebulosite"]["totale"]>=75 %} + {% set img="ovc.svg" %} + {% endif %} + {% endif %} + +
+ {% if weatherData[dt]["temperature"]["2m"]-273.15<=0 %} + {{(weatherData[dt]["temperature"]["2m"]-273.15)|round(1)}} + {% else %} + {{(weatherData[dt]["temperature"]["2m"]-273.15)|round(1)}} + {% endif %} + °C +
+
+ {{weatherData[dt]["humidite"]["2m"]}} + % +
+
+ {{weatherData[dt]["vent_moyen"]["10m"]}} + {% if weatherData[dt]["vent_rafales"]["10m"]>weatherData[dt]["vent_moyen"]["10m"] %} + | {{weatherData[dt]["vent_rafales"]["10m"]}} + {% endif %} + km/h + + + {% set dir=weatherData[dt]["vent_direction"]["10m"] %} + {% if dir > 360 %} + {% set dir=dir-360 %} + {% endif %} + {% if dir < 22.5 or dir >= 337.5 %} + N + {% endif %} + {% if dir >= 22.5 and dir < 67.5 %} + NE + {% endif %} + {% if dir >= 67.5 and dir < 112.5 %} + E + {% endif %} + {% if dir >= 112.5 and dir < 157.5 %} + SE + {% endif %} + {% if dir >= 157.5 and dir < 202.5 %} + S + {% endif %} + {% if dir >= 202.5 and dir < 247.5 %} + SW + {% endif %} + {% if dir >= 247.5 and dir < 292.5 %} + W + {% endif %} + {% if dir >= 292.5 and dir < 337.5 %} + NW + {% endif %} + + ({{dir}}°) + +
+ {% if weatherData[dt]["pluie"]>0 %} +
+ {{weatherData[dt]["pluie"]}} + mm/3h rain +
+ {% endif %} + {% if weatherData[dt]["risque_neige"]=="oui" %} +
+ {{weatherData[dt]["iso_zero"]}} + m freezing level +
+ {% endif %} +
+ {% endif %} +
+
+ Model run: {{weatherData["model_run"]}} + fetched {{fetchTime}} ago +
+{% endblock %} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..bc37223 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,14 @@ + + + + + + + + {% block title %}{% endblock %} + {% block head %}{% endblock %} + + + {% block body %}{% endblock %} + + \ No newline at end of file