added area.go

This commit is contained in:
flopp 2016-02-28 16:03:11 +01:00
parent 900e03ad0a
commit 37c675dbd0
6 changed files with 175 additions and 51 deletions

View File

@ -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)

View File

@ -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)

97
staticmaps/area.go Normal file
View File

@ -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()
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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()
}