Browse Source

Added precision information, reduced memory usage to ~256MB, fixed some edge cases

master
Patrick Gaskin 1 year ago
parent
commit
69291d6c1d
Signed by: geek1011 GPG Key ID: A2FD79F68A2AB707
2 changed files with 37 additions and 16 deletions
  1. +20
    -14
      graph/graph.go
  2. +17
    -2
      main.go

+ 20
- 14
graph/graph.go View File

@ -49,26 +49,26 @@ func (p PriceChange) CSV() string {
// Parse parses a camelcamelcamel price graph (only supports single Amazon
// price line though).
func Parse(img image.Image, maxPrice, minPrice float64, startDate, endDate time.Time) ([]PriceChange, error) {
func Parse(img image.Image, maxPrice, minPrice float64, startDate, endDate time.Time) (pc []PriceChange, pricePrecision, datePrecision float64, err error) {
graphBounds := getGraphBounds(img, IsGridColor)
if graphBounds == nil {
return nil, errors.New("could not extract graph boundaries")
return nil, -1, -1, errors.New("could not extract graph boundaries")
}
priceExtrapolator := getPriceExtrapolator(img, *graphBounds, maxPrice, minPrice, IsMaxLineColor, IsMinLineColor)
priceExtrapolator, pricePrecision := getPriceExtrapolator(img, *graphBounds, maxPrice, minPrice, IsMaxLineColor, IsMinLineColor)
if priceExtrapolator == nil {
return nil, errors.New("could not parse price axis for extrapolations")
return nil, -1, -1, errors.New("could not parse price axis for extrapolations")
}
dateExtrapolator := getDateExtrapolator(img, *graphBounds, startDate, endDate)
dateExtrapolator, datePrecision := getDateExtrapolator(img, *graphBounds, startDate, endDate)
if dateExtrapolator == nil {
return nil, errors.New("could not parse date axis for extrapolations")
return nil, -1, -1, errors.New("could not parse date axis for extrapolations")
}
var changes []PriceChange
var curPrice float64
var curStock bool
err := traceLine(img, *graphBounds, IsLineColor, func(x, y int, isDotted bool) {
err = traceLine(img, *graphBounds, IsLineColor, func(x, y int, isDotted bool) {
inStock, date, price := !isDotted, dateExtrapolator(x), priceExtrapolator(y)
if curPrice == price && curStock == inStock {
return
@ -77,7 +77,7 @@ func Parse(img image.Image, maxPrice, minPrice float64, startDate, endDate time.
changes = append(changes, PriceChange{inStock, date, price})
})
changes = append(changes, PriceChange{changes[len(changes)-1].InStock, endDate, changes[len(changes)-1].Price})
return changes, err
return changes, pricePrecision, datePrecision, err
}
// traceLine traces the grid line.
@ -105,6 +105,12 @@ func traceLine(img image.Image, graphBounds image.Rectangle, line colorMatcherFu
curY = y
} else if onCur {
curY = y
} else if line(img.At(x, y-1)) && line(img.At(x+1, y)) {
// handle edge case where bottom sitting right on dash in dashed max line at top
curY = y
} else if line(img.At(x, y-1)) && line(img.At(x+1, y-1)) {
// handle edge case where bottom sitting right on dash in dashed max line at top
curY = y
} else {
return false
}
@ -166,7 +172,7 @@ func getGraphBounds(img image.Image, fn colorMatcherFunc) *image.Rectangle {
// getPriceExtrapolator gets an extrapolator to extract the price from the image
// based on a y value relative to the image (NOT the grid).
func getPriceExtrapolator(img image.Image, graphBounds image.Rectangle, maxPrice, minPrice float64, maxColor, minColor colorMatcherFunc) func(y int) float64 {
func getPriceExtrapolator(img image.Image, graphBounds image.Rectangle, maxPrice, minPrice float64, maxColor, minColor colorMatcherFunc) (fn func(y int) float64, precision float64) {
// dashed line, so threshold is 0.4; the line is on bottom of plotted line, so subtract 1.
var maxY, minY int
for y := graphBounds.Min.Y; y < graphBounds.Max.Y; y++ {
@ -182,19 +188,19 @@ func getPriceExtrapolator(img image.Image, graphBounds image.Rectangle, maxPrice
}
}
if minY < maxY {
return nil
return nil, -1
}
return func(y int) float64 {
return math.Round(extrapolate(maxY, minY, maxPrice, minPrice, y)*100) / 100
}
}, float64(maxPrice-minPrice) / float64(graphBounds.Max.Y-graphBounds.Min.Y)
}
// getDateExtrapolator gets an extrapolator to extract the date from the image
// based on an x value relative to the image (NOT the grid). It allows extrapolating
// up to 100 days before or after the known range.
func getDateExtrapolator(img image.Image, gridBounds image.Rectangle, startDate, endDate time.Time) func(x int) time.Time {
func getDateExtrapolator(img image.Image, graphBounds image.Rectangle, startDate, endDate time.Time) (fn func(x int) time.Time, precision float64) {
startIndex, dates, endIndex := getDaysBetween(startDate, endDate, 100)
return func(x int) time.Time {
return dates[int(math.Round(extrapolate(gridBounds.Min.X+1, gridBounds.Max.X, float64(startIndex), float64(endIndex), x)))]
}
return dates[int(math.Round(extrapolate(graphBounds.Min.X+1, graphBounds.Max.X, float64(startIndex), float64(endIndex), x)))]
}, float64(len(dates)) / float64(graphBounds.Max.X-graphBounds.Min.X)
}

+ 17
- 2
main.go View File

@ -14,6 +14,8 @@ import (
func main() {
locale := pflag.StringP("locale", "l", "us", "Amazon store location (gb, us, ca)")
writeGraph := pflag.StringP("write-graph", "g", "", "Write the PNG graph to a file (overwrites if exists)")
precision := pflag.BoolP("precision", "p", false, "Write precision information")
highQuality := pflag.BoolP("high-quality", "h", false, "Double graph size (warning: slow, drastically increases memory usage)")
csv := pflag.BoolP("csv", "c", false, "CSV output (date,price,outofstock)")
help := pflag.Bool("help", false, "Show this help text")
pflag.Parse()
@ -38,7 +40,12 @@ func main() {
os.Exit(1)
}
cg, err := camelcamelcamel.Graph(asin, *locale, 16000, 8000)
width, height := 10000, 6000
if *highQuality {
width, height = 16000, 8000
}
cg, err := camelcamelcamel.Graph(asin, *locale, width, height)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: Could not get graph: %v\n", err)
os.Exit(1)
@ -58,12 +65,20 @@ func main() {
f.Close()
}
pc, err := graph.Parse(cg, cr.HighestPricing.PriceAmazon.Price.Price(), cr.LowestPricing.PriceAmazon.Price.Price(), *cr.CreatedAt, time.Now())
pc, pp, dp, err := graph.Parse(cg, cr.HighestPricing.PriceAmazon.Price.Price(), cr.LowestPricing.PriceAmazon.Price.Price(), *cr.CreatedAt, time.Now())
if err != nil {
fmt.Fprintf(os.Stderr, "Error: Could not parse graph file: %v\n", err)
os.Exit(1)
}
if *precision {
if *csv {
fmt.Printf("date(+-%.2f days),price(+-$%.4f),outofstock\n", dp, pp)
} else {
fmt.Printf("date +-%.2f days\nprice +-$%.4f\n", dp, pp)
}
}
for _, p := range pc {
if *csv {
fmt.Println(p.CSV())