From 37c675dbd099a637c578afe2e2ef33d2369af60b Mon Sep 17 00:00:00 2001 From: flopp Date: Sun, 28 Feb 2016 16:03:11 +0100 Subject: [PATCH] added area.go --- README.md | 17 +++- cmd/create-static-map/create-static-map.go | 10 +++ staticmaps/area.go | 97 ++++++++++++++++++++++ staticmaps/map_creator.go | 45 +++++++--- staticmaps/marker.go | 22 ++--- staticmaps/path.go | 35 ++------ 6 files changed, 175 insertions(+), 51 deletions(-) create mode 100644 staticmaps/area.go diff --git a/README.md b/README.md index 8096c93..a957077 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ See [GoDoc](https://godoc.org/github.com/flopp/go-staticmaps/staticmaps) for a c -z, --zoom=ZOOMLEVEL Zoom factor -m, --marker=MARKER Add a marker to the static map -p, --path=PATH Add a path to the static map + -a, --area=AREA Add an area to the static map Help Options: -h, --help Show this help message @@ -93,16 +94,26 @@ The `--marker` option defines one or more map markers of the same style. Use mul - `size:SIZE` - where `SIZE` is one of `mid`, `small`, `tiny` (default: `mid`) ### Paths -The `--path` option defines a path or an area on the map. Use multiple `--path` options to add multiple paths/areas to the map. +The `--path` option defines a path on the map. Use multiple `--path` options to add multiple paths to the map. --path PATH_STYLES|LATLNG|LATLNG|... `PATH_STYLES` consists of a set of style descriptors separated by the pipe character `|`: - `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`) -- `fillcolor:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: none); if a fill color is specified, the path is drawn as a closed, filled area - `weight:WEIGHT` - where `WEIGHT` is the line width in pixels (defaut: `5`) +### Areas +The `--area` option defines a closed area on the map. Use multiple `--area` options to add multiple areas to the map. + + --area AREA_STYLES|LATLNG|LATLNG|... + +`AREA_STYLES` consists of a set of style descriptors separated by the pipe character `|`: + +- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`) +- `weight:WEIGHT` - where `WEIGHT` is the line width in pixels (defaut: `5`) +- `fill:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: none) + ## Examples @@ -183,7 +194,7 @@ $ create-static-map --width 600 --height 400 -o map3.png -m "red|52.514536,13.35 --center="-26.284973,134.303764" \ --output "australia.png" \ --marker "color:blue|-35.305200,149.121574" \ - --path "color:0x00FF00|fillcolor:0x00FF007F|weight:2|-25.994024,129.013847|-25.994024,137.989677|-16.537670,138.011649|\ + --area "color:0x00FF00|fill:0x00FF007F|weight:2|-25.994024,129.013847|-25.994024,137.989677|-16.537670,138.011649|\ -14.834820,135.385917|-12.293236,137.033866|-11.174554,130.398124|-12.925791,130.167411|-14.866678,129.002860" ![Static map of Australia](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/australia.png) diff --git a/cmd/create-static-map/create-static-map.go b/cmd/create-static-map/create-static-map.go index 5408a22..a0ca303 100644 --- a/cmd/create-static-map/create-static-map.go +++ b/cmd/create-static-map/create-static-map.go @@ -54,6 +54,7 @@ func main() { Zoom int `short:"z" long:"zoom" description:"Zoom factor" value-name:"ZOOMLEVEL"` Markers []string `short:"m" long:"marker" description:"Add a marker to the static map" value-name:"MARKER"` Paths []string `short:"p" long:"path" description:"Add a path to the static map" value-name:"PATH"` + Areas []string `short:"a" long:"area" description:"Add an area to the static map" value-name:"AREA"` } parser := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash) @@ -109,6 +110,15 @@ func main() { } } + for _, areaString := range opts.Areas { + area, err := staticmaps.ParseAreaString(areaString) + if err != nil { + log.Fatal(err) + } else { + m.AddArea(area) + } + } + img, err := m.Create() if err != nil { log.Fatal(err) diff --git a/staticmaps/area.go b/staticmaps/area.go new file mode 100644 index 0000000..034f4eb --- /dev/null +++ b/staticmaps/area.go @@ -0,0 +1,97 @@ +// Copyright 2016 Florian Pigorsch. All rights reserved. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package staticmaps + +import ( + "image/color" + "strconv" + "strings" + + "github.com/flopp/go-coordsparser" + "github.com/fogleman/gg" + "github.com/golang/geo/s2" +) + +// Area represents a area or area on the map +type Area struct { + MapObject + Positions []s2.LatLng + Color color.Color + Fill color.Color + Weight float64 +} + +// ParseAreaString parses a string and returns an area +func ParseAreaString(s string) (*Area, error) { + area := new(Area) + area.Color = color.RGBA{0xff, 0, 0, 0xff} + area.Fill = color.Transparent + area.Weight = 5.0 + + for _, ss := range strings.Split(s, "|") { + if strings.HasPrefix(ss, "color:") { + var err error + area.Color, err = ParseColorString(strings.TrimPrefix(ss, "color:")) + if err != nil { + return nil, err + } + } else if strings.HasPrefix(ss, "fill:") { + var err error + area.Fill, err = ParseColorString(strings.TrimPrefix(ss, "fill:")) + if err != nil { + return nil, err + } + } else if strings.HasPrefix(ss, "weight:") { + var err error + area.Weight, err = strconv.ParseFloat(strings.TrimPrefix(ss, "weight:"), 64) + if err != nil { + return nil, err + } + } else { + lat, lng, err := coordsparser.Parse(ss) + if err != nil { + return nil, err + } + area.Positions = append(area.Positions, s2.LatLngFromDegrees(lat, lng)) + } + + } + return area, nil +} + +func (p *Area) extraMarginPixels() float64 { + return 0.5 * p.Weight +} + +func (p *Area) bounds() s2.Rect { + r := s2.EmptyRect() + for _, ll := range p.Positions { + r = r.AddPoint(ll) + } + return r +} + +func (p *Area) draw(gc *gg.Context, trans *transformer) { + if len(p.Positions) <= 1 { + return + } + + gc.ClearPath() + + gc.SetLineWidth(p.Weight) + gc.SetLineCap(gg.LineCapRound) + gc.SetLineJoin(gg.LineJoinRound) + + for _, ll := range p.Positions { + gc.LineTo(trans.ll2p(ll)) + } + + gc.ClosePath() + gc.SetColor(p.Fill) + gc.FillPreserve() + gc.SetColor(p.Color) + gc.Stroke() +} diff --git a/staticmaps/map_creator.go b/staticmaps/map_creator.go index 6fc223d..0e0a756 100644 --- a/staticmaps/map_creator.go +++ b/staticmaps/map_creator.go @@ -29,6 +29,7 @@ type MapCreator struct { markers []*Marker paths []*Path + areas []*Area tileProvider *TileProvider } @@ -87,6 +88,16 @@ func (m *MapCreator) ClearPaths() { m.paths = nil } +// AddArea adds an area to the MapCreator +func (m *MapCreator) AddArea(area *Area) { + m.areas = append(m.areas, area) +} + +// ClearAreas removes all areas from the MapCreator +func (m *MapCreator) ClearAreas() { + m.areas = nil +} + func (m *MapCreator) determineBounds() s2.Rect { r := s2.EmptyRect() for _, marker := range m.markers { @@ -95,6 +106,9 @@ func (m *MapCreator) determineBounds() s2.Rect { for _, path := range m.paths { r = r.Union(path.bounds()) } + for _, area := range m.areas { + r = r.Union(area.bounds()) + } return r } @@ -112,6 +126,12 @@ func (m *MapCreator) determineExtraMarginPixels() float64 { p = pp } } + for _, area := range m.areas { + pp := area.extraMarginPixels() + if pp > p { + p = pp + } + } return p } @@ -231,29 +251,34 @@ func (m *MapCreator) Create() (image.Image, error) { } } - dc := gg.NewContextForRGBA(img) + gc := gg.NewContextForRGBA(img) + + for _, area := range m.areas { + area.draw(gc, trans) + } for _, path := range m.paths { - path.draw(dc, trans) + path.draw(gc, trans) } for _, marker := range m.markers { - marker.draw(dc, trans) + marker.draw(gc, trans) } + croppedImg := image.NewRGBA(image.Rect(0, 0, int(m.width), int(m.height))) draw.Draw(croppedImg, image.Rect(0, 0, int(m.width), int(m.height)), img, image.Point{trans.pCenterX - int(m.width)/2, trans.pCenterY - int(m.height)/2}, draw.Src) // draw attribution - _, textHeight := dc.MeasureString(m.tileProvider.Attribution) + _, textHeight := gc.MeasureString(m.tileProvider.Attribution) boxHeight := textHeight + 4.0 - dc = gg.NewContextForRGBA(croppedImg) - dc.SetRGBA(0.0, 0.0, 0.0, 0.5) - dc.DrawRectangle(0.0, float64(m.height)-boxHeight, float64(m.width), boxHeight) - dc.Fill() - dc.SetRGBA(1.0, 1.0, 1.0, 0.75) - dc.DrawString(m.tileProvider.Attribution, 4.0, float64(m.height)-4.0) + gc = gg.NewContextForRGBA(croppedImg) + gc.SetRGBA(0.0, 0.0, 0.0, 0.5) + gc.DrawRectangle(0.0, float64(m.height)-boxHeight, float64(m.width), boxHeight) + gc.Fill() + gc.SetRGBA(1.0, 1.0, 1.0, 0.75) + gc.DrawString(m.tileProvider.Attribution, 4.0, float64(m.height)-4.0) return croppedImg, nil } diff --git a/staticmaps/marker.go b/staticmaps/marker.go index 1a767b4..4debb10 100644 --- a/staticmaps/marker.go +++ b/staticmaps/marker.go @@ -88,19 +88,19 @@ func (m *Marker) bounds() s2.Rect { return r } -func (m *Marker) draw(dc *gg.Context, trans *transformer) { - dc.ClearPath() +func (m *Marker) draw(gc *gg.Context, trans *transformer) { + gc.ClearPath() - dc.SetLineJoin(gg.LineJoinRound) - dc.SetLineWidth(1.0) + gc.SetLineJoin(gg.LineJoinRound) + gc.SetLineWidth(1.0) radius := 0.5 * m.Size x, y := trans.ll2p(m.Position) - dc.DrawArc(x, y-m.Size, radius, (90.0+60.0)*math.Pi/180.0, (360.0+90.0-60.0)*math.Pi/180.0) - dc.LineTo(x, y) - dc.ClosePath() - dc.SetColor(m.Color) - dc.FillPreserve() - dc.SetRGB(0, 0, 0) - dc.Stroke() + gc.DrawArc(x, y-m.Size, radius, (90.0+60.0)*math.Pi/180.0, (360.0+90.0-60.0)*math.Pi/180.0) + gc.LineTo(x, y) + gc.ClosePath() + gc.SetColor(m.Color) + gc.FillPreserve() + gc.SetRGB(0, 0, 0) + gc.Stroke() } diff --git a/staticmaps/path.go b/staticmaps/path.go index e02590c..97b4c32 100644 --- a/staticmaps/path.go +++ b/staticmaps/path.go @@ -20,8 +20,6 @@ type Path struct { MapObject Positions []s2.LatLng Color color.Color - IsFilled bool - FillColor color.Color Weight float64 } @@ -29,8 +27,6 @@ type Path struct { func ParsePathString(s string) (*Path, error) { path := new(Path) path.Color = color.RGBA{0xff, 0, 0, 0xff} - path.IsFilled = false - path.FillColor = color.Transparent path.Weight = 5.0 for _, ss := range strings.Split(s, "|") { @@ -40,13 +36,6 @@ func ParsePathString(s string) (*Path, error) { if err != nil { return nil, err } - } else if strings.HasPrefix(ss, "fillcolor:") { - path.IsFilled = true - var err error - path.FillColor, err = ParseColorString(strings.TrimPrefix(ss, "fillcolor:")) - if err != nil { - return nil, err - } } else if strings.HasPrefix(ss, "weight:") { var err error path.Weight, err = strconv.ParseFloat(strings.TrimPrefix(ss, "weight:"), 64) @@ -77,29 +66,21 @@ func (p *Path) bounds() s2.Rect { return r } -func (p *Path) draw(dc *gg.Context, trans *transformer) { +func (p *Path) draw(gc *gg.Context, trans *transformer) { if len(p.Positions) <= 1 { return } - dc.ClearPath() + gc.ClearPath() - dc.SetLineWidth(p.Weight) - dc.SetLineCap(gg.LineCapRound) - dc.SetLineJoin(gg.LineJoinRound) + gc.SetLineWidth(p.Weight) + gc.SetLineCap(gg.LineCapRound) + gc.SetLineJoin(gg.LineJoinRound) for _, ll := range p.Positions { - dc.LineTo(trans.ll2p(ll)) + gc.LineTo(trans.ll2p(ll)) } - if p.IsFilled { - dc.ClosePath() - dc.SetColor(p.FillColor) - dc.FillPreserve() - dc.SetColor(p.Color) - dc.Stroke() - } else { - dc.SetColor(p.Color) - dc.Stroke() - } + gc.SetColor(p.Color) + gc.Stroke() }