diff --git a/cli/amesh.go b/cli/amesh.go index 099aaa3..fef32cf 100644 --- a/cli/amesh.go +++ b/cli/amesh.go @@ -17,7 +17,7 @@ func Amesh(r render.Renderer, geo, mask bool) error { } entry := amesh.GetEntry(now) - merged, err := entry.Image(geo, mask) + merged, err := entry.GetImage(geo, mask) if err != nil { return err } diff --git a/cli/timelapse.go b/cli/timelapse.go index b448843..418c179 100644 --- a/cli/timelapse.go +++ b/cli/timelapse.go @@ -20,7 +20,16 @@ func Timelapse(r render.Renderer, minutes, delay int, loop bool) error { fmt.Printf("直近%d分間の降雨画像を取得中", minutes) - snapshots, err := getSnapshots(time.Duration(minutes) * time.Minute) + now, err := getNow() + if err != nil { + return err + } + + start := now.Add(time.Duration(-1*minutes) * time.Minute) + entries := amesh.GetEntries(start, now) + + progress := func(i int) { fmt.Print(".") } + _, err = entries.GetImages(progress) if err != nil { return err } @@ -32,15 +41,10 @@ func Timelapse(r render.Renderer, minutes, delay int, loop bool) error { fmt.Print("\033[s\033[H\033[1;32m") } - length := len(snapshots) - for i := 0; true; i++ { - if i == length && !loop { - break - } - s := snapshots[i%length] + for _, entry := range entries { moveCursorToTop() - r.Render(os.Stdout, s.Image) - fmt.Println(s.Time.String()) + r.Render(os.Stdout, entry.Image) + fmt.Fprintln(os.Stdout, entry.Time.String()) time.Sleep(time.Duration(delay) * time.Millisecond) } @@ -48,24 +52,3 @@ func Timelapse(r render.Renderer, minutes, delay int, loop bool) error { return nil } - -func getSnapshots(dur time.Duration) (snapshots []snapshot, err error) { - - now, err := getNow() - if err != nil { - return nil, err - } - - sheets := int((int64(dur) / int64(5*time.Minute))) + 1 - for i := 0; i < sheets; i++ { - t := now.Add(time.Duration(-5*(sheets-i)) * time.Minute) - entry := amesh.GetEntry(t) - img, err := entry.Image(true, true) - if err != nil { - return nil, err - } - snapshots = append(snapshots, snapshot{img, entry.Time}) - fmt.Print(".") - } - return snapshots, nil -} diff --git a/lib/amesh/entries.go b/lib/amesh/entries.go new file mode 100644 index 0000000..6a88236 --- /dev/null +++ b/lib/amesh/entries.go @@ -0,0 +1,58 @@ +package amesh + +import ( + "image" + "image/color/palette" + "image/draw" + "image/gif" + "net/http" + "time" +) + +// Entries ... +type Entries []*Entry + +// GetEntries ... +func GetEntries(start, end time.Time) (entries Entries) { + t := truncateTime(start) + entries = append(entries, GetEntry(t)) + for t := t.Add(unit); t.Before(end); t = t.Add(unit) { + entries = append(entries, GetEntry(t)) + } + return +} + +// ToImages ... +func (entries Entries) GetImages(progress func(int), client ...*http.Client) ([]*image.RGBA, error) { + images := make([]*image.RGBA, len(entries), len(entries)) + for i, entry := range entries { + img, err := entry.GetImage(true, true, client...) + if err != nil { + return images, err + } + images[i] = img + if progress != nil { + progress(i) + } + } + return images, nil +} + +// ToGif delay == msec +func (entries Entries) ToGif(delay int, loop bool) (*gif.GIF, error) { + dest := &gif.GIF{LoopCount: 5} + if loop { + dest.LoopCount = 0 + } + images, err := entries.GetImages(nil) + if err != nil { + return nil, err + } + for _, img := range images { + paletted := image.NewPaletted(img.Bounds(), palette.Plan9) + draw.Draw(paletted, paletted.Bounds(), img, img.Bounds().Min, draw.Over) + dest.Image = append(dest.Image, paletted) + dest.Delay = append(dest.Delay, delay/10) // Because it's 100ths + } + return dest, err +} diff --git a/lib/amesh/entry.go b/lib/amesh/entry.go index b224e10..8796fe9 100644 --- a/lib/amesh/entry.go +++ b/lib/amesh/entry.go @@ -26,6 +26,9 @@ const ( defaultLocation = "Asia/Tokyo" ) +// オンメモリ固定画像キャッシュ +var cache = map[string]image.Image{} + // Entry ... type Entry struct { URL string `json:"url"` @@ -35,12 +38,13 @@ type Entry struct { Time time.Time `json:"time"` IsRainingFunc func(image.Image) (bool, error) + Image *image.RGBA } // GetEntry ... -func GetEntry(t time.Time) Entry { +func GetEntry(t time.Time) *Entry { t = truncateTime(t) - return Entry{ + return &Entry{ URL: AmeshURL, Map: getMap(), Mesh: getMesh(t), @@ -66,7 +70,7 @@ func getMesh(t time.Time) string { } // Image fetches image data from URL and merge them if needed. -func (entry Entry) Image(geo, mask bool, client ...*http.Client) (*image.RGBA, error) { +func (entry *Entry) GetImage(geo, mask bool, client ...*http.Client) (*image.RGBA, error) { // If client not specified, use default HTTP client. // This is because, for example, Google App Engine requires HTTP client with context. @@ -99,10 +103,14 @@ func (entry Entry) Image(geo, mask bool, client ...*http.Client) (*image.RGBA, e draw.Draw(merged, masklayer.Bounds(), masklayer, image.Point{0, 0}, 0) } + entry.Image = merged return merged, nil } -func (entry Entry) getImageFor(imgurl string, client *http.Client) (image.Image, error) { +func (entry *Entry) getImageFor(imgurl string, client *http.Client) (image.Image, error) { + if cached, ok := cache[imgurl]; ok { + return cached, nil + } res, err := client.Get(imgurl) if err != nil { return nil, err @@ -112,9 +120,11 @@ func (entry Entry) getImageFor(imgurl string, client *http.Client) (image.Image, return nil, fmt.Errorf(res.Status) } img, _, err := image.Decode(res.Body) + cache[imgurl] = img return img, err } +/* FIXME: You ain't gonna need it // IsRaining ... func (entry *Entry) IsRaining(cliet *http.Client) (bool, error) { @@ -151,3 +161,4 @@ func (entry *Entry) IsRaining(cliet *http.Client) (bool, error) { return false, nil } +*/