如何使用 Swift 中的 place_id 从 Google 个地点 API 请求地点详情?

How to request Place Details from Google Places API using place_id in Swift?

我正在创建一个应用程序,它需要能够确定给定区域中的位置,然后检索有关这些位置的详细信息,然后显示这些信息。为此,我一直在使用 Google Places API,但我无法使用其 place_id.

检索位置的详细信息

目前我能够成功执行 Google Places 'Nearby Search' 请求,其中 return 有许多位置和一些关于它们的基本数据(包括 place_id ).我遇到的问题是使用我从第一个请求中收到的 place-id 发出 'Place Details' 请求以获取 Google 拥有的关于各自位置的所有信息。

vv 下面的代码详细说明了我当前的尝试 vv

调用 2 个请求的函数:

    func requestAndCombineGData(location: CLLocation, radius: Int) {

        self.mapView.clear()

        // Calls 'Nearby Search' request
        googleClient.getGooglePlacesData(location: location, withinMeters: radius) { (response) in
            print("Made Nearby Search request. Passed response here:", response)

            // loops through each result from the 'Nearby Request' to get the 'place_id' and make 'Place Details'
            for location in response.results {
                // Calls 'Place Details' request
                self.googleClient.getGooglePlacesDetailsData(place_id: location.place_id) { (detailsResponse) in
                    print("Made Place Details request. Passed response here:", detailsResponse)

//                    function to drop markers using data received above here
//                    self.putPlaces(places: response.results)
                }
            }

        }

    }

GoogleClient.swift 包含处理上述请求的代码的文件:

import SwiftUI
import Foundation
import CoreLocation


//Protocol
protocol GoogleClientRequest {
    var googlePlacesKey : String { get set }
    func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: @escaping (GooglePlacesResponse) -> ())
    func getGooglePlacesDetailsData(place_id: String, using completionHandler: @escaping (GooglePlacesDetailsResponse) -> ())
}



// GoogleClient class that conforms to the ZGoogleClient Request protocol
class GoogleClient: GoogleClientRequest {

    let session = URLSession(configuration: .default)
    var googlePlacesKey: String = "MY_KEY_GOES_HERE"
    let categoriesArray = [
        "park",
        "restaurant",
        "zoo"
    ]


    func getGooglePlacesData(location: CLLocation, withinMeters radius: Int, using completionHandler: @escaping (GooglePlacesResponse) -> ())  {

        for category in categoriesArray {

            let url = googlePlacesNearbyDataURL(forKey: googlePlacesKey, location: location, radius: radius, type: category)

            let task = session.dataTask(with: url) { (responseData, _, error) in
                if let error = error {
                    print(error.localizedDescription)
                    return
                }

                guard let data = responseData, let response = try? JSONDecoder().decode(GooglePlacesResponse.self, from: data) else {
                    print("Could not decode JSON response")
                    completionHandler(GooglePlacesResponse(results:[]))
                    return
                }


                if response.results.isEmpty {
                    print("GC - response returned empty", response)
                } else {
                    print("GC - response contained content", response)
                    completionHandler(response)
                }

            }
            task.resume()
        }

    }


    func getGooglePlacesDetailsData(place_id: String, using completionHandler: @escaping (GooglePlacesDetailsResponse) -> ())  {

        let url = googlePlacesDetailsURL(forKey: googlePlacesKey, place_ID: place_id)

        let task = session.dataTask(with: url) { (responseData, _, error) in
            if let error = error {
                print(error.localizedDescription)
                return
            }

            guard let data = responseData, let detailsResponse = try? JSONDecoder().decode(GooglePlacesDetailsResponse.self, from: data) else {
                print("Could not decode JSON response. responseData was: ", responseData)
                completionHandler(GooglePlacesDetailsResponse(results:[]))
                return
            }

//                print("response result: ", detailsResponse.results)

            if detailsResponse.results.isEmpty {
                print("getGPDetails - response returned empty", detailsResponse)
            } else {
                print("getGPDetails - response contained content", detailsResponse)
                completionHandler(detailsResponse)
            }

        }
        task.resume()

    }


    func googlePlacesNearbyDataURL(forKey apiKey: String, location: CLLocation, radius: Int, type: String) -> URL {
        print("passed  location  before url creation ", location)
        print("passed  radius  before url creation ", radius)

        let baseURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
        let locationString = "location=" + String(location.coordinate.latitude) + "," + String(location.coordinate.longitude)
        let radiusString = "radius=" + String(radius)
        let typeString = "type=" + String(type)
//        let rankby = "rankby=distance"
//        let keywrd = "keyword=" + keyword
        let key = "key=" + apiKey

        print("Request URL:", URL(string: baseURL + locationString + "&" + radiusString + "&" + type + "&" + key)!)
        return URL(string: baseURL + locationString + "&" + radiusString + "&" + typeString + "&" + key)!
    }


    func googlePlacesDetailsURL(forKey apiKey: String, place_ID: String) -> URL {
        print("passed  place_ID  before url creation ", place_ID)

        let baseURL = "https://maps.googleapis.com/maps/api/place/details/json?"
        let place_idString = "place_id=" + place_ID
        let fields = "fields=rating"
        let key = "key=" + apiKey

        print("Details request URL:", URL(string: baseURL + place_idString + "&" + fields + "&" + key)!)
        return URL(string: baseURL + place_idString + "&" + fields + "&" + key)!
    }



}

ResponseModels.swift 包含结构以处理 2 种不同请求响应的文件:

import SwiftUI
import Foundation



struct GooglePlacesResponse : Codable {
    let results : [Place]
    enum CodingKeys : String, CodingKey {
        case results = "results"
    }
}



// Place struct
struct Place : Codable {

    let geometry : Location
    let name : String
    let place_id: String
    let openingHours : OpenNow?
    let photos : [PhotoInfo]?
    let types : [String]
    let address : String


    enum CodingKeys : String, CodingKey {
        case geometry = "geometry"
        case name = "name"
        case place_id = "place_id"
        case openingHours = "opening_hours"
        case photos = "photos"
        case types = "types"
        case address = "vicinity"
    }


    // Location struct
    struct Location : Codable {

        let location : LatLong

        enum CodingKeys : String, CodingKey {
            case location = "location"
        }


        // LatLong struct
        struct LatLong : Codable {

            let latitude : Double
            let longitude : Double

            enum CodingKeys : String, CodingKey {
                case latitude = "lat"
                case longitude = "lng"
            }
        }

    }


    // OpenNow struct
    struct OpenNow : Codable {

        let isOpen : Bool

        enum CodingKeys : String, CodingKey {
            case isOpen = "open_now"
        }
    }


    // PhotoInfo struct
    struct PhotoInfo : Codable {

        let height : Int
        let width : Int
        let photoReference : String

        enum CodingKeys : String, CodingKey {
            case height = "height"
            case width = "width"
            case photoReference = "photo_reference"
        }
    }



}



struct GooglePlacesDetailsResponse : Codable {
    let results : [PlaceDetails]
    enum CodingKeysDetails : String, CodingKey {
        case results = "results"
    }
}



// PlaceDetails struct
// I have fields commented out because I wanted to just get a location's rating for testing before implementing the rest
struct PlaceDetails : Codable {

//    let place_id: String
//    let geometry : Location
//    let name : String
    let rating : CGFloat?
//    let price_level : Int
//    let types : [String]
//    let openingHours : OpenNow?
//    let formatted_address : String
//    let formatted_phone_number : String
//    let website : String
//    let reviews : String
//    let photos : [PhotoInfo]?


    enum CodingKeysDetails : String, CodingKey {
//        case place_id = "place_id"
//        case geometry = "geometry"
//        case name = "name"
        case rating = "rating"
//        case price_level = "price_level"
//        case types = "types"
//        case openingHours = "opening_hours"
//        case formatted_address = "formatted_address"
//        case formatted_phone_number = "formatted_phone_number"
//        case website = "website"
//        case reviews = "reviews"
//        case photos = "photos"
    }



}


Google 地方 API 我一直在参考的文档:

https://developers.google.com/places/web-service/search

-

就像我上面说的,现在我的第一个请求 ('Nearby Search') 成功地 returning 数据,但是当我尝试使用'place_id'。我的控制台保持 returning "Could not decode JSON response. Response: Optional(106 bytes)",它来自我的 'GoogleClient.swift' 文件中的评论。

我的问题是:

我发出 'Place Details' 请求的方式有什么问题导致 return 响应无法解码?

有没有更好的方法来发出附近搜索请求,然后使用 returned 位置的 'place_id' 在 swift 中发出地点详情请求?

您遇到的问题似乎与您的模型有关:

struct GooglePlacesDetailsResponse : Codable {
let results : [PlaceDetails]
enum CodingKeysDetails : String, CodingKey {
    case results = "results"
}

}

根据 documentation "results" 的地点详细信息响应中没有键,正确的键是 "result" 非复数。您还将模型定义为结构 PlaceDetails 的数组,这是不正确的。当您 return 使用地点 ID 放置详细信息时,它将 return 只有一个对象,即该特定 ID 的地点详细信息,因为地点 ID 是唯一值。我希望如果您更正您的响应模型,您的问题应该会得到解决。