隨著外賣業務的發展,外賣配送范圍功能成為了外賣點餐系統中一個非常重要的功能點。為了滿足用戶的需求,很多外賣平臺都會提供這樣一個功能。那么如何利用Go語言開發這個配送范圍功能呢?本文將詳細介紹這個過程,并提供具體的代碼示例,以便讀者更好地了解和掌握這個功能的實現方式。
- 前置條件
在開始開發之前,我們需要先了解一下這個功能的需求和實現方式。具體而言:
需要給出一個多邊形區域,即外賣配送的服務范圍;當用戶在下單頁面輸入地址時,需要通過用戶所在的定位,判斷是否在服務范圍之內,從而決定是否接單。
為了實現這個功能,我們需要使用一些工具和技術:
首先,我們需要使用一個地圖API服務,來獲取我們需要的服務范圍數據和用戶所在位置的地理信息。其次,我們需要使用多邊形算法,即點在多邊形內算法,來判斷定位點是否在服務范圍內。最后,我們需要將這些工具封裝成一個代碼庫,以便在點餐系統中使用。
- 設計思路
在實現這個功能之前,我們需要先定義一些基本的數據結構和接口:
多邊形區域:一個數組,存儲了多個點的地理信息;點:一個結構體,包含經緯度信息;客戶端請求:包含用戶地址信息。
然后,我們可以按照以下的設計思路來實現這個功能:
使用一個地圖API服務,獲取多邊形區域的地理信息,并將這些信息存儲在一個數組中;解析客戶端請求,獲取客戶端所在位置的地理信息;使用多邊形算法,判斷客戶端位置是否在服務范圍內,并給出相應的響應結果。
在Go語言中,我們可以使用go-mapbox庫來訪問地圖API服務。同時,我們也可以使用Go語言中內置的math庫,來實現多邊形算法。具體代碼實現如下:
package main import ( "fmt" "math" "github.com/ustroetz/go-mapbox" ) type Point struct { Lat float64 Lng float64 } type Polygon []Point func (p Point) ToCoordinates() *mapbox.Coordinates { return &mapbox.Coordinates{ Longitude: p.Lng, Latitude: p.Lat, } } func ContainsPointInPolygon(point Point, polygon Polygon) bool { intersectCount := 0 polygonLength := len(polygon) if polygonLength < 3 { return false } endPoint := Point{Lat: 9999.0, Lng: point.Lng} for i := 0; i < len(polygon); i++ { startPoint := polygon[i] nextPointIndex := (i + 1) % len(polygon) nextPoint := polygon[nextPointIndex] if startPoint.Lng == nextPoint.Lng && endPoint.Lng == startPoint.Lng && (point.Lng == startPoint.Lng && (point.Lat > startPoint.Lat) == (point.Lat < endPoint.Lat)) { return true } if point.Lng > math.Min(startPoint.Lng, nextPoint.Lng) && point.Lng <= math.Max(startPoint.Lng, nextPoint.Lng) { deltaLat := nextPoint.Lat - startPoint.Lat if deltaLat == 0 { continue } intersectLat := startPoint.Lat + (point.Lng-startPoint.Lng)*(nextPoint.Lat-startPoint.Lat)/(nextPoint.Lng-startPoint.Lng) if intersectLat == point.Lat { return true } if intersectLat > point.Lat { intersectCount++ } } } return intersectCount%2 != 0 } func InDeliveryArea(point Point, apiKey string) bool { client := mapbox.New(apiKey) // 可以使用自己的多邊形坐標 geojson, _, _ := client.MapMatching().GetMapMatching( []mapbox.Coordinates{ *point.ToCoordinates(), }, nil, ) polygon := geojson.Features[0].Geometry.Coordinates[0].([]interface{}) var polygonArray Polygon for _, item := range polygon { arr := item.([]interface{}) p := Point{Lat: arr[1].(float64), Lng: arr[0].(float64)} polygonArray = append(polygonArray, p) } fmt.Println("多邊形坐標: ", polygonArray) return ContainsPointInPolygon(point, polygonArray) } func main() { point := Point{ Lat: 31.146922, Lng: 121.362282, } apiKey := "YOUR_ACCESS_TOKEN" result := InDeliveryArea(point, apiKey) fmt.Println("坐標是否在配送范圍內:", result) }
登錄后復制
以上是一個基本的Go語言實現代碼示例。在運行這段代碼之前,需要首先在地圖API后臺獲取一個Access Token。將Token替換 YOUR_ACCESS_TOKEN
即可。另外,還需要在地圖API提供的多邊形查詢接口中輸入對應的坐標和相關參數。運行以上代碼,可以得到一個代表坐標所在位置是否在服務范圍內的布爾值。
- 封裝成為可復用庫
上述示例代碼可以幫助我們完成外賣點餐系統的外賣配送范圍功能。但是,在實際應用中,這個功能可能被多個頁面或模塊所使用。為了避免重復編寫代碼的麻煩,我們需要將其封裝成為一個可復用的庫。具體而言:
我們可以將上述的InDeliveryArea函數封裝成為一個可以從外部調用的函數。另外,我們還可以對外部輸入的參數進行檢查和校驗,以保證程序的健壯性。
例如,我們可以將代碼重新組織,把獲取多邊形和判斷點在多邊形內兩個操作分離,這樣也方便后續擴展。
以下是Go語言封裝成為可復用庫的示例代碼:
package delivery import ( "fmt" "math" "github.com/ustroetz/go-mapbox" ) type Point struct { Lat float64 Lng float64 } type Polygon []Point type DeliveryArea struct { polygon Polygon client *mapbox.Client } func NewDeliveryArea(apiKey string, polygonArray []Point) *DeliveryArea { client := mapbox.New(apiKey) var polygon Polygon for _, p := range polygonArray { polygon = append(polygon, p) } return &DeliveryArea{polygon: polygon, client: client} } func (p Point) ToCoordinates() *mapbox.Coordinates { return &mapbox.Coordinates{ Longitude: p.Lng, Latitude: p.Lat, } } func (d *DeliveryArea) containsPoint(point Point) bool { intersectCount := 0 polygonLength := len(d.polygon) if polygonLength < 3 { return false } endPoint := Point{Lat: 9999.0, Lng: point.Lng} for i := 0; i < len(d.polygon); i++ { startPoint := d.polygon[i] nextPointIndex := (i + 1) % len(d.polygon) nextPoint := d.polygon[nextPointIndex] if startPoint.Lng == nextPoint.Lng && endPoint.Lng == startPoint.Lng && (point.Lng == startPoint.Lng && (point.Lat > startPoint.Lat) == (point.Lat < endPoint.Lat)) { return true } if point.Lng > math.Min(startPoint.Lng, nextPoint.Lng) && point.Lng <= math.Max(startPoint.Lng, nextPoint.Lng) { deltaLat := nextPoint.Lat - startPoint.Lat if deltaLat == 0 { continue } intersectLat := startPoint.Lat + (point.Lng-startPoint.Lng)*(nextPoint.Lat-startPoint.Lat)/(nextPoint.Lng-startPoint.Lng) if intersectLat == point.Lat { return true } if intersectLat > point.Lat { intersectCount++ } } } return intersectCount%2 != 0 } func (d *DeliveryArea) Contains(point Point) bool { resp, _, err := d.client.MapMatching().GetMapMatching( []mapbox.Coordinates{ *point.ToCoordinates(), }, nil, ) if err != nil { fmt.Printf("MapMatching error: %s ", err) return false } geojson := resp.Features[0].Geometry.Coordinates[0].([]interface{}) var polygonArray Polygon for _, item := range geojson { arr := item.([]interface{}) p := Point{Lat: arr[1].(float64), Lng: arr[0].(float64)} polygonArray = append(polygonArray, p) } return d.containsPoint(point) }
登錄后復制
這里我們使用了工廠模式來創建DeliveryArea結構體,可以看到,除了方便使用外,還可以發現它們的內部邏輯相對清晰,繼而更易于維護。如下是一個使用上述封裝后庫的示例代碼:
package main import ( "fmt" "github.com/username/repo_deliver_area/delivery" ) func main() { polygonArray := []delivery.Point{ {Lat: 31.23039, Lng: 121.4737}, {Lat: 31.23886, Lng: 121.50016}, {Lat: 31.19394, Lng: 121.5276}, {Lat: 31.18667, Lng: 121.49978}, } apiKey := "YOUR_ACCESS_TOKEN" deliveryArea := delivery.NewDeliveryArea(apiKey, polygonArray) point := delivery.Point{ Lat: 31.146922, Lng: 121.362282, } result := deliveryArea.Contains(point) fmt.Println(result) }
登錄后復制
在運行這段代碼之前,需要先將庫文件放置到指定位置,并替換掉Import路徑中的username/repo_deliver_area
,以及將地圖API的Access Token替換掉 YOUR_ACCESS_TOKEN
。最終輸出將代表坐標所在位置是否在服務范圍內的布爾值。