# How BitChat Implements Geographic Chat Rooms (Location Channels)

> Discover how BitChat uses geohash strings for geographic chat rooms. Learn about LocationStateManager and GeohashPresenceService for precise, anonymized communication.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: how-to-guide
- Published: 2026-08-22

---

**BitChat implements geographic chat rooms using hierarchical geohash strings derived from GPS coordinates, where** `LocationStateManager` **generates precision-scoped channels and** `GeohashPresenceService` **broadcasts anonymized presence heartbeats to Nostr relays for low-precision levels only.**

BitChat's Location Channels enable users to join chat rooms scoped to their physical location without exposing precise coordinates. According to the permissionlesstech/bitchat source code, the implementation relies on geohash encoding to map latitude and longitude into hierarchical string identifiers, creating a dual-transport model where mesh chats remain local while location-based messages route through the global Nostr network.

## Core Architecture Components

### GeohashChannelLevel and GeohashChannel

In [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift), the `GeohashChannelLevel` enum defines five precision levels ranging from **region** (2 characters) to **building** (8 characters). The `GeohashChannel` struct holds the computed geohash string and its corresponding level, enabling type-safe channel identification throughout the codebase.

### LocationStateManager

The `LocationStateManager` class in [`bitchat/Services/LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/LocationStateManager.swift) serves as the central coordinator. It uses CoreLocation to obtain one-shot GPS fixes, then generates a full set of `GeohashChannel` instances via `Geohash.encode(latitude:longitude:precision:)`. This produces varying precision strings: length 2 for region, 4 for province, 6 for city, and 8 for building. The manager exposes `availableChannels` and `selectedChannel` as published properties for UI observation.

### GeohashPresenceService

Located in [`bitchat/Services/GeohashPresenceService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashPresenceService.swift), this service handles decentralized presence broadcasting. Every 40-80 seconds, it generates **Kind 20001** Nostr events for low-precision geohash channels (region, province, city) while explicitly excluding neighborhood, block, and building levels to prevent fine-grained location leakage.

## How Geographic Chat Rooms Work

1. **Location Acquisition** – When users enable location channels, `LocationStateManager.enableLocationChannels()` requests CoreLocation permissions and triggers a one-shot location request.

2. **Geohash Generation** – In `LocationStateManager.computeChannels(from:)`, the system iterates through `GeohashChannelLevel.allCases`, calling `Geohash.encode(latitude:longitude:precision:)` for each level to produce strings of appropriate length. The resulting array populates `availableChannels`.

3. **Channel Selection** – UI components like `LocationChannelsSheet` invoke `LocationStateManager.select(_:)` with a `ChannelID.location(GeohashChannel)` parameter. The manager persists the selection and sets the `teleported` flag when users manually select a geohash outside their current GPS location.

4. **Presence Broadcasting** – `GeohashPresenceService` monitors `availableChannels` and schedules heartbeat tasks every 40-80 seconds. Each task applies a random 2-5 second delay, derives a temporary Nostr identity via `NostrIdentityBridge.deriveIdentity(forGeohash:)`, constructs a Kind 20001 event using `NostrProtocol.createGeohashPresenceEvent`, and publishes to relays returned by `GeoRelayDirectory.closestRelays(toGeohash:count:)`.

5. **Privacy Enforcement** – The service validates precision through `allowedPrecisions.contains(channel.geohash.count)`, ensuring only region, province, and city-level geohashes (lengths 2, 4, and 6) receive presence broadcasts while higher-precision levels remain local-only.

6. **Reverse Geocoding** – `LocationStateManager` uses `CLGeocoder` to convert coordinates into human-readable names (e.g., "San Francisco • u4pruy"), storing these in `locationNames` for UI display and bookmark resolution.

## Implementation Code Examples

```swift
// Create a location channel for the current city level
let cityChannel = GeohashChannel(level: .city,
                                 geohash: "u4pruy")   // 6-character city-level geohash

// Switch UI to that channel
LocationStateManager.shared.select(.location(cityChannel))

// Manually trigger a presence heartbeat for debugging
await GeohashPresenceService.shared.performHeartbeat()

// Add a persistent geohash bookmark
LocationStateManager.shared.addBookmark("9q8yy")

```

## Key Source Files

- [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift) – Defines `GeohashChannelLevel`, `GeohashChannel`, and `ChannelID` protocols.
- [`bitchat/Services/LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/LocationStateManager.swift) – Core manager converting GPS to geohash channels, handling persistence and bookmarks.
- [`bitchat/Services/GeohashPresenceService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/GeohashPresenceService.swift) – Broadcasts presence heartbeats to Nostr relays with privacy controls.
- [`bitchat/Views/LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/LocationChannelsSheet.swift) – UI component for channel selection.
- [`bitchat/Views/GeohashPeopleList.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/GeohashPeopleList.swift) – Displays participants within a specific geohash channel.

## Summary

- BitChat uses **geohash encoding** to convert GPS coordinates into hierarchical string identifiers representing geographic areas from region to building precision.
- The **`LocationStateManager`** generates multiple precision levels simultaneously, allowing users to select granularity from coarse (regional) to fine (building-level) chat rooms.
- **Privacy protection** occurs at the network layer: `GeohashPresenceService` broadcasts presence only for low-precision geohashes (2-6 characters) to Nostr relays, keeping precise location data local.
- The **dual-transport model** routes mesh-only conversations locally while propagating location-based discovery through the global Nostr relay network using Kind 20001 events.
- Users can **teleport** to arbitrary geohashes or bookmark specific locations for persistent access without physical presence.

## Frequently Asked Questions

### What geohash precision levels does BitChat support?

BitChat implements five hierarchical levels defined in `GeohashChannelLevel`: region (2 characters), province (4 characters), city (6 characters), neighborhood (7 characters), block (7+ characters), and building (8 characters). The UI in `LocationChannelsSheet` presents these as nested geographic scopes for user selection.

### How does BitChat prevent precise location leaks over Nostr?

The `GeohashPresenceService` explicitly filters broadcasts using `allowedPrecisions.contains(channel.geohash.count)`, restricting presence heartbeats to geohashes of length 2, 4, or 6 (region through city). Higher-precision levels representing neighborhoods, blocks, and buildings never generate Kind 20001 events, ensuring fine-grained location data remains confined to local mesh transmission.

### Can users join location channels without sharing their GPS location?

Yes. The `teleported` flag in `LocationStateManager` indicates when a user selects a geohash channel not matching their current GPS coordinates. Users can manually enter geohash strings or select from bookmarks using `LocationStateManager.shared.addBookmark()`, enabling participation in distant geographic chat rooms without physical presence or revealing their actual location.

### How often does BitChat broadcast presence heartbeats?

`GeohashPresenceService` schedules presence broadcasts every 40-80 seconds with a random 2-5 second jitter delay per channel. This decorrelation prevents traffic spikes while maintaining sufficient presence granularity for the Nostr relay network to route messages to active geographic cells.