Add ImageMarker. Closes #48.

This commit is contained in:
Florian Pigorsch 2021-04-03 17:55:33 +02:00
parent 1292f1734c
commit 0818a9f287
3 changed files with 164 additions and 18 deletions

View File

@ -81,26 +81,23 @@ See [PkgGoDev](https://pkg.go.dev/badge/github.com/flopp/go-staticmaps) for a co
Creates a static map
Application Options:
--width=PIXELS Width of the generated static map image (default: 512)
--height=PIXELS Height of the generated static map image (default: 512)
-o, --output=FILENAME Output file name (default: map.png)
-t, --type=MAPTYPE Select the map type; list possible map types with '--type list'
-c, --center=LATLNG Center coordinates (lat,lng) of the static map
-z, --zoom=ZOOMLEVEL Zoom factor
-b, --bbox=NW_LATLNG|SE_LATLNG
Set the bounding box (NW_LATLNG = north-western point of the
bounding box, SW_LATLNG = southe-western point of the bounding
box)
--background=COLOR Background color (default: transparent)
-u, --useragent=USERAGENT
Overwrite the default HTTP user agent string
-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
-C, --circle=CIRCLE Add a circle to the static map
--width=PIXELS Width of the generated static map image (default: 512)
--height=PIXELS Height of the generated static map image (default: 512)
-o, --output=FILENAME Output file name (default: map.png)
-t, --type=MAPTYPE Select the map type; list possible map types with '--type list'
-c, --center=LATLNG Center coordinates (lat,lng) of the static map
-z, --zoom=ZOOMLEVEL Zoom factor
-b, --bbox=nwLATLNG|seLATLNG Bounding box of the static map
--background=COLOR Background color (default: transparent)
-u, --useragent=USERAGENT Overwrite the default HTTP user agent string
-m, --marker=MARKER Add a marker to the static map
-i, --imagemarker=MARKER Add an image 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
-C, --circle=CIRCLE Add a circle to the static map
Help Options:
-h, --help Show this help message
-h, --help Show this help message
### General
The command line interface tries to resemble [Google's Static Maps API](https://developers.google.com/maps/documentation/static-maps/intro).
@ -122,6 +119,13 @@ The `--marker` option defines one or more map markers of the same style. Use mul
- `label:LABEL` - where `LABEL` is an alpha numeric character, i.e. `A`-`Z`, `a`-`z`, `0`-`9`; (default: no label)
- `labelcolor:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `black` or `white`, depending on the marker color)
Using the `--imagemarker` option, you can use custom images as markers:
--imagemarker image:IMAGEFILE|offsetx:OFFSETX|offsety:OFFSETY|LATLNG|LATLNG|...
`IMAGEFILE` is the file name of a PNG or JPEG file,
`OFFSETX` and `OFFSETY` are the pixel offsets of the reference point from the top-left corner of the image.
### Paths
The `--path` option defines a path on the map. Use multiple `--path` options to add multiple paths to the map.

View File

@ -103,6 +103,19 @@ func handleMarkersOption(ctx *sm.Context, parameters []string) {
}
}
func handleImageMarkersOption(ctx *sm.Context, parameters []string) {
for _, s := range parameters {
markers, err := sm.ParseImageMarkerString(s)
if err != nil {
log.Fatal(err)
} else {
for _, marker := range markers {
ctx.AddObject(marker)
}
}
}
}
func handlePathsOption(ctx *sm.Context, parameters []string) {
for _, s := range parameters {
paths, err := sm.ParsePathString(s)
@ -153,6 +166,7 @@ func main() {
Background string `long:"background" description:"Background color" value-name:"COLOR" default:"transparent"`
UserAgent string `short:"u" long:"useragent" description:"Overwrite the default HTTP user agent string" value-name:"USERAGENT"`
Markers []string `short:"m" long:"marker" description:"Add a marker to the static map" value-name:"MARKER"`
ImageMarkers []string `short:"i" long:"imagemarker" description:"Add an image 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"`
Circles []string `short:"C" long:"circle" description:"Add a circle to the static map" value-name:"CIRCLE"`
@ -200,6 +214,7 @@ func main() {
handleAreasOption(ctx, opts.Areas)
handleMarkersOption(ctx, opts.Markers)
handleImageMarkersOption(ctx, opts.ImageMarkers)
handleCirclesOption(ctx, opts.Circles)
handlePathsOption(ctx, opts.Paths)

127
image_marker.go Normal file
View File

@ -0,0 +1,127 @@
// Copyright 2021 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 sm
import (
"fmt"
"image"
_ "image/jpeg" // to be able to decode jpegs
_ "image/png" // to be able to decode pngs
"log"
"os"
"strconv"
"strings"
"github.com/flopp/go-coordsparser"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
// ImageMarker represents an image marker on the map
type ImageMarker struct {
MapObject
Position s2.LatLng
Img image.Image
OffsetX float64
OffsetY float64
}
// NewImageMarker creates a new ImageMarker
func NewImageMarker(pos s2.LatLng, img image.Image, offsetX, offsetY float64) *ImageMarker {
m := new(ImageMarker)
m.Position = pos
m.Img = img
m.OffsetX = offsetX
m.OffsetY = offsetY
return m
}
// ParseImageMarkerString parses a string and returns an array of image markers
func ParseImageMarkerString(s string) ([]*ImageMarker, error) {
markers := make([]*ImageMarker, 0)
var img image.Image = nil
offsetX := 0.0
offsetY := 0.0
for _, ss := range strings.Split(s, "|") {
if ok, suffix := hasPrefix(ss, "image:"); ok {
file, err := os.Open(suffix)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err = image.Decode(file)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "offsetx:"); ok {
var err error
offsetX, err = strconv.ParseFloat(suffix, 64)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "offsety:"); ok {
var err error
offsetY, err = strconv.ParseFloat(suffix, 64)
if err != nil {
return nil, err
}
} else {
lat, lng, err := coordsparser.Parse(ss)
if err != nil {
return nil, err
}
if img == nil {
return nil, fmt.Errorf("cannot create an ImageMarker without an image: %s", s)
}
m := NewImageMarker(s2.LatLngFromDegrees(lat, lng), img, offsetX, offsetY)
markers = append(markers, m)
}
}
return markers, nil
}
// SetImage sets the marker's image
func (m *ImageMarker) SetImage(img image.Image) {
m.Img = img
}
// SetOffsetX sets the marker's x offset
func (m *ImageMarker) SetOffsetX(offset float64) {
m.OffsetX = offset
}
// SetOffsetY sets the marker's y offset
func (m *ImageMarker) SetOffsetY(offset float64) {
m.OffsetY = offset
}
// ExtraMarginPixels return the marker's left, top, right, bottom pixel extent.
func (m *ImageMarker) ExtraMarginPixels() (float64, float64, float64, float64) {
size := m.Img.Bounds().Size()
return m.OffsetX, m.OffsetY, float64(size.X) - m.OffsetX, float64(size.Y) - m.OffsetY
}
// Bounds returns single point rect containing the marker's geographical position.
func (m *ImageMarker) Bounds() s2.Rect {
r := s2.EmptyRect()
r = r.AddPoint(m.Position)
return r
}
// Draw draws the object in the given graphical context.
func (m *ImageMarker) Draw(gc *gg.Context, trans *Transformer) {
if !CanDisplay(m.Position) {
log.Printf("ImageMarker coordinates not displayable: %f/%f", m.Position.Lat.Degrees(), m.Position.Lng.Degrees())
return
}
x, y := trans.LatLngToXY(m.Position)
gc.DrawImage(m.Img, int(x-m.OffsetX), int(y-m.OffsetY))
}