You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

313 lines
8.6 KiB

package googlemap
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func newSys(options Options) (sys *GoogleMap, err error) {
sys = &GoogleMap{}
sys.options = options
return
}
type GoogleMap struct {
options Options
}
// 逆地理编码:根据经纬度坐标获取结构化地址
// 参数:
// - location: 坐标字符串,格式为"纬度,经度"(例如"39.90909,116.434307")
// 返回:
// - result: 包含结构化地址与坐标的最小化结果
// - err: 请求或解析过程中产生的错误
// 异常:
// - 当请求失败或响应体解析失败时返回错误
func (this *GoogleMap) ReverseGeocode(location string) (result *ReverseGeocodeResult, err error) {
var (
reqURL string
resp *http.Response
body []byte
)
reqURL = fmt.Sprintf(
"https://maps.googleapis.com/maps/api/geocode/json?latlng=%s&key=%s&language=zh-CN",
url.QueryEscape(location),
this.options.ApiKey,
)
if resp, err = http.Get(reqURL); err != nil {
return
}
defer resp.Body.Close()
if body, err = ioutil.ReadAll(resp.Body); err != nil {
return
}
// 定义与Google返回兼容的结构体(仅取必要字段)
var geores struct {
Status string `json:"status"`
Results []struct {
FormattedAddress string `json:"formatted_address"`
Geometry struct {
Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"location"`
} `json:"geometry"`
} `json:"results"`
}
if err = json.Unmarshal(body, &geores); err != nil {
return
}
if len(geores.Results) == 0 {
err = fmt.Errorf("no reverse geocode result")
return
}
result = &ReverseGeocodeResult{
FormattedAddress: geores.Results[0].FormattedAddress,
Lat: geores.Results[0].Geometry.Location.Lat,
Lng: geores.Results[0].Geometry.Location.Lng,
}
return
}
// 地理编码:根据地址获取坐标
// 参数:
// - address: 地址字符串,例如"北京市朝阳区三里屯"
// 返回:
// - result: 包含结构化地址与坐标的最小化结果
// - err: 请求或解析过程中产生的错误
func (this *GoogleMap) Geocode(address string) (result *GeocodeResult, err error) {
var (
reqURL string
resp *http.Response
body []byte
)
reqURL = fmt.Sprintf(
"https://maps.googleapis.com/maps/api/geocode/json?address=%s&key=%s",
url.QueryEscape(address),
this.options.ApiKey,
)
if resp, err = http.Get(reqURL); err != nil {
return
}
defer resp.Body.Close()
if body, err = ioutil.ReadAll(resp.Body); err != nil {
return
}
// 定义与Google返回兼容的结构体(仅取必要字段)
var geores struct {
Status string `json:"status"`
Results []struct {
FormattedAddress string `json:"formatted_address"`
Geometry struct {
Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"location"`
} `json:"geometry"`
} `json:"results"`
}
if err = json.Unmarshal(body, &geores); err != nil {
return
}
if len(geores.Results) == 0 {
err = fmt.Errorf("no geocode result")
return
}
result = &GeocodeResult{
FormattedAddress: geores.Results[0].FormattedAddress,
Lat: geores.Results[0].Geometry.Location.Lat,
Lng: geores.Results[0].Geometry.Location.Lng,
}
return
}
// 范围搜索:根据坐标与半径进行附近搜索
// 参数:
// - location: 坐标字符串,格式为"纬度,经度"(例如"39.90909,116.434307")
// - radius: 搜索半径,单位米(例如"1000")
// - keyword: 搜索关键词(例如"餐厅")
// 返回:
// - result: 附近搜索结果列表(最小化结构)
// - err: 请求或解析过程中产生的错误
func (this *GoogleMap) NearbySearch(location string, radius string, keyword string) (result *NearbySearchResult, err error) {
var (
reqURL string
resp *http.Response
body []byte
)
reqURL = fmt.Sprintf(
"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=%s&radius=%s&keyword=%s&key=%s&language=zh-CN",
url.QueryEscape(location),
url.QueryEscape(radius),
url.QueryEscape(keyword),
this.options.ApiKey,
)
if resp, err = http.Get(reqURL); err != nil {
return
}
defer resp.Body.Close()
if body, err = ioutil.ReadAll(resp.Body); err != nil {
return
}
// 定义与Google返回兼容的结构体(仅取必要字段)
var places struct {
Status string `json:"status"`
Results []struct {
Name string `json:"name"`
Vicinity string `json:"vicinity"`
PlaceID string `json:"place_id"`
Rating float64 `json:"rating"`
Geometry struct {
Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"location"`
} `json:"geometry"`
Photos []struct {
PhotoReference string `json:"photo_reference"`
} `json:"photos"`
} `json:"results"`
}
if err = json.Unmarshal(body, &places); err != nil {
return
}
result = &NearbySearchResult{
Items: make([]NearbyPlace, 0, len(places.Results)),
}
for _, r := range places.Results {
item := NearbyPlace{
Name: r.Name,
Address: r.Vicinity,
Lat: r.Geometry.Location.Lat,
Lng: r.Geometry.Location.Lng,
Rating: r.Rating,
PlaceID: r.PlaceID,
}
if len(r.Photos) > 0 {
item.PhotoRef = r.Photos[0].PhotoReference
}
result.Items = append(result.Items, item)
}
return
}
func (this *GoogleMap) Navigation(origin string, destination string, mode string) (root *RootResponse, err error) {
// 1. 调用 Directions API
directionsURL := fmt.Sprintf(
"https://maps.googleapis.com/maps/api/directions/json?origin=%s&destination=%s&mode=%s&key=%s",
url.QueryEscape(origin),
url.QueryEscape(destination),
url.QueryEscape(mode),
this.options.ApiKey,
)
resp, err := http.Get(directionsURL)
if err != nil {
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var directions DirectionsResponse
if err = json.Unmarshal(body, &directions); err != nil {
return
}
if len(directions.Routes) == 0 {
err = fmt.Errorf("no route found")
return
}
polyline := directions.Routes[0].OverviewPolyline.Points
// 2. 拼接 Static Maps URL
staticMapURL := fmt.Sprintf(
"https://maps.googleapis.com/maps/api/staticmap?size=600x400&path=enc:%s&markers=color:green|label:S|%s&markers=color:red|label:E|%s&key=%s",
url.QueryEscape(polyline),
url.QueryEscape(origin),
url.QueryEscape(destination),
this.options.ApiKey,
)
// mapURL := fmt.Sprintf(
// "https://www.google.com/maps/dir/?api=1&origin=%s&destination=%s&travelmode=%s",
// url.QueryEscape(origin),
// url.QueryEscape(destination),
// url.QueryEscape(mode),
// )
// App 深链:Google Maps 直接导航(仅支持已安装 Google Maps App 的设备)
navURL := fmt.Sprintf(
"google.navigation:q=%s&mode=%s",
url.QueryEscape(destination),
url.QueryEscape(mode),
)
// 3. 转换数据到RootResponse结构体
route := directions.Routes[0]
leg := route.Legs[0] // 取第一段路径
startaddr := leg.StartAddress
endaddr := leg.EndAddress
// 创建DirectionPath
path := &DirectionPath{
Distance: leg.Distance.Text,
Duration: leg.Duration.Text,
Image: staticMapURL,
MapUrl: navURL,
Cost: struct {
Duration string `json:"duration"`
}{
Duration: fmt.Sprintf("%d", leg.Duration.Value),
},
}
// 转换Steps
for _, step := range leg.Steps {
pathStep := struct {
Instruction string `json:"instruction"`
Orientation string `json:"orientation"`
RoadName string `json:"road_name"`
StepDistance interface{} `json:"step_distance"`
Navi struct {
Action string `json:"action"`
AssistantAction string `json:"assistant_action"`
WalkType string `json:"walk_type"`
} `json:"navi"`
Polyline string `json:"polyline"`
}{
Instruction: step.HTMLInstructions,
Orientation: origin, // Google API没有直接提供方向信息
RoadName: destination, // 可以从HTMLInstructions中提取
StepDistance: step.Distance.Value,
Navi: struct {
Action string `json:"action"`
AssistantAction string `json:"assistant_action"`
WalkType string `json:"walk_type"`
}{
Action: step.Maneuver,
AssistantAction: "",
WalkType: "0",
},
Polyline: step.Polyline.Points,
}
path.Steps = append(path.Steps, pathStep)
}
// 创建RootResponse
root = &RootResponse{
Count: "1",
Origin: origin,
Destination: destination,
OriginAddr: startaddr, // Google Directions API不直接提供地址
DestinationAddr: endaddr, // Google Directions API不直接提供地址
Route: &struct {
Paths []*DirectionPath `json:"paths"`
}{
Paths: []*DirectionPath{path},
},
}
return
}