├── .gitignore ├── __init__.py ├── requirements.txt ├── .gitattributes ├── LICENSE ├── README.md ├── templates ├── index.html └── base.html ├── app.py └── sample-forecast.json /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | venv 3 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.20.0 2 | Flask==1.0 3 | Flask-Caching==1.3.3 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Julian Norton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Experiment to render the weather forecast as fast as possible. ~~Goal is sub 100ms.~~ ~40 milliseconds if you're in NYC. 2 | 3 | ## Set-up 4 | 5 | ``` 6 | python3 -m venv venv && source venv/bin/activate && pip3 install -r requirements.txt && FLASK_APP=app.py FLASK_DEBUG=1 python -m flask run --port=5001 7 | ``` 8 | 9 | 10 | #### Good resources 11 | * Setting up Flask: https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world 12 | 13 | 14 | ### Production setup 15 | 16 | ``` 17 | # Use a screen 18 | # [not needed after venv installed] sudo python3 -m venv venv 19 | screen 20 | source venv/bin/activate 21 | sudo -H pip3 install -r requirements.txt 22 | sudo FLASK_APP=app.py python3 -m flask run --port=5001 23 | ``` 24 | `Ctrl a` `p` to detatch from the screen 25 | 26 | ### Production reset 27 | 28 | ``` 29 | (To find python processes:) 30 | ps -ef | grep python 31 | sudo kill -9 23826 32 | ("23826" is whatever the number is returned that you want to kill) 33 | screen 34 | sudo git pull 35 | sudo python3 -m venv venv 36 | source venv/bin/activate 37 | sudo -H pip3 install -r requirements.txt 38 | sudo FLASK_APP=app.py python3 -m flask run --port=5001 39 | ``` 40 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Weather for {{areaDescription}}

7 | Refresh 8 |
9 |
10 |
    11 |
  1. 12 | Radar loop of the northeast United States 13 |
  2. 14 |
  3. 15 |

    {{forecast_time}} – {{forecast_data_weather}}

    16 |

    {{forecast_data_text}}

    17 |
  4. 18 |
  5. 19 |

    {{forecast_time_1}} – {{forecast_data_weather_1}}

    20 |

    {{forecast_data_text_1}}

    21 |
  6. 22 |
  7. 23 |

    {{forecast_time_2}} – {{forecast_data_weather_2}}

    24 |

    {{forecast_data_text_2}}

    25 |
  8. 26 |
  9. 27 |

    {{forecast_time_3}} – {{forecast_data_weather_3}}

    28 |

    {{forecast_data_text_3}}

    29 |
  10. 30 |
31 |
32 | 40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Manhattan Weather 12 | 13 | 14 | 15 | 16 |
17 | {% block content %}{% endblock %} 18 |
19 | 20 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import render_template 3 | from flask import json 4 | from flask_caching import Cache 5 | 6 | import datetime 7 | import requests 8 | 9 | app = Flask(__name__) 10 | 11 | cache = Cache(config={'CACHE_TYPE': 'simple'}) 12 | cache.init_app(app) 13 | 14 | @app.route('/') 15 | @app.route('/index') 16 | 17 | # 1800 seconds == 30 minutes 18 | @cache.cached(timeout=300) 19 | def index(): 20 | timestamp = datetime.datetime.now() 21 | raw_data = requests.get('https://forecast.weather.gov/MapClick.php?lat=40.74&lon=-74&unit=0&lg=english&FcstType=json').json() 22 | # raw_data = json.load(open('sample-forecast.json')) 23 | parsed_data = { 24 | 'areaDescription' : raw_data['location']['areaDescription'], 25 | 'forecast_time' : raw_data['time']['startPeriodName'][0], 26 | 'forecast_data_weather' : raw_data['data']['weather'][0], 27 | 'forecast_data_text' : raw_data['data']['text'][0], 28 | 'forecast_time_1' : raw_data['time']['startPeriodName'][1], 29 | 'forecast_data_weather_1' : raw_data['data']['weather'][1], 30 | 'forecast_data_text_1' : raw_data['data']['text'][1], 31 | 'forecast_time_2' : raw_data['time']['startPeriodName'][2], 32 | 'forecast_data_weather_2' : raw_data['data']['weather'][2], 33 | 'forecast_data_text_2' : raw_data['data']['text'][2], 34 | 'forecast_time_3' : raw_data['time']['startPeriodName'][3], 35 | 'forecast_data_weather_3' : raw_data['data']['weather'][3], 36 | 'forecast_data_text_3' : raw_data['data']['text'][3], 37 | 'forecast_data_weather_4' : raw_data['data']['weather'][4], 38 | 'forecast_data_text_4' : raw_data['data']['text'][4] 39 | } 40 | return render_template('index.html',timestamp=timestamp, **parsed_data) 41 | -------------------------------------------------------------------------------- /sample-forecast.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationalMode":"Production", 3 | "srsName":"WGS 1984", 4 | "creationDate":"2018-03-09T11:21:00-05:00", 5 | "creationDateLocal":"9 Mar 13:51 pm EST", 6 | "productionCenter":"New York, NY", 7 | "credit":"http://weather.gov/okx/", 8 | "moreInformation":"http://weather.gov", 9 | "location":{ 10 | "region":"erh", 11 | "latitude":"40.71", 12 | "longitude":"-73.98", 13 | "elevation":"26", 14 | "wfo":"OKX", 15 | "timezone":"E|Y|5", 16 | "areaDescription":"New York NY", 17 | "radar":"KDIX", 18 | "zone":"NYZ072", 19 | "county":"NYC061", 20 | "firezone":"NYZ072", 21 | "metar":"KNYC" 22 | }, 23 | "time":{ 24 | "layoutKey":"k-p12h-n13-1", 25 | "startPeriodName":[ 26 | "This Afternoon", 27 | "Tonight", 28 | "Saturday", 29 | "Saturday Night", 30 | "Sunday", 31 | "Sunday Night", 32 | "Monday", 33 | "Monday Night", 34 | "Tuesday", 35 | "Tuesday Night", 36 | "Wednesday", 37 | "Wednesday Night", 38 | "Thursday" 39 | ], 40 | "startValidTime":[ 41 | "2018-03-09T14:00:00-05:00", 42 | "2018-03-09T18:00:00-05:00", 43 | "2018-03-10T06:00:00-05:00", 44 | "2018-03-10T18:00:00-05:00", 45 | "2018-03-11T06:00:00-04:00", 46 | "2018-03-11T18:00:00-04:00", 47 | "2018-03-12T06:00:00-04:00", 48 | "2018-03-12T18:00:00-04:00", 49 | "2018-03-13T06:00:00-04:00", 50 | "2018-03-13T18:00:00-04:00", 51 | "2018-03-14T06:00:00-04:00", 52 | "2018-03-14T18:00:00-04:00", 53 | "2018-03-15T06:00:00-04:00" 54 | ], 55 | "tempLabel":["High","Low","High","Low","High","Low","High","Low","High","Low","High","Low","High"] 56 | }, 57 | "data":{ 58 | "temperature":["43","32","44","31","43","33","40","32","40","30","40","30","42"], 59 | "pop":["20",null,null,null,null,"30","40","30",null,null,null,null,null], 60 | 61 | "weather":[ 62 | "Slight Chance Rain/Snow", 63 | "Chance Sprinkles/Flurries then Partly Cloudy", 64 | "Mostly Sunny", 65 | "Mostly Clear", 66 | "Sunny", 67 | "Partly Cloudy then Chance Snow", 68 | "Chance Snow then Chance Rain/Snow", 69 | "Chance Snow", 70 | "Partly Sunny", 71 | "Partly Cloudy", 72 | "Mostly Sunny", 73 | "Partly Cloudy", 74 | "Mostly Sunny" 75 | ], 76 | "iconLink":[ 77 | "http://forecast.weather.gov/newimages/medium/ra_sn20.png", 78 | "http://forecast.weather.gov/DualImage.php?i=nra_sn&j=nsct&ip=10", 79 | "http://forecast.weather.gov/newimages/medium/sct.png", 80 | "http://forecast.weather.gov/newimages/medium/nfew.png", 81 | "http://forecast.weather.gov/newimages/medium/few.png", 82 | "http://forecast.weather.gov/DualImage.php?i=nsct&j=nsn&jp=30", 83 | "http://forecast.weather.gov/DualImage.php?i=sn&j=ra_sn&ip=40&jp=40", 84 | "http://forecast.weather.gov/newimages/medium/nsn30.png", 85 | "http://forecast.weather.gov/newimages/medium/bkn.png", 86 | "http://forecast.weather.gov/newimages/medium/nsct.png", 87 | "http://forecast.weather.gov/newimages/medium/sct.png", 88 | "http://forecast.weather.gov/newimages/medium/nsct.png", 89 | "http://forecast.weather.gov/newimages/medium/sct.png" 90 | ], 91 | "hazard":[ 92 | 93 | ], 94 | "hazardUrl":[ 95 | 96 | ], 97 | "text":[ 98 | "A slight chance of rain and snow showers. Mostly cloudy, with a high near 43. West wind around 17 mph. Chance of precipitation is 20%.", 99 | "A chance of sprinkles and flurries before 10pm, then a chance of flurries. Mostly cloudy, with a low around 32. Wind chill values between 25 and 30. West wind 11 to 14 mph. ", 100 | "Mostly sunny, with a high near 44. Wind chill values between 25 and 35. West wind 11 to 15 mph. ", 101 | "Mostly clear, with a low around 31. Northwest wind 8 to 10 mph. ", 102 | "Sunny, with a high near 43. Northwest wind 6 to 10 mph. ", 103 | "A 30 percent chance of snow after 1am. Mostly cloudy, with a low around 33.", 104 | "A chance of snow before 1pm, then a chance of rain and snow. Mostly cloudy, with a high near 40. Chance of precipitation is 40%.", 105 | "A 30 percent chance of snow. Mostly cloudy, with a low around 32.", 106 | "Partly sunny, with a high near 40.", 107 | "Partly cloudy, with a low around 30.", 108 | "Mostly sunny, with a high near 40.", 109 | "Partly cloudy, with a low around 30.", 110 | "Mostly sunny, with a high near 42." ] 111 | }, 112 | "currentobservation":{ 113 | "id":"KNYC", 114 | "name":"New York City, Central Park", 115 | "elev":"154", 116 | "latitude":"40.78", 117 | "longitude":"-73.97", 118 | "Date":"9 Mar 13:51 pm EST", 119 | "Temp":"41", 120 | "Dewp":"21", 121 | "Relh":"45", 122 | "Winds":"13", 123 | "Windd":"280", 124 | "Gust":"25", 125 | "Weather":"Mostly Cloudy", 126 | "Weatherimage":"bkn.png", 127 | "Visibility":"10.00", 128 | "Altimeter":"1006.1", 129 | "SLP":"29.74", 130 | "timezone":"EST", 131 | "state":"NY", 132 | "WindChill":"34" 133 | } 134 | } 135 | --------------------------------------------------------------------------------