mirror of
https://github.com/imjasonh/staticmaps
synced 2026-07-18 14:56:16 +00:00
Add StaticMap method to generate static map image
This commit is contained in:
parent
e0af619690
commit
449c03fc61
4 changed files with 289 additions and 22 deletions
|
|
@ -33,7 +33,7 @@ const (
|
||||||
|
|
||||||
func (c Client) Directions(orig, dest Location, opts *DirectionsOpts) (*DirectionsResponse, error) {
|
func (c Client) Directions(orig, dest Location, opts *DirectionsOpts) (*DirectionsResponse, error) {
|
||||||
var d DirectionsResponse
|
var d DirectionsResponse
|
||||||
if err := c.do(baseURL+directions(orig, dest, opts), &d); err != nil {
|
if err := c.doDecode(baseURL+directions(orig, dest, opts), &d); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &d, nil
|
return &d, nil
|
||||||
|
|
@ -48,15 +48,11 @@ func directions(orig, dest Location, opts *DirectionsOpts) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DirectionsOpts struct {
|
type DirectionsOpts struct {
|
||||||
Mode string
|
Waypoints []Location
|
||||||
Waypoints Route
|
Alternatives bool
|
||||||
Alternatives bool
|
Avoid []string
|
||||||
Avoid []string
|
Mode, Language, Units, Region string
|
||||||
Language string
|
DepartureTime, ArrivalTime time.Time
|
||||||
Units string
|
|
||||||
Region string
|
|
||||||
DepartureTime time.Time
|
|
||||||
ArrivalTime time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (do *DirectionsOpts) update(p url.Values) {
|
func (do *DirectionsOpts) update(p url.Values) {
|
||||||
|
|
@ -67,7 +63,7 @@ func (do *DirectionsOpts) update(p url.Values) {
|
||||||
p.Set("mode", do.Mode)
|
p.Set("mode", do.Mode)
|
||||||
}
|
}
|
||||||
if do.Waypoints != nil {
|
if do.Waypoints != nil {
|
||||||
p.Set("waypoints", do.Waypoints.String())
|
p.Set("waypoints", encodeLocations(do.Waypoints))
|
||||||
}
|
}
|
||||||
if do.Alternatives {
|
if do.Alternatives {
|
||||||
p.Set("alternatives", "true")
|
p.Set("alternatives", "true")
|
||||||
|
|
|
||||||
18
maps.go
18
maps.go
|
|
@ -18,20 +18,22 @@ func NewClient(key string) Client {
|
||||||
return Client{Key: key, Transport: http.DefaultTransport}
|
return Client{Key: key, Transport: http.DefaultTransport}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) do(url string, r interface{}) error {
|
func (c Client) do(url string) (*http.Response, error) {
|
||||||
cl := &http.Client{Transport: c.Transport}
|
cl := &http.Client{Transport: c.Transport}
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Key != "" {
|
if c.Key != "" {
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
q.Set("key", c.Key)
|
q.Set("key", c.Key)
|
||||||
req.URL.RawQuery = q.Encode()
|
req.URL.RawQuery = q.Encode()
|
||||||
}
|
}
|
||||||
|
return cl.Get(url)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := cl.Get(url)
|
func (c Client) doDecode(url string, r interface{}) error {
|
||||||
|
resp, err := c.do(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -76,11 +78,9 @@ func (a Address) Location() string {
|
||||||
return string(a)
|
return string(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Route []Location
|
func encodeLocations(ls []Location) string {
|
||||||
|
s := make([]string, len(ls))
|
||||||
func (r Route) String() string {
|
for i, l := range ls {
|
||||||
s := make([]string, len(r))
|
|
||||||
for i, l := range r {
|
|
||||||
s[i] = l.Location()
|
s[i] = l.Location()
|
||||||
}
|
}
|
||||||
return strings.Join(s, "|")
|
return strings.Join(s, "|")
|
||||||
|
|
|
||||||
60
maps_test.go
60
maps_test.go
|
|
@ -9,13 +9,69 @@ func TestDirections(t *testing.T) {
|
||||||
c := NewClient("")
|
c := NewClient("")
|
||||||
orig, dest := Address("111 8th Ave, NYC"), Address("170 E 92nd St, NYC")
|
orig, dest := Address("111 8th Ave, NYC"), Address("170 E 92nd St, NYC")
|
||||||
opts := &DirectionsOpts{
|
opts := &DirectionsOpts{
|
||||||
Mode: ModeWalking,
|
Mode: ModeTransit,
|
||||||
DepartureTime: time.Now(),
|
DepartureTime: time.Now(),
|
||||||
|
Alternatives: true,
|
||||||
}
|
}
|
||||||
t.Logf("%s", baseURL+directions(orig, dest, opts))
|
t.Logf("%s", baseURL+directions(orig, dest, opts))
|
||||||
r, err := c.Directions(orig, dest, nil)
|
r, err := c.Directions(orig, dest, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
t.Logf("%v", r)
|
t.Logf("%v", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStaticMap(t *testing.T) {
|
||||||
|
c := NewClient("")
|
||||||
|
s := Size{512, 512}
|
||||||
|
opts := &StaticMapOpts{
|
||||||
|
Markers: []Markers{
|
||||||
|
{
|
||||||
|
Size: "small",
|
||||||
|
Color: "blue",
|
||||||
|
Locations: []Location{
|
||||||
|
LatLng{1, 1},
|
||||||
|
LatLng{2, 2},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Size: "mid",
|
||||||
|
Color: "red",
|
||||||
|
Locations: []Location{
|
||||||
|
LatLng{3, 3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Paths: []Path{
|
||||||
|
{
|
||||||
|
Weight: 10,
|
||||||
|
Color: "green",
|
||||||
|
Locations: []Location{
|
||||||
|
LatLng{4, 4},
|
||||||
|
LatLng{5, 5},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Color: "0x00000000",
|
||||||
|
FillColor: "0x00000033",
|
||||||
|
Locations: []Location{
|
||||||
|
LatLng{6, 6}, LatLng{7, 7}, LatLng{7, 3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Visible: []Location{
|
||||||
|
LatLng{15, 15},
|
||||||
|
},
|
||||||
|
Styles: []Style{
|
||||||
|
{
|
||||||
|
Feature: "water",
|
||||||
|
Element: "geometry.fill",
|
||||||
|
Rules: []StyleRule{
|
||||||
|
{Hue: "0xFF0000"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t.Logf("%s", baseURL+staticmap(s, opts))
|
||||||
|
if _, err := c.StaticMap(s, opts); err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
215
static.go
Normal file
215
static.go
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
package maps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MapFormatPNG = "png"
|
||||||
|
MapFormatPNG32 = "png32"
|
||||||
|
MapFormatGIF = "gif"
|
||||||
|
MapFormatJPG = "jpg"
|
||||||
|
MapFormatJPGBaseline = "jpg-baseline"
|
||||||
|
|
||||||
|
MapTypeRoadmap = "roadmap"
|
||||||
|
MapTypeSatellite = "satellite"
|
||||||
|
MapTypeTerrain = "terrain"
|
||||||
|
MapTypeHybrid = "hybrid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c Client) StaticMap(s Size, opts *StaticMapOpts) (io.ReadCloser, error) {
|
||||||
|
resp, err := c.do(baseURL + staticmap(s, opts))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, HTTPError{resp}
|
||||||
|
}
|
||||||
|
return resp.Body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func staticmap(s Size, opts *StaticMapOpts) string {
|
||||||
|
p := url.Values{}
|
||||||
|
p.Set("size", s.String())
|
||||||
|
opts.update(p)
|
||||||
|
return "staticmap?" + p.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Size struct {
|
||||||
|
H, W int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Size) String() string {
|
||||||
|
return fmt.Sprintf("%dx%d", s.H, s.W)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StaticMapOpts struct {
|
||||||
|
Center *Location
|
||||||
|
Zoom, Scale int
|
||||||
|
Format, MapType, Language, Region string
|
||||||
|
Markers []Markers
|
||||||
|
Paths []Path
|
||||||
|
Visible []Location
|
||||||
|
Styles []Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type Markers struct {
|
||||||
|
Size string // tiny, mid, small
|
||||||
|
Color string // hex color, or certain color names
|
||||||
|
Label string
|
||||||
|
IconURL string
|
||||||
|
HideShadow bool
|
||||||
|
Locations []Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Markers) encode() string {
|
||||||
|
s := []string{}
|
||||||
|
if m.Size != "" {
|
||||||
|
s = append(s, "size:"+m.Size)
|
||||||
|
}
|
||||||
|
if m.Color != "" {
|
||||||
|
s = append(s, "color:"+m.Color)
|
||||||
|
}
|
||||||
|
if m.Label != "" {
|
||||||
|
s = append(s, "label:"+m.Label)
|
||||||
|
}
|
||||||
|
if m.IconURL != "" {
|
||||||
|
s = append(s, "icon:"+m.IconURL)
|
||||||
|
}
|
||||||
|
if m.HideShadow {
|
||||||
|
s = append(s, "shadow:false")
|
||||||
|
}
|
||||||
|
style := strings.Join(s, "|")
|
||||||
|
if style != "" {
|
||||||
|
style += "|"
|
||||||
|
}
|
||||||
|
return style + encodeLocations(m.Locations)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
Weight int
|
||||||
|
Color string // with opacity, or certain color names
|
||||||
|
FillColor string // with opacity, or certain color names
|
||||||
|
Geodesic bool
|
||||||
|
Polyline string
|
||||||
|
Locations []Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Path) encode() string {
|
||||||
|
s := []string{}
|
||||||
|
if p.Weight != 0 {
|
||||||
|
s = append(s, fmt.Sprintf("weight:%d", p.Weight))
|
||||||
|
}
|
||||||
|
if p.Color != "" {
|
||||||
|
s = append(s, "color:"+p.Color)
|
||||||
|
}
|
||||||
|
if p.FillColor != "" {
|
||||||
|
s = append(s, "fillcolor:"+p.FillColor)
|
||||||
|
}
|
||||||
|
if p.Geodesic {
|
||||||
|
s = append(s, "geodesic:true")
|
||||||
|
}
|
||||||
|
style := strings.Join(s, "|")
|
||||||
|
if style != "" {
|
||||||
|
style += "|"
|
||||||
|
}
|
||||||
|
if p.Polyline != "" {
|
||||||
|
return style + p.Polyline
|
||||||
|
}
|
||||||
|
return style + encodeLocations(p.Locations)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Style struct {
|
||||||
|
Feature string // TODO enum with sub-options
|
||||||
|
Element string // TODO enum
|
||||||
|
Rules []StyleRule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Style) encode() string {
|
||||||
|
s := []string{}
|
||||||
|
if t.Feature != "" {
|
||||||
|
s = append(s, "feature:"+t.Feature)
|
||||||
|
}
|
||||||
|
if t.Element != "" {
|
||||||
|
s = append(s, "element:"+t.Element)
|
||||||
|
}
|
||||||
|
for _, r := range t.Rules {
|
||||||
|
s = append(s, r.encode())
|
||||||
|
}
|
||||||
|
return strings.Join(s, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
type StyleRule struct {
|
||||||
|
Hue string // rgb color
|
||||||
|
Lightness float64 // -100 to 100
|
||||||
|
Saturation float64 // -100 to 100
|
||||||
|
Gamma *float64 // .01 to 10, default 1
|
||||||
|
InverseLightness *bool
|
||||||
|
Visibility string // on, off, simplified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r StyleRule) encode() string {
|
||||||
|
s := []string{}
|
||||||
|
if r.Hue != "" {
|
||||||
|
s = append(s, "hue:"+r.Hue)
|
||||||
|
}
|
||||||
|
if r.Lightness != 0 {
|
||||||
|
s = append(s, fmt.Sprintf("lightness:%f", r.Lightness))
|
||||||
|
}
|
||||||
|
if r.Saturation != 0 {
|
||||||
|
s = append(s, fmt.Sprintf("saturation:%f", r.Saturation))
|
||||||
|
}
|
||||||
|
if r.Gamma != nil {
|
||||||
|
s = append(s, fmt.Sprintf("gamma:%f", *r.Gamma))
|
||||||
|
}
|
||||||
|
if r.InverseLightness != nil && *r.InverseLightness == false {
|
||||||
|
s = append(s, "inverse_lightness:false")
|
||||||
|
}
|
||||||
|
if r.Visibility != "" {
|
||||||
|
s = append(s, "visibility:"+r.Visibility)
|
||||||
|
}
|
||||||
|
return strings.Join(s, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (so *StaticMapOpts) update(p url.Values) {
|
||||||
|
if so == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if so.Center != nil {
|
||||||
|
p.Set("center", (*so.Center).Location())
|
||||||
|
}
|
||||||
|
if so.Zoom != 0 {
|
||||||
|
p.Set("zoom", fmt.Sprintf("%d", so.Zoom))
|
||||||
|
}
|
||||||
|
if so.Scale != 0 {
|
||||||
|
p.Set("scale", fmt.Sprintf("%d", so.Scale))
|
||||||
|
}
|
||||||
|
if so.Format != "" {
|
||||||
|
p.Set("format", so.Format)
|
||||||
|
}
|
||||||
|
if so.MapType != "" {
|
||||||
|
p.Set("maptype", so.MapType)
|
||||||
|
}
|
||||||
|
if so.Language != "" {
|
||||||
|
p.Set("language", so.Language)
|
||||||
|
}
|
||||||
|
if so.Region != "" {
|
||||||
|
p.Set("region", so.Region)
|
||||||
|
}
|
||||||
|
for _, m := range so.Markers {
|
||||||
|
p.Add("markers", m.encode())
|
||||||
|
}
|
||||||
|
for _, path := range so.Paths {
|
||||||
|
p.Add("path", path.encode())
|
||||||
|
}
|
||||||
|
if so.Visible != nil {
|
||||||
|
p.Set("visible", encodeLocations(so.Visible))
|
||||||
|
}
|
||||||
|
for _, s := range so.Styles {
|
||||||
|
p.Add("style", s.encode())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue