Passed
Push — master ( 1b5cb4...0a1a6b )
by Viktor
01:49
created

api/darksky/darksky.go   A

Size/Duplication

Total Lines 270
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 192
dl 0
loc 270
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
F darksky.GetWeatherImage 0 142 14
A darksky.*DarkSkyData.GetTime 0 7 2
A darksky.*DarkSkyData.TZTime 0 2 1
1
package darksky
2
3
import (
4
	"bytes"
5
	"encoding/json"
6
	"fmt"
7
	"github.com/FlameInTheDark/dtbot/api/location"
8
	"github.com/FlameInTheDark/dtbot/bot"
9
	"github.com/fogleman/gg"
10
	"image/png"
11
	"net/http"
12
	"strings"
13
	"time"
14
)
15
16
// DarkSkyResponse contains main structures of API response
17
type DarkSkyResponse struct {
0 ignored issues
show
introduced by
type name will be used as darksky.DarkSkyResponse by other packages, and that stutters; consider calling this Response
Loading history...
18
	Latitude  float32       `json:"latitude"`
19
	Longitude float32       `json:"longitude"`
20
	Timezone  string        `json:"timezone"`
21
	Currently DarkSkyData   `json:"currently"`
22
	Hourly    DarkSkyHourly `json:"hourly"`
23
	Daily     DarkSkyDaily  `json:"daily"`
24
	Flags     DarkSkyFlags  `json:"flags"`
25
	Offset    int64         `json:"offset"`
26
}
27
28
// DarkSkyData contains main hourly weather data
29
type DarkSkyData struct {
0 ignored issues
show
introduced by
type name will be used as darksky.DarkSkyData by other packages, and that stutters; consider calling this Data
Loading history...
30
	Time                int64   `json:"time"`
31
	Summary             string  `json:"summary"`
32
	Icon                string  `json:"icon"`
33
	PrecipIntensity     float32 `json:"precipIntensity"`
34
	PrecipProbability   float32 `json:"precipProbability"`
35
	Temperature         float32 `json:"temperature"`
36
	ApparentTemperature float32 `json:"apparentTemperature"`
37
	DewPoint            float32 `json:"dewPoint"`
38
	Humidity            float32 `json:"humidity"`
39
	Pressure            float32 `json:"pressure"`
40
	WindSpeed           float32 `json:"windSpeed"`
41
	WindGust            float32 `json:"windGust"`
42
	WindBearing         int64   `json:"windBearing"`
43
	CloudCover          float32 `json:"cloudCover"`
44
	UVIndex             int64   `json:"uvIndex"`
45
	Visibility          float32 `json:"visibility"`
46
	Ozone               float32 `json:"ozone"`
47
}
48
49
// DarkSkyHourly contains hourly weather data array
50
type DarkSkyHourly struct {
0 ignored issues
show
introduced by
type name will be used as darksky.DarkSkyHourly by other packages, and that stutters; consider calling this Hourly
Loading history...
51
	Summary string        `json:"summary"`
52
	Icon    string        `json:"icon"`
53
	Data    []DarkSkyData `json:"data"`
54
}
55
56
// DarkSkyDaily contains daily weather data array
57
type DarkSkyDaily struct {
0 ignored issues
show
introduced by
type name will be used as darksky.DarkSkyDaily by other packages, and that stutters; consider calling this Daily
Loading history...
58
	Summary string           `json:"summary"`
59
	Icon    string           `json:"icon"`
60
	Data    []DarkSkyDayData `json:"data"`
61
}
62
63
// DarkSkyDayData contains main daily weather data
64
type DarkSkyDayData struct {
0 ignored issues
show
introduced by
type name will be used as darksky.DarkSkyDayData by other packages, and that stutters; consider calling this DayData
Loading history...
65
	Time                        int64   `json:"time"`
66
	Summary                     string  `json:"summary"`
67
	Icon                        string  `json:"icon"`
68
	SunriseTime                 int64   `json:"sunriseTime"`
69
	SunsetTime                  int64   `json:"sunsetTime"`
70
	MoonPhase                   float32 `json:"moonPhase"`
71
	PrecipIntensity             float32 `json:"precipIntensity"`
72
	PrecipIntensityMax          float32 `json:"precipIntensityMax"`
73
	PrecipIntensityMaxTime      int64   `json:"precipIntensityMaxTime"`
74
	PrecipProbability           float32 `json:"precipProbability"`
75
	PrecipAccumulation          float32 `json:"precipAccumulation"`
76
	PrecipType                  string  `json:"precipType"`
77
	TemperatureHigh             float32 `json:"temperatureHigh"`
78
	TemperatureHighTime         int64   `json:"temperatureHighTime"`
79
	TemperatureLow              float32 `json:"temperatureLow"`
80
	TemperatureLowTime          int64   `json:"temperatureLowTime"`
81
	ApparentTemperatureHigh     float32 `json:"apparentTemperatureHigh"`
82
	ApparentTemperatureHighTime int64   `json:"apparentTemperatureHighTime"`
83
	ApparentTemperatureLow      float32 `json:"apparentTemperatureLow"`
84
	ApparentTemperatureLowTime  int64   `json:"apparentTemperatureLowTime"`
85
	DewPoint                    float32 `json:"dewPoint"`
86
	Humidity                    float32 `json:"humidity"`
87
	Pressure                    float32 `json:"pressure"`
88
	WindSpeed                   float32 `json:"windSpeed"`
89
	WindGust                    float32 `json:"windGust"`
90
	WindGustTime                int64   `json:"windGustTime"`
91
	WindBearing                 int64   `json:"windBearing"`
92
	CloudCover                  float32 `json:"cloudCover"`
93
	UVIndex                     int64   `json:"uvIndex"`
94
	UVIndexTime                 int64   `json:"uvIndexTime"`
95
	Visibility                  float32 `json:"visibility"`
96
	Ozone                       float32 `json:"ozone"`
97
	TemperatureMin              float32 `json:"temperatureMin"`
98
	TemperatureMinTime          int64   `json:"temperatureMinTime"`
99
	TemperatureMax              float32 `json:"temperatureMax"`
100
	TemperatureMaxTime          int64   `json:"temperatureMaxTime"`
101
	ApparentTemperatureMin      float32 `json:"apparentTemperatureMin"`
102
	ApparentTemperatureMinTime  int64   `json:"apparentTemperatureMinTime"`
103
	ApparentTemperatureMax      float32 `json:"apparentTemperatureMax"`
104
	ApparentTemperatureMaxTime  int64   `json:"apparentTemperatureMaxTime"`
105
}
106
107
// DarkSkyFlags contains response flags
108
type DarkSkyFlags struct {
0 ignored issues
show
introduced by
type name will be used as darksky.DarkSkyFlags by other packages, and that stutters; consider calling this Flags
Loading history...
109
	Sources        []string `json:"sources"`
110
	NearestStation float32  `json:"nearest-station"`
111
	Units          string   `json:"units"`
112
}
113
114
func (d *DarkSkyData) GetTime(location string, tz int) time.Time {
0 ignored issues
show
introduced by
exported method DarkSkyData.GetTime should have comment or be unexported
Loading history...
115
	fLoc, fLocErr := time.LoadLocation(location)
116
	if fLocErr != nil {
117
		fmt.Printf("Weather timezone error: %v", fLocErr)
118
		return d.TZTime(tz)
119
	}
120
	return time.Unix(d.Time, 0).UTC().In(fLoc)
121
}
122
123
// TZTime converts epoch date to normal with timezone
124
func (d *DarkSkyData) TZTime(tz int) time.Time {
125
	return time.Unix(d.Time, 0).UTC().Add(time.Hour * time.Duration(tz))
126
}
127
128
// GetWeatherImage returns weather image widget
129
func GetWeatherImage(ctx *bot.Context) (buf *bytes.Buffer, err error) {
130
	var (
131
		forecast DarkSkyResponse
132
		city     = ctx.GetGuild().WeatherCity
133
	)
134
135
	if len(ctx.Args) > 0 {
136
		city = strings.Join(ctx.Args, "+")
137
	}
138
139
	loc, err := location.New(ctx.Conf.General.GeonamesUsername, city)
140
	if err != nil {
141
		fmt.Printf("Location API: %v", err)
142
		return
143
	}
144
145
	cityName := loc.Geonames[0].CountryName + ", " + loc.Geonames[0].Name
146
147
	// Get coordinates and get weather data
148
	newlat, newlng := loc.GetCoordinates()
149
	resp, err := http.Get(fmt.Sprintf("https://api.darksky.net/forecast/%v/%v,%v?units=ca&lang=%v",
150
		ctx.Conf.DarkSky.Token, newlat, newlng, ctx.Conf.General.Language))
151
	if err != nil {
152
		fmt.Printf("Weather API: %v", err)
153
		return
154
	}
155
156
	err = json.NewDecoder(resp.Body).Decode(&forecast)
157
	if err != nil {
158
		fmt.Printf("Weather Decode: %v", err)
159
		return
160
	}
161
162
	gc := gg.NewContext(400, 650)
163
	gc.SetRGBA(0, 0, 0, 0)
164
	gc.Clear()
165
166
	// Template
167
	gc.SetRGB255(242, 97, 73)
168
	gc.DrawRoundedRectangle(0, 0, 400, 650, 10)
169
	gc.Fill()
170
171
	// Weather lines
172
	gc.SetRGB255(234, 89, 65)
173
	gc.DrawRectangle(0, 250, 400, 100)
174
	gc.DrawRectangle(0, 450, 400, 100)
175
	gc.Fill()
176
177
	gc.SetLineWidth(2)
178
	gc.SetRGBA(0, 0, 0, 0.05)
179
	gc.DrawLine(0, 250, 400, 250)
180
	gc.DrawLine(0, 349, 400, 348)
181
	gc.DrawLine(0, 450, 400, 450)
182
	gc.DrawLine(0, 549, 400, 548)
183
	gc.Stroke()
184
185
	// Text
186
	if err := gc.LoadFontFace("lato.ttf", 20); err != nil {
187
		panic(err)
188
	}
189
190
	// Header (place and date)
191
	gc.SetRGBA(1, 1, 1, 0.7)
192
	gc.DrawStringAnchored(cityName, 10, 15, 0, 0.5)
193
	gc.SetRGBA(1, 1, 1, 0.4)
194
	gc.DrawStringAnchored(time.Now().Format("Jan 2, 2006"), 270, 15, 0, 0.5)
195
196
	// First weather data
197
	gc.SetRGBA(1, 1, 1, 0.5)
198
	if err := gc.LoadFontFace("lato.ttf", 30); err != nil {
199
		panic(err)
200
	}
201
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Currently.GetTime(forecast.Timezone, ctx.Conf.General.Timezone).Hour()), 50, 200, 0.5, 0.5)
202
	gc.DrawStringAnchored(fmt.Sprintf("H:%v%%", int(forecast.Currently.Humidity*100)), 200, 200, 0.5, 0.5)
203
	gc.DrawStringAnchored(fmt.Sprintf("C:%v%%", int(forecast.Currently.CloudCover*100)), 350, 200, 0.5, 0.5)
204
205
	gc.SetRGBA(1, 1, 1, 1)
206
	if err := gc.LoadFontFace("lato.ttf", 90); err != nil {
207
		panic(err)
208
	}
209
210
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Currently.Temperature)), 100, 120, 0.5, 0.5)
211
212
	if err := gc.LoadFontFace("weathericons.ttf", 70); err != nil {
213
		panic(err)
214
	}
215
216
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Currently.Icon)), 250, 120, 0, 0.7)
217
218
	if err := gc.LoadFontFace("lato.ttf", 30); err != nil {
219
		panic(err)
220
	}
221
222
	// Time
223
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Hourly.Data[2].GetTime(forecast.Timezone, ctx.Conf.General.Timezone).Hour()), 100, 285, 0, 0.5)
224
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Hourly.Data[4].GetTime(forecast.Timezone, ctx.Conf.General.Timezone).Hour()), 100, 385, 0, 0.5)
225
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Hourly.Data[6].GetTime(forecast.Timezone, ctx.Conf.General.Timezone).Hour()), 100, 485, 0, 0.5)
226
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Hourly.Data[8].GetTime(forecast.Timezone, ctx.Conf.General.Timezone).Hour()), 100, 585, 0, 0.5)
227
228
	// Humidity and cloudiness
229
	if err := gc.LoadFontFace("lato.ttf", 20); err != nil {
230
		panic(err)
231
	}
232
	gc.SetRGBA(1, 1, 1, 0.5)
233
234
	gc.DrawStringAnchored(fmt.Sprintf("H:%v%%", int(forecast.Hourly.Data[2].Humidity*100)), 100, 315, 0, 0.5)
235
	gc.DrawStringAnchored(fmt.Sprintf("H:%v%%", int(forecast.Hourly.Data[4].Humidity*100)), 100, 415, 0, 0.5)
236
	gc.DrawStringAnchored(fmt.Sprintf("H:%v%%", int(forecast.Hourly.Data[6].Humidity*100)), 100, 515, 0, 0.5)
237
	gc.DrawStringAnchored(fmt.Sprintf("H:%v%%", int(forecast.Hourly.Data[8].Humidity*100)), 100, 615, 0, 0.5)
238
239
	gc.DrawStringAnchored(fmt.Sprintf("C:%v%%", int(forecast.Hourly.Data[2].CloudCover*100)), 170, 315, 0, 0.5)
240
	gc.DrawStringAnchored(fmt.Sprintf("C:%v%%", int(forecast.Hourly.Data[4].CloudCover*100)), 170, 415, 0, 0.5)
241
	gc.DrawStringAnchored(fmt.Sprintf("C:%v%%", int(forecast.Hourly.Data[6].CloudCover*100)), 170, 515, 0, 0.5)
242
	gc.DrawStringAnchored(fmt.Sprintf("C:%v%%", int(forecast.Hourly.Data[8].CloudCover*100)), 170, 615, 0, 0.5)
243
244
	gc.SetRGBA(1, 1, 1, 1)
245
	if err := gc.LoadFontFace("lato.ttf", 50); err != nil {
246
		panic(err)
247
	}
248
249
	// Temperature
250
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Hourly.Data[2].Temperature)), 320, 300, 0.5, 0.5)
251
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Hourly.Data[4].Temperature)), 320, 400, 0.5, 0.5)
252
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Hourly.Data[6].Temperature)), 320, 500, 0.5, 0.5)
253
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Hourly.Data[8].Temperature)), 320, 600, 0.5, 0.5)
254
255
	if err := gc.LoadFontFace("weathericons.ttf", 40); err != nil {
256
		panic(err)
257
	}
258
259
	// Weather icon
260
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Hourly.Data[2].Icon)), 20, 300, 0, 0.7)
261
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Hourly.Data[4].Icon)), 20, 400, 0, 0.7)
262
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Hourly.Data[6].Icon)), 20, 500, 0, 0.7)
263
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Hourly.Data[8].Icon)), 20, 600, 0, 0.7)
264
265
	buf = new(bytes.Buffer)
266
	pngerr := png.Encode(buf, gc.Image())
267
	if pngerr != nil {
268
		fmt.Printf("Image: %v", pngerr)
269
	}
270
	return
271
}
272