Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/api/internal/dailygeo/geocoder.go
T

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