mcpField
G
geotoolbox

geotoolbox

github.com/rshankras开发83874 次安装14 周前更新
代码仓库

关于 geotoolbox

面向 Swift 开发者的 GeoToolbox 技能,用于表示地点并解析地址。内容涵盖 PlaceDescriptor、MapKit 转换、异步地理编码,以及跨地点服务互操作所需的标识符。

主要功能

  1. 01用坐标或地址表示地点
  2. 02在 PlaceDescriptor 与 MKMapItem 间转换
  3. 03执行异步正向和反向地理编码
  4. 04保存多个地点服务的标识符
  5. 05安全处理可选地点属性

适用场景

  1. 01同时用地址和坐标表示地点
  2. 02将地址解析为地图项目
  3. 03根据坐标查找地址
  4. 04在系统间传递服务标识符

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rshankras/claude-code-apple-skills --skill geotoolbox

安装后在 Claude Code 中输入/geotoolbox确认已加载。

详细介绍

GeoToolbox 和 PlaceDescriptor 模式

使用 GeoToolbox 框架中的 PlaceDescriptor 表示可移植的位置。涵盖从坐标、地址和 MapKit 条目构建地点;使用新版异步 API 进行正向和反向地理编码;以及用于跨平台互操作的多服务地点标识符。

此技能的触发条件

在以下情况下使用此技能:

  • 用户询问 GeoToolbox 或 PlaceDescriptor
  • 用户想要用坐标、地址或常用名称表示地点
  • 用户需要在 MKMapItem 和 PlaceDescriptor 之间进行转换
  • 用户询问 正向地理编码(地址转坐标)或 反向地理编码(坐标转地址)
  • 用户想要存储或传输多服务地点标识符(Apple Maps、Google Maps 等)
  • 用户提到 PlaceRepresentation、SupportingPlaceRepresentation 或 MKGeocodingRequest
  • 用户询问可移植或可互操作的位置数据结构

决策树

What do you need?
|
+-- Represent a place with coordinates and/or address
|   +-- From a known coordinate
|   |   +-- PlaceRepresentation.coordinate(CLLocationCoordinate2D)
|   +-- From a known address string
|   |   +-- PlaceRepresentation.address(String)
|   +-- Both coordinate and address
|   |   +-- Pass multiple representations to PlaceDescriptor
|   +-- From an existing MKMapItem
|       +-- PlaceDescriptor(item: MKMapItem)
|
+-- Geocode an address to coordinates
|   +-- MKGeocodingRequest(addressString:)
|   +-- try await request.mapItems
|
+-- Reverse geocode coordinates to an address
|   +-- MKReverseGeocodingRequest(location:)
|   +-- try await request.mapItems
|
+-- Attach service identifiers (Apple Maps, Google, etc.)
|   +-- SupportingPlaceRepresentation.serviceIdentifiers([String: String])
|
+-- Read place properties
    +-- descriptor.coordinate, descriptor.address, descriptor.commonName
    +-- descriptor.serviceIdentifier(for: "com.apple.maps")

API 可用性

API最低 OS 版本导入
PlaceDescriptoriOS 26 / macOS 26GeoToolbox
PlaceRepresentationiOS 26 / macOS 26GeoToolbox
SupportingPlaceRepresentationiOS 26 / macOS 26GeoToolbox
MKGeocodingRequestiOS 26 / macOS 26MapKit
MKReverseGeocodingRequestiOS 26 / macOS 26MapKit
PlaceDescriptor(item:)iOS 26 / macOS 26GeoToolbox + MapKit
MKMapItemiOS 6 / macOS 10.9MapKit
CLLocationCoordinate2DiOS 2 / macOS 10.6CoreLocation

快速入门

从坐标创建 PlaceDescriptor
import GeoToolbox
import CoreLocation

let coordinate = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)

let descriptor = PlaceDescriptor(
    representations: [.coordinate(coordinate)],
    commonName: "Apple Park"
)

// Read back
if let coord = descriptor.coordinate {
    print("Lat: \(coord.latitude), Lon: \(coord.longitude)")
}
print(descriptor.commonName ?? "No name")
从地址创建 PlaceDescriptor
import GeoToolbox

let descriptor = PlaceDescriptor(
    representations: [.address("One Apple Park Way, Cupertino, CA 95014")],
    commonName: "Apple Park"
)

if let address = descriptor.address {
    print("Address: \(address)")
}
多种表示形式和服务标识符
import GeoToolbox
import CoreLocation

let coordinate = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)

let descriptor = PlaceDescriptor(
    representations: [
        .coordinate(coordinate),
        .address("One Apple Park Way, Cupertino, CA 95014")
    ],
    commonName: "Apple Park",
    supportingRepresentations: [
        .serviceIdentifiers([
            "com.apple.maps": "apple-maps-id-12345",
            "com.google.maps": "ChIJ-bfVTh8_j4ARDMPaL2Njo3I"
        ])
    ]
)

// Access a specific service identifier
if let appleId = descriptor.serviceIdentifier(for: "com.apple.maps") {
    print("Apple Maps ID: \(appleId)")
}
从 MKMapItem 转换
import GeoToolbox
import MapKit

func descriptorFromMapItem(_ mapItem: MKMapItem) -> PlaceDescriptor {
    PlaceDescriptor(item: mapItem)
}

正向地理编码(地址转坐标)

import MapKit

func geocodeAddress(_ addressString: String) async throws -> [MKMapItem] {
    let request = MKGeocodingRequest(addressString: addressString)
    let mapItems = try await request.mapItems
    return mapItems
}

// Usage
let items = try await geocodeAddress("One Apple Park Way, Cupertino, CA")
if let first = items.first {
    let coord = first.placemark.coordinate
    print("Found: \(coord.latitude), \(coord.longitude)")
}

反向地理编码(坐标转地址)

import MapKit
import CoreLocation

func reverseGeocode(_ coordinate: CLLocationCoordinate2D) async throws -> [MKMapItem] {
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
    let request = MKReverseGeocodingRequest(location: location)
    let mapItems = try await request.mapItems
    return mapItems
}

// Usage
let coordinate = CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)
let items = try await reverseGeocode(coordinate)
if let first = items.first {
    print("Address: \(first.placemark.title ?? "Unknown")")
}

完整集成示例

对地址进行地理编码,将结果转换为带服务标识符的 PlaceDescriptor,并读取所有属性:

import GeoToolbox
import MapKit
import CoreLocation

func buildPlaceDescriptor(from addressString: String) async throws -> PlaceDescriptor? {
    // Forward geocode
    let request = MKGeocodingRequest(addressString: addressString)
    let mapItems = try await request.mapItems
    guard let mapItem = mapItems.first else { return nil }

    // Convert MKMapItem to PlaceDescriptor
    var descriptor = PlaceDescriptor(item: mapItem)

    // Or build manually with extra data
    let coordinate = mapItem.placemark.coordinate
    descriptor = PlaceDescriptor(
        representations: [
            .coordinate(coordinate),
            .address(addressString)
        ],
        commonName: mapItem.name,
        supportingRepresentations: [
            .serviceIdentifiers([
                "com.apple.maps": "resolved-id-\(coordinate.latitude)"
            ])
        ]
    )

    return descriptor
}

func displayDescriptor(_ descriptor: PlaceDescriptor) {
    if let name = descriptor.commonName {
        print("Name: \(name)")
    }
    if let coord = descriptor.coordinate {
        print("Coordinate: \(coord.latitude), \(coord.longitude)")
    }
    if let address = descriptor.address {
        print("Address: \(address)")
    }
    if let appleId = descriptor.serviceIdentifier(for: "com.apple.maps") {
        print("Apple Maps ID: \(appleId)")
    }
}

常见错误

#错误问题修复方法
1使用 PlaceDescriptor 时只导入 MapKitPlaceDescriptor 属于 GeoToolbox,而非 MapKit在 import MapKit 旁添加 import GeoToolbox
2硬编码服务标识符键服务标识符键是字符串;拼写错误会导致静默失败为 "com.apple.maps" 等服务键定义常量
3未处理地理编码错误网络或服务故障会抛出错误使用 do/catch,或通过 try await 进行适当的错误处理

模式

服务标识符常量

定义常量,避免服务标识符键出现拼写错误:

// Good -- constants prevent typos
enum PlaceService {
    static let appleMaps = "com.apple.maps"
    static let googleMaps = "com.google.maps"
    static let foursquare = "com.foursquare"
}

if let id = descriptor.serviceIdentifier(for: PlaceService.appleMaps) {
    // use id
}
// Bad -- raw string literals are error-prone
if let id = descriptor.serviceIdentifier(for: "com.apple.map") { // typo: "map" not "maps"
    // silently nil
}
安全处理 nil 属性
// Good -- check each optional property
func formatPlace(_ descriptor: PlaceDescriptor) -> String {
    var parts: [String] = []
    if let name = descriptor.commonName {
        parts.append(name)
    }
    if let address = descriptor.address {
        parts.append(address)
    }
    if let coord = descriptor.coordinate {
        parts.append("\(coord.latitude), \(coord.longitude)")
    }
    return parts.joined(separator: " -- ")
}
// Bad -- force-unwrapping optional properties
let name = descriptor.commonName! // crashes if nil
let coord = descriptor.coordinate! // crashes if no coordinate representation
使用备用方案进行地理编码
// Good -- handle empty results and errors
func resolvePlace(_ address: String) async -> PlaceDescriptor? {
    do {
        let request = MKGeocodingRequest(addressString: address)
        let items = try await request.mapItems
        guard let item = items.first else {
            print("No results for address: \(address)")
            return nil
        }
        return PlaceDescriptor(item: item)
    } catch {
        print("Geocoding failed: \(error.localizedDescription)")
        return nil
    }
}
// Bad -- no error handling, no empty check
func resolvePlace(_ address: String) async -> PlaceDescriptor {
    let request = MKGeocodingRequest(addressString: address)
    let items = try! await request.mapItems // crashes on failure
    return PlaceDescriptor(item: items.first!) // crashes if empty
}

检查清单

  • 使用 PlaceDescriptor、PlaceRepresentation 或 SupportingPlaceRepresentation 时,已导入 GeoToolbox
  • 使用 MKGeocodingRequest、MKReverseGeocodingRequest 或 MKMapItem 时,已导入 MapKit
  • 使用 CLLocationCoordinate2D 或 CLLocation 时,已导入 CoreLocation
  • 使用前检查 PlaceDescriptor 的属性(coordinate、address、commonName)是否为 nil
  • 服务标识符键使用已定义的常量,而非原始字符串字面量
  • MKReverseGeocodingRequest 接收的是 CLLocation,而非原始 CLLocationCoordinate2D
  • 访问地理编码结果中的元素前,先检查数组是否为空
  • 地理编码调用使用 do/catch 进行适当的 async/await 错误处理
  • 已知坐标和地址时,同时提供多种表示形式
  • 需要跨服务互操作时,附带服务标识符的辅助表示形式

交叉引用

  • 有关 MapKit 地图视图和标注,请参阅 MapKit 文档
  • 有关 CoreLocation 权限和位置更新,请参阅 CoreLocation 文档
  • 有关地图的 SwiftUI 集成,请参阅 SwiftUI 中的 Map 视图

参考资料

评论

支持 Markdown