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 { client *http.Client config *platformapi.APIConfig key string } 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]) } // baseapi.SugarLogger.Debugf("sign str:%v", finalStr) 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) 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) 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 } // 这里的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 }