Completed
Push — master ( 5ea822...0be30c )
by Viktor
02:21
created

weather.GetWeatherImage   F

Complexity

Conditions 13

Size

Total Lines 137
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 82
nop 1
dl 0
loc 137
rs 3.529
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like weather.GetWeatherImage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package weather
2
3
import (
4
	"bytes"
5
	"encoding/json"
6
	"fmt"
7
	"image"
8
	"image/png"
9
	"net/http"
10
	"strings"
11
	"time"
12
13
	"github.com/FlameInTheDark/dtbot/api/location"
14
	"github.com/FlameInTheDark/dtbot/bot"
15
	"github.com/fogleman/gg"
16
)
17
18
// Forecast Weather forecast struct
19
type Forecast struct {
20
	Cod     string        `json:"cod"`
21
	Weather []WeatherData `json:"list"`
22
	City    CityData      `json:"city"`
23
}
24
25
// WeatherData Weather data struct
26
type WeatherData struct {
0 ignored issues
show
introduced by
type name will be used as weather.WeatherData by other packages, and that stutters; consider calling this Data
Loading history...
27
	Time   int64       `json:"dt"`
28
	Main   MainData    `json:"main"`
29
	Wind   WindData    `json:"wind"`
30
	Clouds CloudsData  `json:"clouds"`
31
	WDesc  []WDescData `json:"weather"`
32
}
33
34
// TZTime returns time in specified timezone
35
func (w WeatherData) TZTime(tz int) time.Time {
36
	return time.Unix(w.Time, 0).UTC().Add(time.Hour * time.Duration(tz))
37
}
38
39
// WDescData Weather description struct
40
type WDescData struct {
41
	Id   int64  `json:"id"`
0 ignored issues
show
introduced by
struct field Id should be ID
Loading history...
42
	Main string `json:"main"`
43
	Desc string `json:"description"`
44
	Icon string `json:"icon"`
45
}
46
47
// MainData Weather main data struct
48
type MainData struct {
49
	Temp     float64 `json:"temp"`
50
	Pressure float64 `json:"pressure"`
51
	TempMin  float64 `json:"temp_min"`
52
	TempMax  float64 `json:"temp_max"`
53
	Humidity int     `json:"humidity"`
54
}
55
56
// WindData Weather wind data struct
57
type WindData struct {
58
	Speed float64 `json:"speed"`
59
	Deg   float64 `json:"deg"`
60
}
61
62
// CloudsData Weather cloud data struct
63
type CloudsData struct {
64
	All int `json:"all"`
65
}
66
67
// CityData Weather city data struct
68
type CityData struct {
69
	Name string `json:"name"`
70
}
71
72
// DrawOne returns image with one day forecast
73
func DrawOne(temp, hum, clo int, time, icon string) image.Image {
74
	dpc := gg.NewContext(300, 400)
75
	dpc.SetRGBA(0, 0, 0, 0)
76
	dpc.Clear()
77
	// Drawing weather icon
78
79
	dpc.Push()
80
	if err := dpc.LoadFontFace("owfont-regular.ttf", 140); err != nil {
81
		fmt.Printf("Weather font: %v", err)
82
	}
83
	if temp < 0 {
84
		dpc.SetRGB255(51, 179, 216)
85
	} else {
86
		dpc.SetRGB255(231, 81, 56)
87
	}
88
	dpc.DrawStringAnchored(icon, 150, 145, 0.5, 0.5)
89
	dpc.Pop()
90
91
	// Drawing lines
92
	dpc.SetRGB255(76, 90, 109)
93
	dpc.SetLineWidth(6)
94
	dpc.DrawLine(299, 61, 299, 400)
95
	dpc.Stroke()
96
97
	// Drawing rectangle
98
	//dpc.DrawRectangle(0, 0, 300, 60)
99
	//dpc.SetRGBA(1, 1, 1, 0.6)
100
	//dpc.Fill()
101
102
	// Drawing hummidity and cloudnes
103
	if err := dpc.LoadFontFace("arial.ttf", 50); err != nil {
104
		fmt.Printf("Image font: %v", err)
105
	}
106
	dpc.SetRGB255(255, 255, 255)
107
	dpc.DrawStringAnchored(time, 150, 30, 0.5, 0.5)
108
	dpc.DrawStringAnchored(fmt.Sprintf("H: %v%%", hum), 150, 305, 0.5, 0.5)
109
	dpc.DrawStringAnchored(fmt.Sprintf("C: %v%%", clo), 150, 355, 0.5, 0.5)
110
111
	// Drawing temperature
112
	if err := dpc.LoadFontFace("arial.ttf", 80); err != nil {
113
		fmt.Printf("Image font: %v", err)
114
	}
115
	dpc.DrawStringAnchored(fmt.Sprintf("%v°", temp), 150, 230, 0.5, 0.5)
116
117
	return dpc.Image()
118
}
119
120
// GetWeatherImage returns buffer with weather image
121
func GetWeatherImage(ctx *bot.Context) (buf *bytes.Buffer, cityName string, err error) {
122
	var (
123
		forecast Forecast
124
		city     = ctx.GetGuild().WeatherCity
125
	)
126
127
	if len(ctx.Args) > 0 {
128
		city = strings.Join(ctx.Args, "+")
129
	}
130
131
	loc, err := location.New(ctx.Conf.General.GeonamesUsername, city)
132
	if err != nil {
133
		fmt.Printf("Location API: %v", err)
134
		return
135
	}
136
137
	cityName = loc.Geonames[0].CountryName + ", " + loc.Geonames[0].Name
138
139
	// Get coordinates and get weather data
140
	newlat, newlng := loc.GetCoordinates()
141
	resp, err := http.Get(fmt.Sprintf("https://api.openweathermap.org/data/2.5/forecast?lat=%v&lon=%v&lang=%v&units=metric&appid=%v",
142
		newlat, newlng, ctx.Conf.General.Language, ctx.Conf.Weather.WeatherToken))
143
	if err != nil {
144
		fmt.Printf("Weather API: %v", err)
145
		return
146
	}
147
148
	err = json.NewDecoder(resp.Body).Decode(&forecast)
149
	if err != nil {
150
		fmt.Printf("Weather Decode: %v", err)
151
		return
152
	}
153
154
	// Drawing forecast
155
	//dc := gg.NewContext(1500, 400)
156
	//dc.SetRGBA255(36, 48, 64, 255)
157
	//dc.Clear()
158
	//for i := 0; i < 6; i++ {
159
	//	dc.DrawImage(DrawOne(int(forecast.Weather[i].Main.TempMin),
160
	//		forecast.Weather[i].Main.Humidity,
161
	//		int(forecast.Weather[i].Clouds.All),
162
	//		fmt.Sprintf("%.2v:00", forecast.Weather[i].TZTime(ctx.Conf.General.Timezone).Hour()),
163
	//		ctx.WeatherCode(fmt.Sprintf("%v", forecast.Weather[i].WDesc[0].Id))), 300*i, 0)
164
	//}
165
166
	gc := gg.NewContext(400, 700)
167
	gc.Clear()
168
169
	// Template
170
	gc.SetRGB255(242, 97, 73)
171
	gc.DrawRectangle(0, 0, 400, 700)
172
	gc.Fill()
173
174
	gc.SetRGB255(234, 89, 65)
175
	gc.DrawRectangle(0, 250, 400, 100)
176
	gc.DrawRectangle(0, 450, 400, 100)
177
	gc.DrawRectangle(0, 650, 400, 100)
178
	gc.Fill()
179
180
	gc.SetLineWidth(2)
181
	gc.SetRGBA(0, 0, 0,0.05)
182
	gc.DrawLine(0, 250, 400, 250)
183
	gc.DrawLine(0, 348, 400, 348)
184
	gc.DrawLine(0, 450, 400, 450)
185
	gc.DrawLine(0, 548, 400, 548)
186
	gc.DrawLine(0, 650, 400, 650)
187
	gc.DrawLine(0, 748, 400, 748)
188
	gc.Stroke()
189
190
	// Text
191
	if err := gc.LoadFontFace("lato.ttf", 20); err != nil {
192
		panic(err)
193
	}
194
	// Header
195
	gc.SetRGBA(1, 1, 1, 0.7)
196
	gc.DrawStringAnchored(cityName, 10, 15, 0, 0.5)
197
	gc.SetRGBA(1, 1, 1, 0.4)
198
	gc.DrawStringAnchored(time.Now().Format("Jan 2, 2006"), 280, 15, 0, 0.5)
199
200
	// First weather data
201
	gc.SetRGBA(1, 1, 1, 0.5)
202
	if err := gc.LoadFontFace("lato.ttf", 30); err != nil {
203
		panic(err)
204
	}
205
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Weather[0].TZTime(ctx.Conf.General.Timezone).Hour()), 50, 200, 0.5, 0.5)
206
	gc.DrawStringAnchored(fmt.Sprintf("H:%v%%", forecast.Weather[0].Main.Humidity), 200, 200, 0.5, 0.5)
207
	gc.DrawStringAnchored(fmt.Sprintf("C:%v%%", int(forecast.Weather[0].Clouds.All)), 350, 200, 0.5, 0.5)
208
209
	gc.SetRGBA(1, 1, 1, 1)
210
	if err := gc.LoadFontFace("lato.ttf", 90); err != nil {
211
		panic(err)
212
	}
213
214
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Weather[0].Main.TempMin)), 30, 120, 0, 0.5)
215
216
	if err := gc.LoadFontFace("owfont-regular.ttf", 90); err != nil {
217
		panic(err)
218
	}
219
220
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Weather[0].WDesc[0].Id)), 250, 120, 0, 0.7)
221
222
	if err := gc.LoadFontFace("lato.ttf", 30); err != nil {
223
		panic(err)
224
	}
225
226
	// Time
227
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Weather[1].TZTime(ctx.Conf.General.Timezone).Hour()), 100, 300, 0, 0.5)
228
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Weather[2].TZTime(ctx.Conf.General.Timezone).Hour()), 100, 400, 0, 0.5)
229
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Weather[3].TZTime(ctx.Conf.General.Timezone).Hour()), 100, 500, 0, 0.5)
230
	gc.DrawStringAnchored(fmt.Sprintf("%.2v:00", forecast.Weather[4].TZTime(ctx.Conf.General.Timezone).Hour()), 100, 600, 0, 0.5)
231
232
	if err := gc.LoadFontFace("lato.ttf", 50); err != nil {
233
		panic(err)
234
	}
235
236
	// Temperature
237
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Weather[1].Main.TempMin)), 250, 300, 0, 0.5)
238
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Weather[2].Main.TempMin)), 250, 400, 0, 0.5)
239
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Weather[3].Main.TempMin)), 250, 500, 0, 0.5)
240
	gc.DrawStringAnchored(fmt.Sprintf("%v°", int(forecast.Weather[4].Main.TempMin)), 250, 600, 0, 0.5)
241
242
	if err := gc.LoadFontFace("owfont-regular.ttf", 60); err != nil {
243
		panic(err)
244
	}
245
246
	// Weather icon
247
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Weather[1].WDesc[0].Id)), 20, 300, 0, 0.7)
248
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Weather[2].WDesc[0].Id)), 20, 400, 0, 0.7)
249
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Weather[3].WDesc[0].Id)), 20, 500, 0, 0.7)
250
	gc.DrawStringAnchored(ctx.WeatherCode(fmt.Sprintf("%v", forecast.Weather[4].WDesc[0].Id)), 20, 600, 0, 0.7)
251
252
	buf = new(bytes.Buffer)
253
	pngerr := png.Encode(buf, gc.Image())
254
	if pngerr != nil {
255
		fmt.Printf("Image: %v", pngerr)
256
	}
257
	return
258
}
259