diff --git a/platformapi/baidunavi/baidunavi.go b/platformapi/baidunavi/baidunavi.go new file mode 100644 index 00000000..20fde070 --- /dev/null +++ b/platformapi/baidunavi/baidunavi.go @@ -0,0 +1,168 @@ +package baidunavi + +import ( + "crypto/md5" + "fmt" + "net/http" + "net/url" + "sort" + "strings" + + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +const ( + signKey = "sn" + resultKey = "result" + prodURL = "http://api.map.baidu.com" +) + +const ( + StatusCodeSuccess = 0 + StatusCodeInternalErr = 1 // 服务器内部错误 + StatusCodeExceedDailyQuota = 301 // 永久配额超限,限制访问 + StatusCodeExceedQuota = 302 // 天配额超限,限制访问 + StatusCodeExceedDailyConcurrentQuota = 401 // 当前并发量已经超过约定并发配额,限制访问 + StatusCodeExceedConcurrentQuota = 402 // 当前并发量已经超过约定并发配额,并且服务总并发量也已经超过设定的总并发配额,限制访问 +) + +const ( + CoordSysWGS84 = 1 // GPS设备获取的角度坐标,WGS84坐标 + CoordSysGCJ02 = 3 // google地图、soso地图、aliyun地图、mapabc地图和amap地图所用坐标,国测局(GCJ02)坐标 + CoordSysBaiDu = 5 // 百度地图采用的经纬度坐标 +) + +const ( + MaxCoordsConvBatchSize = 100 +) + +var ( + exceedLimitCodes = map[int]int{ + StatusCodeExceedDailyQuota: 1, + StatusCodeExceedQuota: 1, + StatusCodeExceedDailyConcurrentQuota: 1, + StatusCodeExceedConcurrentQuota: 1, + } + + canRetryCodes = map[int]int{ + StatusCodeInternalErr: 1, + } +) + +type Coordinate struct { + Lng float64 `json:"x"` + Lat float64 `json:"y"` +} + +type API struct { + client *http.Client + config *platformapi.APIConfig + ak string + sk string +} + +func New(ak, sk string, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + return &API{ + ak: ak, + sk: sk, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } +} + +func (a *API) signParams(apiStr string, mapData map[string]interface{}) string { + keys := make([]string, 0) + for k := range mapData { + if k != signKey { + keys = append(keys, k) + } + } + sort.Strings(keys) + + strList := []string{} + for _, k := range keys { + strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(mapData[k]))) + } + finalStr := "/" + apiStr + "?" + strings.Join(strList, "&") + a.sk + // baseapi.SugarLogger.Debugf("sign str:%v", finalStr) + finalStr = url.QueryEscape(finalStr) + return fmt.Sprintf("%x", md5.Sum([]byte(finalStr))) +} + +func genGetURL(baseURL, apiStr string, params map[string]interface{}) string { + keys := make([]string, 0) + for k := range params { + if k != signKey { + keys = append(keys, k) + } + } + sort.Strings(keys) + + strList := []string{} + for _, k := range keys { + strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(params[k]))) + } + strList = append(strList, signKey+"="+url.QueryEscape(fmt.Sprint(params[signKey]))) + queryString := "?" + strings.Join(strList, "&") + if apiStr != "" { + return baseURL + "/" + apiStr + queryString + } + return baseURL + queryString +} + +func (a *API) AccessAPI(apiStr string, params map[string]interface{}) (retVal interface{}, err error) { + apiStr += "/" + params2 := utils.MergeMaps(utils.Params2Map("ak", a.ak, "output", "json"), params) + params2[signKey] = a.signParams(apiStr, params2) + + err = platformapi.AccessPlatformAPIWithRetry(a.client, + func() *http.Request { + request, _ := http.NewRequest(http.MethodGet, genGetURL(prodURL, 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 := int(utils.MustInterface2Int64(jsonResult1["status"])) + if status == StatusCodeSuccess { + retVal = jsonResult1[resultKey] + return platformapi.ErrLevelSuccess, nil + } + newErr := utils.NewErrorIntCode(utils.Interface2String(jsonResult1["message"]), status) + if _, ok := exceedLimitCodes[status]; ok { + return platformapi.ErrLevelExceedLimit, newErr + } else if _, ok := canRetryCodes[status]; ok { + return platformapi.ErrLevelRecoverableErr, newErr + } else { + return platformapi.ErrLevelCodeIsNotOK, newErr + } + }) + return retVal, err +} + +func (a *API) BatchCoordinateConvert(coords []*Coordinate, fromCoordSys, toCoordSys int) (outCoords []*Coordinate, err error) { + if fromCoordSys == toCoordSys { + return coords, nil + } + var coordsStrList []string + for _, v := range coords { + coordsStrList = append(coordsStrList, fmt.Sprintf("%.6f,%.6f", v.Lng, v.Lat)) + } + params := map[string]interface{}{ + "coords": strings.Join(coordsStrList, ";"), + "from": fromCoordSys, + "to": toCoordSys, + } + result, err := a.AccessAPI("geoconv/v1", params) + if err == nil { + err = utils.Map2StructByJson(result, &outCoords, false) + } + return outCoords, err +} diff --git a/platformapi/baidunavi/baidunavi_test.go b/platformapi/baidunavi/baidunavi_test.go new file mode 100644 index 00000000..3b11f2f4 --- /dev/null +++ b/platformapi/baidunavi/baidunavi_test.go @@ -0,0 +1,41 @@ +package baidunavi + +import ( + "testing" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/utils" + + "go.uber.org/zap" +) + +var ( + api *API + sugarLogger *zap.SugaredLogger +) + +func init() { + logger, _ := zap.NewDevelopment() + sugarLogger = logger.Sugar() + baseapi.Init(sugarLogger) + + api = New("eL94zToVOdGDTkNQxV8dnEQ1ZRcB2UKb", "ZG0OOpOsOVURUwAkkmoHQFKRCbzn0zGb") +} + +func TestBatchCoordinateConvert(t *testing.T) { + result, err := api.BatchCoordinateConvert([]*Coordinate{ + &Coordinate{ + Lng: 104.057367, + Lat: 30.694686, + }, + &Coordinate{ + Lng: 104.057367, + Lat: 30.694686, + }, + }, CoordSysGCJ02, CoordSysBaiDu) + if err != nil { + t.Fatalf("TestCoordinateConvert failed with error:%v", err) + } else { + t.Log(utils.Format4Output(result, false)) + } +}