Files
baseapi/platformapi/autonavi/autonavi.go
邹宗楠 56588c5738 1
2023-09-06 13:50:39 +08:00

681 lines
21 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package autonavi
import (
"bytes"
"crypto/md5"
"fmt"
"net/http"
"sort"
"strings"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
)
const (
signKey = "sig"
prodURL = "https://restapi.amap.com"
prodURLv3 = "/v3"
prodURLFullv3 = prodURL + prodURLv3
batchAPIStr = "batch"
)
const (
StatusCodeFailed = "0"
StatusCodeSuccess = "1"
)
const (
InfoCode_OK = "10000"
InfoCode_ACCESS_TOO_FREQUENT = "10004"
InfoCode_QPS_HAS_EXCEEDED_THE_LIMIT = "10014"
InfoCode_SERVER_IS_BUSY = "10016"
InfoCode_RESOURCE_UNAVAILABLE = "10017"
InfoCode_CQPS_HAS_EXCEEDED_THE_LIMIT = "10019"
InfoCode_CKQPS_HAS_EXCEEDED_THE_LIMIT = "10020"
InfoCode_CIQPS_HAS_EXCEEDED_THE_LIMIT = "10021"
InfoCode_CIKQPS_HAS_EXCEEDED_THE_LIMIT = "10022"
InfoCode_KQPS_HAS_EXCEEDED_THE_LIMIT = "10023"
)
const (
CoordSysAutonavi = "autonavi"
CoordSysGPS = "gps"
CoordSysMapbar = "mapbar"
CoordSysBaidu = "baidu"
)
const (
FakeDistrictPadding = 9000000
MaxConvertCount = 40
)
var (
exceedLimitCodes = map[string]int{
InfoCode_ACCESS_TOO_FREQUENT: 1,
InfoCode_QPS_HAS_EXCEEDED_THE_LIMIT: 1,
InfoCode_CQPS_HAS_EXCEEDED_THE_LIMIT: 1,
InfoCode_CKQPS_HAS_EXCEEDED_THE_LIMIT: 1,
InfoCode_CIQPS_HAS_EXCEEDED_THE_LIMIT: 1,
InfoCode_CIKQPS_HAS_EXCEEDED_THE_LIMIT: 1,
InfoCode_KQPS_HAS_EXCEEDED_THE_LIMIT: 1,
}
canRetryCodes = map[string]int{
InfoCode_SERVER_IS_BUSY: 1,
}
)
const (
DistrictLevelCountry = 0
DistrictLevelProvince = 1
DistrictLevelCity = 2
DistrictLevelDistrict = 3
DistrictLevelStreet = 4
)
const (
RoadLevelAll = 0 // 显示所有道路
RoadLevelMain = 1 // 过滤非主干道路,仅输出主干道路数据
HomeOrCorpDef = 0 // 不对召回的排序策略进行干扰。
HomeOrCorpPreferHome = 1 // 综合大数据分析将居家相关的 POI 内容优先返回,即优化返回结果中 pois 字段的poi顺序。
HomeOrCorpPreferCorp = 2 // 综合大数据分析将公司相关的 POI 内容优先返回,即优化返回结果中 pois 字段的poi顺序。
)
var (
levelStr2IntMap = map[string]int{
"country": DistrictLevelCountry,
"province": DistrictLevelProvince,
"city": DistrictLevelCity,
"district": DistrictLevelDistrict,
"street": DistrictLevelStreet,
}
)
type District struct {
Adcode string `json:"adcode"` // 国家行政编码
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
CityCode string `json:"citycode"` // 电话区号
Level int `json:"level"`
Name string `json:"name"`
Districts []*District `json:"districts"`
}
type Coordinate struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
}
type ResponseResult map[string]interface{}
type API struct {
platformapi.APICookie
client *http.Client
config *platformapi.APIConfig
key string
}
func (a *API) SetKey(key string) {
a.key = key
}
type BuildingOrNeighborInfo struct {
Name string `json:"name"`
Type string `json:"type"`
}
type BusinessAreaInfo struct {
ID string `json:"id"`
Location string `json:"location"`
Name string `json:"name"`
}
type StreetNumberInfo struct {
Direction string `json:"direction"`
Distance string `json:"distance"`
Location string `json:"location"`
Number string `json:"number"`
Street string `json:"street"`
}
type AoiInfo struct {
Adcode string `json:"adcode"`
Area string `json:"area"`
Distance string `json:"distance"`
ID string `json:"id"`
Location string `json:"location"`
Name string `json:"name"`
Type string `json:"type"`
}
type PoiInfo struct {
Address string `json:"address"`
Businessarea string `json:"businessarea"`
Direction string `json:"direction"`
Distance string `json:"distance"`
ID string `json:"id"`
Location string `json:"location"`
Name string `json:"name"`
Poiweight string `json:"poiweight"`
Tel string `json:"tel"`
Type string `json:"type"`
}
type RoadinterInfo struct {
Direction string `json:"direction"`
Distance string `json:"distance"`
FirstID string `json:"first_id"`
FirstName string `json:"first_name"`
Location string `json:"location"`
SecondID string `json:"second_id"`
SecondName string `json:"second_name"`
}
type RoadInfo struct {
Direction string `json:"direction"`
Distance string `json:"distance"`
ID string `json:"id"`
Location string `json:"location"`
Name string `json:"name"`
}
type AddressComponentInfo struct {
Adcode string `json:"adcode"`
Building *BuildingOrNeighborInfo `json:"building"`
// BusinessAreas []*BusinessAreaInfo `json:"businessAreas"`
City string `json:"city"`
Citycode string `json:"citycode"`
Country string `json:"country"`
District string `json:"district"`
Neighborhood *BuildingOrNeighborInfo `json:"neighborhood"`
Province string `json:"province"`
StreetNumber *StreetNumberInfo `json:"streetNumber"`
Towncode string `json:"towncode"`
Township string `json:"township"`
}
type RegeoCodeInfo struct {
AddressComponent *AddressComponentInfo `json:"addressComponent"`
Aois []*AoiInfo `json:"aois"`
FormattedAddress string `json:"formatted_address"`
Pois []*PoiInfo `json:"pois"`
Roadinters []*RoadinterInfo `json:"roadinters"`
Roads []*RoadInfo `json:"roads"`
}
type tBatchAPIParams struct {
APIStr string
APIParams map[string]interface{}
}
type tBatchAPIResponse struct {
Result ResponseResult
Err error
}
func New(key string, config ...*platformapi.APIConfig) *API {
curConfig := platformapi.DefAPIConfig
if len(config) > 0 {
curConfig = *config[0]
}
return &API{
key: key,
client: &http.Client{Timeout: curConfig.ClientTimeout},
config: &curConfig,
}
}
func (a *API) signParams(mapData map[string]interface{}) string {
keys := make([]string, 0)
for k := range mapData {
if k != signKey {
keys = append(keys, k)
}
}
sort.Strings(keys)
finalStr := ""
for _, k := range keys {
finalStr += k + "=" + fmt.Sprint(mapData[k])
}
return fmt.Sprintf("%X", md5.Sum([]byte(finalStr)))
}
func coordinate2String(lng, lat float64) (str string) {
return fmt.Sprintf("%.6f,%.6f", lng, lat)
}
func (a *API) AccessAPI(apiStr string, params map[string]interface{}) (retVal ResponseResult, err error) {
params2 := utils.MergeMaps(utils.Params2Map("key", a.key, "output", "json"), params)
params2[signKey] = a.signParams(params2)
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodGet, utils.GenerateGetURL(prodURLFullv3, apiStr, params2), nil)
return request
},
a.config,
func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
if jsonResult1 == nil {
return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
}
status := jsonResult1["status"].(string)
if status == StatusCodeSuccess {
retVal = jsonResult1
return platformapi.ErrLevelSuccess, nil
}
infoCode := jsonResult1["infocode"].(string)
newErr := utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
if _, ok := exceedLimitCodes[infoCode]; ok {
return platformapi.ErrLevelExceedLimit, newErr
} else if _, ok := canRetryCodes[infoCode]; ok {
return platformapi.ErrLevelRecoverableErr, newErr
} else {
return platformapi.ErrLevelCodeIsNotOK, newErr
}
})
return retVal, err
}
func (a *API) AccessAPI3(apiStr string, params map[string]interface{}) (retVal ResponseResult, err error) {
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodGet, utils.GenerateGetURL(BaseUrl, apiStr, params), nil)
return request
},
a.config,
func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
if jsonResult1 == nil {
return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
}
status := jsonResult1["status"].(string)
if status == StatusCodeSuccess {
retVal = jsonResult1
return platformapi.ErrLevelSuccess, nil
}
infoCode := jsonResult1["infocode"].(string)
newErr := utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
if _, ok := exceedLimitCodes[infoCode]; ok {
return platformapi.ErrLevelExceedLimit, newErr
} else if _, ok := canRetryCodes[infoCode]; ok {
return platformapi.ErrLevelRecoverableErr, newErr
} else {
return platformapi.ErrLevelCodeIsNotOK, newErr
}
})
return retVal, err
}
func (a *API) BatchAccessAPI(apiList []*tBatchAPIParams) (retVal []*tBatchAPIResponse, err error) {
if len(apiList) == 0 {
return nil, nil
}
var ops []map[string]interface{}
for _, v := range apiList {
params2 := utils.MergeMaps(utils.Params2Map("key", a.key, "output", "json"), v.APIParams)
params2[signKey] = a.signParams(params2)
op := make(map[string]interface{})
op["url"] = utils.GenerateGetURL(prodURLv3, v.APIStr, params2)
ops = append(ops, op)
}
params2 := map[string]interface{}{
"ops": ops,
}
err = platformapi.AccessPlatformAPIWithRetry(a.client,
func() *http.Request {
request, _ := http.NewRequest(http.MethodGet, utils.GenerateGetURL(prodURLFullv3, batchAPIStr, utils.Params2Map("key", a.key)),
bytes.NewReader(utils.MustMarshal(params2)))
request.Header.Set("charset", "UTF-8")
request.Header.Set("Content-Type", "application/json")
return request
},
a.config,
func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
if jsonResult1 == nil {
return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
}
if resultList, ok := jsonResult1[platformapi.KeyData].([]interface{}); ok {
for _, v := range resultList {
jsonResult1 := v.(map[string]interface{})
status := int(utils.ForceInterface2Int64(jsonResult1["status"]))
if status == http.StatusOK {
jsonResult1 := v.(map[string]interface{})["body"].(map[string]interface{})
var retVal2 map[string]interface{}
var err2 error
status := jsonResult1["status"].(string)
if status == StatusCodeSuccess {
retVal2 = jsonResult1
} else {
infoCode := jsonResult1["infocode"].(string)
err2 = utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
}
retVal = append(retVal, &tBatchAPIResponse{
Result: retVal2,
Err: err2,
})
}
}
errLevel = platformapi.ErrLevelSuccess
} else {
infoCode := jsonResult1["infocode"].(string)
err = utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
if _, ok := exceedLimitCodes[infoCode]; ok {
errLevel = platformapi.ErrLevelExceedLimit
} else if _, ok := canRetryCodes[infoCode]; ok {
errLevel = platformapi.ErrLevelRecoverableErr
} else {
errLevel = platformapi.ErrLevelCodeIsNotOK
}
}
return errLevel, err
})
return retVal, err
}
// 为了方便调用者编码,如果失败,也会返回未转换的原始值
func (a *API) CoordinateConvert(lng, lat float64, coordsys string) (retLng, retLat float64, err error) {
// outCoords, err := a.BatchCoordinateConvert([]*Coordinate{
// &Coordinate{
// Lng: lng,
// Lat: lat,
// },
// }, coordsys)
// if err == nil {
// retLng, retLat = outCoords[0].Lng, outCoords[0].Lat
// }
if coordsys == "" || coordsys == CoordSysAutonavi {
return lng, lat, nil
}
if lng == 0.0 && lat == 0.0 {
return 0.0, 0.0, nil
}
params := map[string]interface{}{
"locations": coordinate2String(lng, lat),
"coordsys": coordsys,
}
result, err := a.AccessAPI("assistant/coordinate/convert", params)
if err == nil {
coordinate := result["locations"].(string)
index := strings.Index(coordinate, ",")
return utils.Str2Float64(coordinate[:index]), utils.Str2Float64(coordinate[index+1:]), nil
}
return lng, lat, err
}
func (a *API) BatchCoordinateConvert(coords []*Coordinate, coordsys string) (outCoords []*Coordinate, err error) {
if coordsys == "" || coordsys == CoordSysAutonavi {
return coords, nil
}
var coordsStrList []string
for _, v := range coords {
coordsStrList = append(coordsStrList, coordinate2String(v.Lng, v.Lat))
}
params := map[string]interface{}{
"locations": strings.Join(coordsStrList, "|"),
"coordsys": coordsys,
}
result, err := a.AccessAPI("assistant/coordinate/convert", params)
if err == nil {
coordinate := result["locations"].(string)
retCoordsStrList := strings.Split(coordinate, ";")
for _, v := range retCoordsStrList {
pair := strings.Split(v, ",")
outCoords = append(outCoords, &Coordinate{
Lng: utils.Str2Float64(pair[0]),
Lat: utils.Str2Float64(pair[1]),
})
}
}
return outCoords, err
}
func (a *API) GetCoordinateFromAddress(address string, cityInfo string) (lng, lat float64, districtCode int) {
params := map[string]interface{}{
"address": address,
}
if cityInfo != "" {
params["city"] = cityInfo
}
result, err := a.AccessAPI("geocode/geo", params)
if err == nil {
if geocodes, ok := result["geocodes"].([]interface{}); ok && len(geocodes) > 0 {
geocode := geocodes[0].(map[string]interface{})
locationList := strings.Split(utils.Interface2String(geocode["location"]), ",")
if len(locationList) > 1 {
return utils.Str2Float64WithDefault(locationList[0], 0), utils.Str2Float64WithDefault(locationList[1], 0), int(utils.Str2Int64(utils.Interface2String(geocode["adcode"])))
}
}
}
return lng, lat, 0
}
func (a *API) GetCoordinateFromAddressAll(address string, cityInfo string) (getCoordinateFromAddressByPageAllResult *GetCoordinateFromAddressByPageAllResult, err error) {
getCoordinateFromAddressByPageAllResult = &GetCoordinateFromAddressByPageAllResult{}
params := map[string]interface{}{
"address": address,
}
if cityInfo != "" {
params["city"] = cityInfo
}
result, err := a.AccessAPI("geocode/geo", params)
if err == nil {
if geocodes, ok := result["geocodes"].([]interface{}); ok && len(geocodes) > 0 {
geocode := geocodes[0].(map[string]interface{})
locationList := strings.Split(utils.Interface2String(geocode["location"]), ",")
if len(locationList) > 1 {
getCoordinateFromAddressByPageAllResult.Lng = utils.Str2Float64WithDefault(locationList[0], 0)
getCoordinateFromAddressByPageAllResult.Lat = utils.Str2Float64WithDefault(locationList[1], 0)
}
if str, ok := geocode["city"].(string); ok {
getCoordinateFromAddressByPageAllResult.CityName = str
}
if str, ok := geocode["district"].(string); ok {
getCoordinateFromAddressByPageAllResult.AdName = str
}
}
}
return getCoordinateFromAddressByPageAllResult, err
}
func (a *API) GeoCodeRegeo(coords []*Coordinate, radius int, isExt bool, poiTypes []string, roadLevel, homeOrCorp int) (coordInfoList []*RegeoCodeInfo, err error) {
coordStrList := make([]string, len(coords))
for k, v := range coords {
coordStrList[k] = coordinate2String(v.Lng, v.Lat)
}
params := map[string]interface{}{
"location": strings.Join(coordStrList, "|"),
"batch": len(coordStrList) > 1,
}
if radius > 0 {
params["radius"] = radius
}
if isExt {
params["extensions"] = "all"
params["roadlevel"] = roadLevel
if len(poiTypes) > 0 {
params["poitype"] = strings.Join(poiTypes, "|")
}
if homeOrCorp > 0 {
params["homeorcorp"] = homeOrCorp
}
}
result, err := a.AccessAPI("geocode/regeo", params)
if err == nil {
if len(coordStrList) > 1 {
err = utils.Map2StructByJson(result["regeocodes"], &coordInfoList, true)
} else {
coordInfoList = make([]*RegeoCodeInfo, 1)
err = utils.Map2StructByJson(result["regeocode"], &coordInfoList[0], true)
}
}
return coordInfoList, err
}
func (a *API) GeoCodeRegeoSingle(lng, lat float64, radius int, isExt bool, poiTypes []string, roadLevel, homeOrCorp int) (coordInfo *RegeoCodeInfo, err error) {
coordInfoList, err := a.GeoCodeRegeo([]*Coordinate{
&Coordinate{
Lng: lng,
Lat: lat,
},
}, radius, isExt, poiTypes, roadLevel, homeOrCorp)
if err == nil {
coordInfo = coordInfoList[0]
}
return coordInfo, err
}
// 这里的District指的是实际的District有些市是没有区的比如东莞这种情况下返回的区码是一个假的区域即市的编码加上9000000
func (a *API) GetCoordinateDistrictCode(lng, lat float64) (districtCode int) {
result, err := a.GetCoordinateAreaInfo(lng, lat)
if err == nil {
addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{})
if strAdcode, ok := addressComponent["adcode"].(string); ok {
districtCode = int(utils.Str2Int64WithDefault(strAdcode, 0))
if _, ok := addressComponent["district"].(string); !ok {
districtCode += FakeDistrictPadding
}
}
}
return districtCode
}
func (a *API) GetCoordinateAreaInfo(lng, lat float64) (areaInfo map[string]interface{}, err error) {
params := map[string]interface{}{
"location": coordinate2String(lng, lat),
}
result, err := a.AccessAPI("geocode/regeo", params)
return result, err
}
func (a *API) GetCoordinateTownInfo(lng, lat float64) (townName, townCode string) {
result, err := a.GetCoordinateAreaInfo(lng, lat)
// baseapi.SugarLogger.Debug(utils.Format4Output(result, false))
if err == nil {
addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{})
townName = utils.Interface2String(addressComponent["township"])
townCode = utils.Interface2String(addressComponent["towncode"])
}
return townName, townCode
}
func (a *API) GetCoordinateCityInfo(lng, lat float64) (cityName, cityCode string) {
result, err := a.GetCoordinateAreaInfo(lng, lat)
// baseapi.SugarLogger.Debug(utils.Format4Output(result, false))
if err == nil {
addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{})
if addressComponent["city"] != nil {
if cn, ok := addressComponent["city"].(string); ok {
cityName = cn
} else {
if s, ok := addressComponent["province"].(string); ok {
cityName = s
}
}
}
if s, ok := addressComponent["citycode"].(string); ok {
cityCode = s
}
// cityCode = utils.Interface2String(addressComponent["citycode"])
}
return cityName, cityCode
}
// 这里的District指的是地点不是实际上的区具体级别看level
// 这个函数返回的可能不是同级别的地点
func (a *API) GetDistricts(subDistrict int, keywords string) (districtList []*District, err error) {
params := map[string]interface{}{
"subdistrict": subDistrict,
}
if keywords != "" {
params["keywords"] = keywords
}
result, err := a.AccessAPI("config/district", params)
// baseapi.SugarLogger.Debug(utils.Format4Output(result, false))
if err == nil {
return a.getDistrictsFromInterface(result["districts"]), nil
}
return nil, err
}
func (a *API) getDistrictsFromInterface(districts interface{}) (districtList []*District) {
if districts != nil {
districts2 := districts.([]interface{})
districtList = make([]*District, len(districts2))
for k, v := range districts2 {
v2 := v.(map[string]interface{})
districtList[k] = &District{
Adcode: utils.Interface2String(v2["adcode"]),
Name: utils.Interface2String(v2["name"]),
Level: GetDistrictLevel(utils.Interface2String(v2["level"])),
}
coordStrNodes := strings.Split(utils.Interface2String(v2["center"]), ",")
if len(coordStrNodes) == 2 {
if coordStrNodes[0] != "" && coordStrNodes[1] != "" {
districtList[k].Lng = utils.Str2Float64(coordStrNodes[0])
districtList[k].Lat = utils.Str2Float64(coordStrNodes[1])
}
}
if cityCodeStr, ok := v2["citycode"].(string); ok {
districtList[k].CityCode = cityCodeStr
}
districtList[k].Districts = a.getDistrictsFromInterface(v2["districts"])
}
}
return districtList
}
func GetDistrictLevel(levelName string) (level int) {
return levelStr2IntMap[levelName]
}
// 两点之间的步行距离,单位为米
func (a *API) WalkingDistance(lng1, lat1, lng2, lat2 float64) (distance float64) {
params := map[string]interface{}{
"origin": coordinate2String(lng1, lat1),
"destination": coordinate2String(lng2, lat2),
}
result, err := a.AccessAPI("direction/walking", params)
if err == nil {
if paths, _ := result["route"].(map[string]interface{})["paths"].([]interface{}); len(paths) > 0 {
distance = utils.Interface2Float64WithDefault(paths[0].(map[string]interface{})["distance"], 0)
}
}
return distance
}
func (a *API) BatchWalkingDistance(srcLng, srcLat float64, destCoords []*Coordinate) (distanceList []float64, err error) {
var reqList []*tBatchAPIParams
for _, v := range destCoords {
reqList = append(reqList, &tBatchAPIParams{
APIStr: "direction/walking",
APIParams: map[string]interface{}{
"origin": coordinate2String(srcLng, srcLat),
"destination": coordinate2String(v.Lng, v.Lat),
},
})
}
resultList, err := a.BatchAccessAPI(reqList)
if err == nil {
for _, v := range resultList {
distance := float64(9527123)
if v.Err == nil {
if paths, _ := v.Result["route"].(map[string]interface{})["paths"].([]interface{}); len(paths) > 0 {
distance = utils.Interface2Float64WithDefault(paths[0].(map[string]interface{})["distance"], 0)
}
}
distanceList = append(distanceList, distance)
}
}
return distanceList, err
}