如何使用 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 是唯一值。我希望如果您更正您的响应模型,您的问题应该会得到解决。
我正在创建一个应用程序,它需要能够确定给定区域中的位置,然后检索有关这些位置的详细信息,然后显示这些信息。为此,我一直在使用 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 是唯一值。我希望如果您更正您的响应模型,您的问题应该会得到解决。