package dailygeo import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "time" ) type Address struct{ Province, City, Region, Adcode string } type Resolver interface { Resolve(context.Context, float64, float64) (Address, error) } type AMap struct { Key, BaseURL string Client *http.Client } func (a *AMap) Resolve(ctx context.Context, lng, lat float64) (Address, error) { if a.Key == "" { return Address{}, fmt.Errorf("AMAP_API_KEY is not configured") } base := a.BaseURL if base == "" { base = "https://restapi.amap.com" } u, e := url.Parse(base + "/v3/geocode/regeo") if e != nil { return Address{}, fmt.Errorf("invalid geocoder URL") } x, y := WGS84ToGCJ02(lng, lat) q := u.Query() q.Set("key", a.Key) q.Set("location", fmt.Sprintf("%.6f,%.6f", x, y)) q.Set("extensions", "base") q.Set("radius", "1000") u.RawQuery = q.Encode() req, e := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if e != nil { return Address{}, fmt.Errorf("invalid geocoder request") } client := a.Client if client == nil { client = &http.Client{Timeout: 10 * time.Second} } resp, e := client.Do(req) if e != nil { return Address{}, fmt.Errorf("geocoder request failed") } defer resp.Body.Close() if resp.StatusCode != 200 { return Address{}, fmt.Errorf("geocoder HTTP %d", resp.StatusCode) } var body struct { Status string Infocode string Regeocode struct { AddressComponent struct{ Province, City, District, Adcode json.RawMessage } `json:"addressComponent"` } } if e = json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&body); e != nil { return Address{}, fmt.Errorf("invalid geocoder response") } if body.Status != "1" { return Address{}, fmt.Errorf("geocoder rejected: %s", body.Infocode) } p, c := textValue(body.Regeocode.AddressComponent.Province), textValue(body.Regeocode.AddressComponent.City) if c == "" { switch p { case "北京市", "上海市", "天津市", "重庆市", "香港特别行政区", "澳门特别行政区": c = p } } // Province-administered county-level cities (for example 潜江市) are // returned in district, with city=[], by AMap. if c == "" && p != "" { district := textValue(body.Regeocode.AddressComponent.District) if strings.HasSuffix(district, "市") { c = district } } if p == "" || c == "" { return Address{}, fmt.Errorf("geocoder returned incomplete province/city") } return Address{p, c, Region(p), textValue(body.Regeocode.AddressComponent.Adcode)}, nil } func textValue(raw json.RawMessage) string { var s string if json.Unmarshal(raw, &s) == nil { return s } var ss []string if json.Unmarshal(raw, &ss) == nil { return strings.Join(ss, "") } return "" } func Region(province string) string { groups := []struct { name string provinces []string }{ {"华东", []string{"上海", "江苏", "浙江", "安徽", "福建", "江西", "山东", "台湾"}}, {"华北", []string{"北京", "天津", "河北", "山西", "内蒙古"}}, {"华中", []string{"河南", "湖北", "湖南"}}, {"华南", []string{"广东", "广西", "海南", "香港", "澳门"}}, {"东北", []string{"辽宁", "吉林", "黑龙江"}}, {"西南", []string{"重庆", "四川", "贵州", "云南", "西藏"}}, {"西北", []string{"陕西", "甘肃", "青海", "宁夏", "新疆"}}, } for _, g := range groups { for _, p := range g.provinces { if strings.HasPrefix(province, p) { return g.name } } } return "" }