124 lines
3.4 KiB
Go
124 lines
3.4 KiB
Go
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 ""
|
|
}
|