geotoolbox
GeoToolbox PlaceDescriptor patterns with MapKit integration for location representation, geocoding, and multi-service place identifiers. Use when working with place descriptors, geocoding, or cross-service location data.
Best use case
geotoolbox is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
GeoToolbox PlaceDescriptor patterns with MapKit integration for location representation, geocoding, and multi-service place identifiers. Use when working with place descriptors, geocoding, or cross-service location data.
Teams using geotoolbox should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/geotoolbox/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How geotoolbox Compares
| Feature / Agent | geotoolbox | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
GeoToolbox PlaceDescriptor patterns with MapKit integration for location representation, geocoding, and multi-service place identifiers. Use when working with place descriptors, geocoding, or cross-service location data.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# GeoToolbox and PlaceDescriptor Patterns
Portable location representation using `PlaceDescriptor` from the GeoToolbox framework. Covers place construction from coordinates, addresses, and MapKit items; forward and reverse geocoding with the new async APIs; and multi-service place identifiers for cross-platform interop.
## When This Skill Activates
Use this skill when the user:
- Asks about **GeoToolbox** or **PlaceDescriptor**
- Wants to represent a place with coordinates, address, or common name
- Needs to convert between **MKMapItem** and **PlaceDescriptor**
- Asks about **forward geocoding** (address to coordinates) or **reverse geocoding** (coordinates to address)
- Wants to store or transmit **multi-service place identifiers** (Apple Maps, Google Maps, etc.)
- Mentions **PlaceRepresentation**, **SupportingPlaceRepresentation**, or **MKGeocodingRequest**
- Asks about portable or interoperable location data structures
## Decision Tree
```
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 Availability
| API | Minimum OS | Import |
|-----|-----------|--------|
| `PlaceDescriptor` | iOS 26 / macOS 26 | `GeoToolbox` |
| `PlaceRepresentation` | iOS 26 / macOS 26 | `GeoToolbox` |
| `SupportingPlaceRepresentation` | iOS 26 / macOS 26 | `GeoToolbox` |
| `MKGeocodingRequest` | iOS 26 / macOS 26 | `MapKit` |
| `MKReverseGeocodingRequest` | iOS 26 / macOS 26 | `MapKit` |
| `PlaceDescriptor(item:)` | iOS 26 / macOS 26 | `GeoToolbox` + `MapKit` |
| `MKMapItem` | iOS 6 / macOS 10.9 | `MapKit` |
| `CLLocationCoordinate2D` | iOS 2 / macOS 10.6 | `CoreLocation` |
## Quick Start
### Create a PlaceDescriptor from Coordinates
```swift
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")
```
### Create a PlaceDescriptor from an Address
```swift
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)")
}
```
### Multiple Representations and Service Identifiers
```swift
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)")
}
```
### Convert from MKMapItem
```swift
import GeoToolbox
import MapKit
func descriptorFromMapItem(_ mapItem: MKMapItem) -> PlaceDescriptor {
PlaceDescriptor(item: mapItem)
}
```
## Forward Geocoding (Address to Coordinates)
```swift
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)")
}
```
## Reverse Geocoding (Coordinates to Address)
```swift
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")")
}
```
## Full Integration Example
Geocode an address, convert the result to a PlaceDescriptor with service identifiers, and read back all properties:
```swift
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)")
}
}
```
## Top Mistakes
| # | Mistake | Problem | Fix |
|---|---------|---------|-----|
| 1 | Importing only `MapKit` when using `PlaceDescriptor` | `PlaceDescriptor` lives in `GeoToolbox`, not `MapKit` | Add `import GeoToolbox` alongside `import MapKit` |
| 2 | Using `CLGeocoder` instead of `MKGeocodingRequest` | `CLGeocoder` returns `CLPlacemark` which lacks MapKit integration | Use `MKGeocodingRequest` / `MKReverseGeocodingRequest` for `MKMapItem` results |
| 3 | Assuming `PlaceDescriptor` always has a coordinate | A descriptor can be address-only with no coordinate | Check `descriptor.coordinate` for `nil` before use |
| 4 | Assuming `PlaceDescriptor` always has an address | A descriptor can be coordinate-only with no address | Check `descriptor.address` for `nil` before use |
| 5 | Hardcoding service identifier keys | Service identifier keys are strings; typos cause silent failures | Define constants for service keys like `"com.apple.maps"` |
| 6 | Passing `CLLocationCoordinate2D` directly to `MKReverseGeocodingRequest` | The initializer takes a `CLLocation`, not a raw coordinate | Wrap in `CLLocation(latitude:longitude:)` first |
| 7 | Ignoring empty geocoding results | Geocoding can return zero results for ambiguous or invalid input | Guard against empty `mapItems` arrays |
| 8 | Not handling geocoding errors | Network or service failures throw errors | Use `do/catch` or `try await` with proper error handling |
## Patterns
### Service Identifier Constants
Define constants to avoid typos in service identifier keys:
```swift
// 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
}
```
```swift
// Bad -- raw string literals are error-prone
if let id = descriptor.serviceIdentifier(for: "com.apple.map") { // typo: "map" not "maps"
// silently nil
}
```
### Nil-Safe Property Access
```swift
// 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: " -- ")
}
```
```swift
// Bad -- force-unwrapping optional properties
let name = descriptor.commonName! // crashes if nil
let coord = descriptor.coordinate! // crashes if no coordinate representation
```
### Geocoding with Fallback
```swift
// 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
}
}
```
```swift
// 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
}
```
## Review Checklist
- [ ] `import GeoToolbox` is present when using `PlaceDescriptor`, `PlaceRepresentation`, or `SupportingPlaceRepresentation`
- [ ] `import MapKit` is present when using `MKGeocodingRequest`, `MKReverseGeocodingRequest`, or `MKMapItem`
- [ ] `import CoreLocation` is present when using `CLLocationCoordinate2D` or `CLLocation`
- [ ] `PlaceDescriptor` properties (`coordinate`, `address`, `commonName`) are checked for `nil` before use
- [ ] Service identifier keys use defined constants, not raw string literals
- [ ] `MKReverseGeocodingRequest` receives a `CLLocation`, not a raw `CLLocationCoordinate2D`
- [ ] Geocoding results are checked for empty arrays before accessing elements
- [ ] Geocoding calls use proper `async/await` error handling with `do/catch`
- [ ] Multiple representations are provided when both coordinate and address are known
- [ ] Supporting representations include service identifiers when cross-service interop is needed
## Cross-References
- For **MapKit map views** and annotations, see MapKit documentation
- For **CoreLocation** permissions and location updates, see CoreLocation documentation
- For **SwiftUI integration** with maps, see `Map` view in SwiftUI
## References
- [GeoToolbox Framework](https://developer.apple.com/documentation/geotoolbox)
- [PlaceDescriptor](https://developer.apple.com/documentation/geotoolbox/placedescriptor)
- [MKGeocodingRequest](https://developer.apple.com/documentation/mapkit/mkgeocodingrequest)
- [MKReverseGeocodingRequest](https://developer.apple.com/documentation/mapkit/mkreversegeocodingrequest)
- [MKMapItem](https://developer.apple.com/documentation/mapkit/mkmapitem)Related Skills
watchOS
watchOS development guidance including SwiftUI for Watch, Watch Connectivity, complications, and watch-specific UI patterns. Use for watchOS code review, best practices, or Watch app development.
visionos-widgets
visionOS widget patterns including mounting styles, glass/paper textures, proximity-aware layouts, and spatial widget families. Use when creating or adapting widgets for visionOS.
test-data-factory
Generate test fixture factories for your models. Builder pattern and static factories for zero-boilerplate test data. Use when tests need sample data setup.
test-contract
Generate protocol/interface test suites that any implementation must pass. Define the contract once, test every implementation. Use when designing protocols or swapping implementations.
tdd-refactor-guard
Pre-refactor safety checklist. Verifies test coverage exists before AI modifies existing code. Use before asking AI to refactor anything.
tdd-feature
Red-green-refactor scaffold for building new features with TDD. Write failing tests first, then implement to pass. Use when building new features test-first.
tdd-bug-fix
Fix bugs using red-green-refactor — reproduce the bug as a failing test first, then fix it. Use when fixing bugs to ensure they never regress.
snapshot-test-setup
Set up SwiftUI visual regression testing with swift-snapshot-testing. Generates snapshot test boilerplate and CI configuration. Use for UI regression prevention.
integration-test-scaffold
Generate cross-module test harness with mock servers, in-memory stores, and test configuration. Use when testing networking + persistence + business logic together.
characterization-test-generator
Generates tests that capture current behavior of existing code before refactoring. Use when you need a safety net before AI-assisted refactoring or modifying legacy code.
testing
TDD and testing skills for iOS/macOS apps. Covers characterization tests, TDD workflows, test contracts, snapshot tests, and test infrastructure. Use for test-driven development, adding tests to existing code, or building test infrastructure.
webkit-integration
WebKit integration in SwiftUI using WebView and WebPage for embedding web content, navigation, JavaScript interop, and customization. Use when embedding web content in SwiftUI apps.