New weather layout

This commit is contained in:
Zakary Timson 2018-12-07 17:55:54 -05:00
parent 24c4addd48
commit b23739edf1
11 changed files with 267 additions and 489 deletions

View File

@ -24,7 +24,7 @@
<mat-divider></mat-divider>
</mat-nav-list>
</mat-drawer>
<mat-drawer-content class="bg-secondary text-white p-4" (click)="open = (mobile && open) ? false : open">
<mat-drawer-content class="bg-secondary text-white" (click)="open = (mobile && open) ? false : open">
<main class="h-100" [@routerTransition]="transition(o)">
<router-outlet #o="outlet"></router-outlet>
</main>

View File

@ -25,6 +25,7 @@ import {environment} from '../environments/environment';
import * as firebase from 'firebase/app';
import { ServiceWorkerModule } from '@angular/service-worker';
import {LineChartModule, NgxChartsModule} from '@swimlane/ngx-charts';
import { RoundPipe } from './round.pipe';
export const firebaseApp = firebase.initializeApp(environment.firebase);
@ -36,7 +37,8 @@ export const firebaseApp = firebase.initializeApp(environment.firebase);
WeatherComponent,
SecurityComponent,
SettingsComponent,
LoginComponent
LoginComponent,
RoundPipe
],
imports: [
AppRoutingModule,

View File

@ -1,3 +1,4 @@
<div class="p-3">
<h1 class="mb-3">
<mat-icon>{{batteryService.icon}}</mat-icon>
Powerwall:
@ -30,3 +31,4 @@
[roundDomains]="true"
></ngx-charts-area-chart>
</div>
</div>

11
src/app/round.pipe.ts Normal file
View File

@ -0,0 +1,11 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'round'
})
export class RoundPipe implements PipeTransform {
transform(value: number, decimalPlaces: number = 0): any {
const shift = Math.pow(10, decimalPlaces);
return Math.round(value * shift) / shift;
}
}

View File

@ -1,30 +1,111 @@
<div class="w-100 p-3 pt-5" [style.backgroundColor]="day ? '#88aaff' : '#000e31'">
<!-- Current Weather -->
<div class="d-flex flex-column align-items-center">
<div>
<div class="d-flex flex-column flex-md-row justify-content-center align-items-center flex-grow-1">
<i [class]="'wi wi-fw ' + weatherService.icon" style="font-size: 6rem"></i>
<div class="ml-0 ml-md-3">
<h1 class="my-4 my-md-0 font-weight-bold text-center text-md-left">{{weatherService.temp}} °C</h1>
<h3 class="m-0">{{weatherService.weather}}</h3>
<h3>{{weatherService.weather?.currently.summary}}</h3>
</div>
<div class="d-flex flex-column flex-md-row align-items-center justify-content-center">
<div class="p-3 text-center">
<i [class]="'mt-4 wi wi-fw ' + weatherService.icon" style="font-size: 6rem"></i>
</div>
<div class="d-flex">
<div class="d-flex flex-column mr-3">
<div>
<h1 class="mb-0">{{weatherService.weather?.currently.temperature | round: 1}} °C</h1>
</div>
<div>
Feels Like: {{weatherService.weather?.currently.apparentTemperature | round}} °C
</div>
</div>
<div class="my-4 d-flex mx-auto" style="max-width: 450px">
<div class="text-center flex-grow-1">
<i class="wi wi-fw wi-umbrella"></i> {{weatherService.pop}} mm
<div class="d-flex flex-column">
<div>
<mat-icon style="font-size: 18px; width: 18px; height: 18px">arrow_upward</mat-icon>
{{weatherService.weather?.daily.data[0].temperatureHigh | round}} °C
</div>
<div class="text-center flex-grow-1">
<i class="wi wi-fw wi-cloud"></i> {{weatherService.cloudCover}} %
<div>
<mat-icon style="font-size: 18px; width: 18px; height: 18px">arrow_downward</mat-icon>
{{weatherService.weather?.daily.data[0].temperatureLow | round}} °C
</div>
<div class="text-center flex-grow-1">
<i class="wi wi-fw wi-strong-wind"></i> {{weatherService.wind[1]}} KM/H
<div>
<i class="wi wi-fw wi-umbrella"></i> {{weatherService.weather?.daily.data[0].precipProbability * 100 | round}}%
</div>
</div>
<mat-divider></mat-divider>
<div class="my-4 d-flex justify-content-center">
<div *ngFor="let w of weatherService.forecast" class="d-flex flex-column align-items-center flex-grow-1" style="max-width: 75px;">
{{w.day}}
<i [class]="'my-2 wi wi-fw ' + w.icon" style="font-size: 2rem"></i>
<div class="text-center">{{w.max}} °C</div>
<div class="text-center text-muted">{{w.min}} °C</div>
</div>
</div>
<mat-divider></mat-divider>
</div>
<!-- Forecast -->
<mat-card class="m-3" style="max-width: 450px">
<div class="d-flex justify-content-center">
<div *ngFor="let day of weatherService.weather?.daily.data.slice(1, 7)" class="d-flex flex-column align-items-center flex-grow-1" style="max-width: 75px;">
{{day.time.toString().slice(0, 4).toUpperCase()}}
<i [class]="'my-2 wi wi-fw ' + day.icon" style="font-size: 2rem"></i>
<div class="text-center">{{day.temperatureHigh | round}} °C</div>
<div class="text-center text-muted">{{day.temperatureLow | round}} °C</div>
</div>
</div>
</mat-card>
<!-- Sunlight -->
<mat-card class="m-3" style="max-width: 450px">
<h5>Sunlight</h5>
<div class="d-flex justify-content-center">
<div class="d-flex align-items-center flex-grow-1">
<i class="wi wi-sunrise mr-1"></i> {{weatherService.weather?.daily.data[0].sunriseTime | date: 'shortTime'}}
</div>
<div class="d-flex align-items-center justify-content-center flex-grow-1">
<i class="wi wi-fw wi-cloud mr-1"></i> {{weatherService.weather?.currently.cloudCover * 100 | round}}%
</div>
<div class="d-flex align-items-center justify-content-end flex-grow-1">
<i class="wi wi-sunset mr-1"></i> {{weatherService.weather?.daily.data[0].sunsetTime | date: 'shortTime'}}
</div>
</div>
<canvas id="myCanvas" class="mt-3" style="width: 100%" height="150"></canvas>
</mat-card>
<!-- Wind -->
<mat-card class="m-3" style="max-width: 450px">
<h5>Wind</h5>
<div class="d-flex justify-content-center">
<div class="d-flex align-items-center flex-grow-1">
<i class="wi wi-fw wi-windy"></i> {{weatherService.weather?.currently.windSpeed | round}} km/h
</div>
<div class="d-flex align-items-center justify-content-center flex-grow-1">
{{weatherService.weather?.currently.windBearing}}°
</div>
<div class="d-flex align-items-center justify-content-end flex-grow-1">
<i class="wi wi-fw wi-strong-wind"></i> {{weatherService.weather?.currently.windGust | round}} km/h
</div>
</div>
<div class="p-3 text-center">
<i class="wi wi-wind wi-fw wi-towards-0" [style.transform]="'rotate(' + weatherService.weather?.currently.windBearing + 'deg)'" style="color: #5d8cff; font-size: 14rem; height: 14rem; width: 14rem"></i>
</div>
</mat-card>
<!-- Atmospheric -->
<mat-card class="m-3" style="max-width: 450px">
<h5>Atmospheric</h5>
<div class="d-flex">
<div class="flex-grow-1">
<div>
<i class="wi wi-fw wi-barometer"></i> {{weatherService.weather?.currently.pressure / 10 | round: 1}} kPa
</div>
<div>
<i class="wi wi-fw wi-hot"></i> UV Index: {{weatherService.weather?.currently.uvIndex}}
</div>
<div>
<mat-icon>remove_red_eye</mat-icon> {{weatherService.weather?.currently.visibility | round}} km
</div>
</div>
<div class="flex-grow-1">
<div>
<i class="wi wi-fw wi-humidity"></i> {{weatherService.weather?.currently.humidity * 100 | round}}% Humidity
</div>
<div>
<i class="wi wi-fw wi-umbrella"></i> {{weatherService.weather?.daily.data[0].precipProbability * 100 | round}}%
</div>
<div>
<i class="wi wi-fw wi-flood"></i> {{weatherService.weather?.daily.data[0].precipAccumulation | round: 1}} cm {{weatherService.weather?.daily.data[0].precipType}}
</div>
</div>
</div>
</mat-card>
</div>

View File

@ -1,15 +1,89 @@
import {Component, OnInit} from '@angular/core';
import {WeatherService} from './weather.service';
import {timer} from 'rxjs';
import {filter} from 'rxjs/operators';
@Component({
selector: 'app-weather',
templateUrl: './weather.component.html'
})
export class WeatherComponent implements OnInit {
day = false;
constructor(public weatherService: WeatherService) { }
ngOnInit() {
timer(0, 1000).pipe(filter(() => this.weatherService.weather)).subscribe(() => {
const now = new Date().getTime();
const sunrise = this.weatherService.weather.daily.data[0].sunriseTime.getTime();
const sunset = this.weatherService.weather.daily.data[0].sunsetTime.getTime();
this.day = now > sunrise && now < sunset;
let diff = sunset - sunrise;
let current = new Date().getTime() - sunrise;
this.drawSunChart(current / diff);
});
}
drawSunChart(progress: number) {
const c = <HTMLCanvasElement>document.getElementById('myCanvas');
if (c) {
const ctx = c.getContext('2d');
// All the points in 2D space we care about
const width = c.width;
const height = c.height;
const centerX = width / 2;
const centerY = height - 20;
const radius = height * 0.8;
const pointX = centerX + radius * Math.cos(Math.PI * (1 + progress));
const pointY = centerY + radius * Math.sin(Math.PI * (1 + progress));
// Reset
ctx.clearRect(0, 0, width, height);
// Path background
ctx.fillStyle = '#aeaeae';
ctx.beginPath();
ctx.arc(centerX, centerY, radius, Math.PI, 0);
ctx.fill();
ctx.stroke();
// Path
ctx.lineWidth = 5;
ctx.strokeStyle = '#585858';
ctx.fillStyle = '#585858';
ctx.beginPath();
ctx.arc(centerX, centerY, radius, Math.PI, 0);
ctx.stroke();
// Show sun
if(this.day) {
// Stroke background
ctx.lineWidth = 2;
ctx.fillStyle = '#b3ad00';
ctx.beginPath();
ctx.arc(centerX, centerY, radius, Math.PI, Math.PI * (1 + progress));
ctx.lineTo(pointX, centerY);
ctx.fill();
ctx.stroke();
// Stroke
ctx.lineWidth = 6;
ctx.strokeStyle = '#e5df00';
ctx.fillStyle = '#e5df00';
ctx.beginPath();
ctx.arc(centerX, centerY, radius, Math.PI, Math.PI * (1 + progress));
ctx.stroke();
// Stroke end dot
ctx.lineWidth = 1;
ctx.fillStyle = '#e5df00';
ctx.beginPath();
ctx.arc(pointX, pointY, 6, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
}
}
}
}

View File

@ -1,73 +1,31 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {WeatherIcons} from './weatherIcons';
import {timer} from 'rxjs';
import {database} from 'firebase';
@Injectable({
providedIn: 'root'
})
export class WeatherService {
readonly apiKey = 'e8391af54b6fc09dc82b019fc68b8409';
readonly city = 'London';
readonly countryCode = 'CA';
readonly days = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
readonly weatherCodes = require('./weatherCodes.json');
// Weather information
cloudCover = 0;
forecast = [];
humidity = 0;
icon: string;
pop = 0;
pressure = 0;
sunrise: Date;
sunset: Date;
temp = 0;
tempMin = 0;
tempMax = 0;
weather: string = '';
wind: [number, number] = [0, 0];
lat = 42.9849;
lng = -81.2453;
units = 'ca';
weather: any;
constructor(httpClient: HttpClient) {
constructor(http: HttpClient) {
// Get weather every 5 minutes
timer(0, 5 * 60000).subscribe(async () => {
// Current weather information
httpClient.get(`https://api.openweathermap.org/data/2.5/weather?q=${this.city},${this.countryCode}&APPID=${this.apiKey}&units=metric`).toPromise().then((weather: any) => {
this.cloudCover = weather.clouds.all;
this.humidity = weather.main.humidity;
this.icon = `wi-${this.weatherCodes[weather.weather[0].id].icon}`;
this.pressure = weather.main.pressure;
this.sunrise = new Date(weather.sys.sunrise);
this.sunset = new Date(weather.sys.sunset);
this.temp = Math.round(weather.main.temp);
this.tempMin = Math.round(weather.main.temp_min);
this.tempMax = Math.round(weather.main.temp_max);
this.weather = weather.weather[0].description;
this.wind = [weather.wind.deg, Math.round(weather.wind.speed)];
});
this.weather = await http.get(`https://cors-anywhere.herokuapp.com/https://api.darksky.net/forecast/88373fac9e52db6ad33a296f3b30404d/${this.lat},${this.lng}?units=${this.units}`).toPromise();
// 5 day forecast
httpClient.get(`https://api.openweathermap.org/data/2.5/forecast?q=${this.city},${this.countryCode}&APPID=${this.apiKey}&units=metric`).toPromise().then((weather: any) => {
this.pop = Math.round(weather.list.slice(0, 4).reduce((acc, weather) => {
if(weather['rain']) acc += weather['rain']['3h'] || 0;
if(weather['snow']) acc += weather['snow']['3h'] || 0;
return acc;
}, 0) * 10) / 10;
let temp = {};
weather.list.forEach(weather => {
let timestamp = new Date(weather.dt * 1000);
let day = timestamp.getDate();
console.log(timestamp.getHours());
if(!temp[day]) temp[day] = {};
if(!temp[day].max || weather.main.temp_max > temp[day].max) temp[day].max = Math.round(weather.main.temp_max);
if(!temp[day].min || weather.main.temp_min < temp[day].min) temp[day].min = Math.round(weather.main.temp_min);
if(!temp[day].day || timestamp.getHours() == 13) {
temp[day].day = timestamp.toString().substring(0, 4);
temp[day].icon = `wi-${this.weatherCodes[weather.weather[0].id].icon}`;
}
});
this.forecast = Object.values(temp);
});
// Format data
this.weather.daily.data.map(day => Object.assign(day, {
icon: WeatherIcons[day.icon],
time: new Date(day.time * 1000),
sunsetTime: new Date(day.sunsetTime * 1000),
sunriseTime: new Date(day.sunriseTime * 1000),
}));
this.icon = WeatherIcons[this.weather.currently.icon];
});
}
}

View File

@ -1,367 +0,0 @@
{
"200": {
"label": "thunderstorm with light rain",
"icon": "storm-showers"
},
"201": {
"label": "thunderstorm with rain",
"icon": "storm-showers"
},
"202": {
"label": "thunderstorm with heavy rain",
"icon": "storm-showers"
},
"210": {
"label": "light thunderstorm",
"icon": "storm-showers"
},
"211": {
"label": "thunderstorm",
"icon": "thunderstorm"
},
"212": {
"label": "heavy thunderstorm",
"icon": "thunderstorm"
},
"221": {
"label": "ragged thunderstorm",
"icon": "thunderstorm"
},
"230": {
"label": "thunderstorm with light drizzle",
"icon": "storm-showers"
},
"231": {
"label": "thunderstorm with drizzle",
"icon": "storm-showers"
},
"232": {
"label": "thunderstorm with heavy drizzle",
"icon": "storm-showers"
},
"300": {
"label": "light intensity drizzle",
"icon": "sprinkle"
},
"301": {
"label": "drizzle",
"icon": "sprinkle"
},
"302": {
"label": "heavy intensity drizzle",
"icon": "sprinkle"
},
"310": {
"label": "light intensity drizzle rain",
"icon": "sprinkle"
},
"311": {
"label": "drizzle rain",
"icon": "sprinkle"
},
"312": {
"label": "heavy intensity drizzle rain",
"icon": "sprinkle"
},
"313": {
"label": "shower rain and drizzle",
"icon": "sprinkle"
},
"314": {
"label": "heavy shower rain and drizzle",
"icon": "sprinkle"
},
"321": {
"label": "shower drizzle",
"icon": "sprinkle"
},
"500": {
"label": "light rain",
"icon": "rain"
},
"501": {
"label": "moderate rain",
"icon": "rain"
},
"502": {
"label": "heavy intensity rain",
"icon": "rain"
},
"503": {
"label": "very heavy rain",
"icon": "rain"
},
"504": {
"label": "extreme rain",
"icon": "rain"
},
"511": {
"label": "freezing rain",
"icon": "rain-mix"
},
"520": {
"label": "light intensity shower rain",
"icon": "showers"
},
"521": {
"label": "shower rain",
"icon": "showers"
},
"522": {
"label": "heavy intensity shower rain",
"icon": "showers"
},
"531": {
"label": "ragged shower rain",
"icon": "showers"
},
"600": {
"label": "light snow",
"icon": "snow"
},
"601": {
"label": "snow",
"icon": "snow"
},
"602": {
"label": "heavy snow",
"icon": "snow"
},
"611": {
"label": "sleet",
"icon": "sleet"
},
"612": {
"label": "shower sleet",
"icon": "sleet"
},
"615": {
"label": "light rain and snow",
"icon": "rain-mix"
},
"616": {
"label": "rain and snow",
"icon": "rain-mix"
},
"620": {
"label": "light shower snow",
"icon": "rain-mix"
},
"621": {
"label": "shower snow",
"icon": "rain-mix"
},
"622": {
"label": "heavy shower snow",
"icon": "rain-mix"
},
"701": {
"label": "mist",
"icon": "sprinkle"
},
"711": {
"label": "smoke",
"icon": "smoke"
},
"721": {
"label": "haze",
"icon": "day-haze"
},
"731": {
"label": "sand, dust whirls",
"icon": "cloudy-gusts"
},
"741": {
"label": "fog",
"icon": "fog"
},
"751": {
"label": "sand",
"icon": "cloudy-gusts"
},
"761": {
"label": "dust",
"icon": "dust"
},
"762": {
"label": "volcanic ash",
"icon": "smog"
},
"771": {
"label": "squalls",
"icon": "day-windy"
},
"781": {
"label": "tornado",
"icon": "tornado"
},
"800": {
"label": "clear sky",
"icon": "day-sunny"
},
"801": {
"label": "few clouds",
"icon": "cloudy"
},
"802": {
"label": "scattered clouds",
"icon": "cloudy"
},
"803": {
"label": "broken clouds",
"icon": "cloudy"
},
"804": {
"label": "overcast clouds",
"icon": "cloudy"
},
"900": {
"label": "tornado",
"icon": "tornado"
},
"901": {
"label": "tropical storm",
"icon": "hurricane"
},
"902": {
"label": "hurricane",
"icon": "hurricane"
},
"903": {
"label": "cold",
"icon": "snowflake-cold"
},
"904": {
"label": "hot",
"icon": "hot"
},
"905": {
"label": "windy",
"icon": "windy"
},
"906": {
"label": "hail",
"icon": "hail"
},
"951": {
"label": "calm",
"icon": "day-sunny"
},
"952": {
"label": "light breeze",
"icon": "cloudy-gusts"
},
"953": {
"label": "gentle breeze",
"icon": "cloudy-gusts"
},
"954": {
"label": "moderate breeze",
"icon": "cloudy-gusts"
},
"955": {
"label": "fresh breeze",
"icon": "cloudy-gusts"
},
"956": {
"label": "strong breeze",
"icon": "cloudy-gusts"
},
"957": {
"label": "high wind, near gale",
"icon": "cloudy-gusts"
},
"958": {
"label": "gale",
"icon": "cloudy-gusts"
},
"959": {
"label": "severe gale",
"icon": "cloudy-gusts"
},
"960": {
"label": "storm",
"icon": "thunderstorm"
},
"961": {
"label": "violent storm",
"icon": "thunderstorm"
},
"962": {
"label": "hurricane",
"icon": "cloudy-gusts"
}
}

View File

@ -0,0 +1,16 @@
export enum WeatherIcons {
'clear-day' = 'wi-day-sunny',
'clear-night' = 'wi-night-clear',
'cloudy' = 'wi-cloudy',
'default' = '',
'fog' = 'wi-fog',
'hail' = 'wi-hail',
'partly-cloudy-day' = 'wi-day-cloudy',
'partly-cloudy-night' = 'wi-night-alt-cloudy',
'rain' = 'wi-rain',
'sleet' = 'wi-sleet',
'snow' = 'wi-snow',
'thunderstorm' = 'wi-thunderstorm',
'tornado' = 'wi-tornado',
'wind' = 'wi-strong-wind',
}

View File

@ -12,9 +12,9 @@
<link rel="stylesheet" type="text/css" href="assets/bootstrap.min.css">
<link rel="manifest" href="manifest.json">
</head>
<body>
<body style="background-color: #2F323A;">
<app-root>
<div class="center">
<div style="position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);">
<div class="text-center">
<h1 class="d-inline text-white">Home Front</h1>
</div>

View File

@ -2,6 +2,7 @@
@import url('https://fonts.googleapis.com/css?family=Archivo|Material+Icons');
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
@import url('https://cdnjs.cloudflare.com/ajax/libs/weather-icons/2.0.9/css/weather-icons.min.css');
@import url('https://cdnjs.cloudflare.com/ajax/libs/weather-icons/2.0.9/css/weather-icons-wind.min.css');
::-webkit-scrollbar-track {
border-radius: 10px;