# Implementation of Location-Based Channels Using Geohash Coordinates in BitChat

> Discover how BitChat uses geohash coordinates to implement location-based channels. Learn about privacy protection through hierarchical geohashing and presence zone restrictions.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: implementation-details
- Published: 2026-07-31

---

**BitChat implements location-based channels by encoding GPS coordinates into hierarchical Geohash strings, mapping each precision level to a distinct channel while restricting presence broadcasts to low-precision zones to protect user privacy.**

BitChat, an open-source decentralized messaging application from the permissionlesstech/bitchat repository, enables proximity-based conversations without centralized infrastructure. The system converts geographic coordinates into compact Geohash identifiers, creating a hierarchy of channels that range from regional scope down to individual buildings while integrating with the Nostr protocol for decentralized message relay.

## Core Geohash Channel Architecture

The foundation of BitChat's location system resides in [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift), which defines the data structures that bridge GPS coordinates to channel identifiers.

### GeohashChannelLevel Enum

The `GeohashChannelLevel` enum establishes a six-tier precision hierarchy:

```swift
enum GeohashChannelLevel: CaseIterable, Codable, Equatable {
    case building, block, neighborhood, city, province, region
    
    var precision: Int { 
        // Returns length of geohash string: 
        // region=2, province=3, city=4, neighborhood=5, block=6, building=8
    }
    var displayName: String { 
        // Returns UI-friendly names like "City", "Building", etc.
    }
}

```

Each level corresponds to a specific geohash string length—**region** uses 2 characters while **building** uses 8—creating increasingly precise geographic boundaries.

### GeohashChannel and ChannelID Types

The `GeohashChannel` struct bundles a precision level with its computed geohash string:

```swift
struct GeohashChannel: Codable, Equatable, Hashable, Identifiable {
    let level: GeohashChannelLevel   // e.g., .city
    let geohash: String              // e.g., "u4pru"
    var id: String { "\(level)-\(geohash)" }
    var displayName: String { "\(level.displayName) • \(geohash)" }
}

```

The `ChannelID` enum distinguishes between the global mesh channel and location-based channels:

```swift
enum ChannelID: Equatable, Codable {
    case mesh
    case location(GeohashChannel)     // Wraps the geohash channel
}

```

## From GPS Coordinates to Channel IDs

BitChat transforms raw location data into channel identifiers through a four-stage pipeline validated in [`bitchatTests/LocationChannelsTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/LocationChannelsTests.swift):

1. **Location Sampling** – CoreLocation provides latitude and longitude updates.
2. **Geohash Encoding** – The app encodes coordinates to a full-precision 8-character string.
3. **Level Derivation** – The system truncates the string to match each `GeohashChannelLevel.precision` value, generating a hierarchy of channel identifiers (region → province → city → neighborhood → block → building).
4. **Channel List Publication** – `LocationChannelsModel` publishes an array of `GeohashChannel` objects, one per level, for the UI layer in [`LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsSheet.swift).

## Bridging to the Nostr Protocol

Each geohash channel maps to a **derived public key** unique to that geographic string, enabling decentralized routing without central coordination. According to the [`docs/GeohashPresenceSpec.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/GeohashPresenceSpec.md) specification, BitChat uses geohash-scoped Nostr tags and specialized event kinds:

- **Chat Messages** – Broadcast with `kind 20000` and the tag `["g", "<geohash>"]`
- **Presence Heartbeats** – Sent as ephemeral `kind 20001` events using the same geohash tag

This design allows relays to filter and forward messages based on geographic scope while maintaining the permissionless nature of the Nostr network.

## Presence Broadcasting and Privacy Controls

The `GeohashPresenceService` (tested in [`bitchatTests/Services/GeohashPresenceServiceTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/GeohashPresenceServiceTests.swift)) manages heartbeat transmissions that signal user availability without exposing precise locations.

### Randomized Broadcast Loop

When the app enters the foreground, the service initiates a randomized loop with the following characteristics:

- **Interval timing** – 40 to 80 seconds between broadcast cycles
- **Decorrelation delays** – 2 to 5 second randomized delays between level transmissions to prevent timing correlation attacks

### Precision-Based Privacy Restrictions

BitChat enforces strict privacy boundaries for presence data:

- **Low-precision only** – Presence heartbeats broadcast exclusively for region, province, and city levels (precision ≤ 5 characters)
- **High-precision silence** – Neighborhood, block, and building channels never transmit presence data
- **Ambiguous indicators** – The UI displays "`[? people]`" for high-precision channels where presence counting is disabled, preventing misleading assumptions about participant visibility

## UI Integration and Activity Tracking

The user interface in [`bitchat/Views/LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/LocationChannelsSheet.swift) renders the channel hierarchy using `GeohashChannel.levelTitle` for headers and `GeohashChannel.displayName` for subtitles.

### Participant Counting

`GeohashChatActivityTracker` (validated in [`bitchatTests/Services/GeohashChatActivityTrackerTests.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchatTests/Services/GeohashChatActivityTrackerTests.swift)) aggregates participant counts by monitoring unique public keys across both chat events (`kind 20000`) and presence events (`kind 20001`). For channels where presence is disabled, the tracker relies solely on active chat participants, with the "`?`" indicator signaling incomplete visibility.

## Summary

- **Hierarchical precision** – Six levels from region (2-character geohash) to building (8-character geohash) provide granular location targeting.
- **Nostr integration** – `kind 20000` handles chat messages while `kind 20001` manages ephemeral presence heartbeats, both scoped via `["g", "<geohash>"]` tags.
- **Privacy by design** – Presence broadcasts limited to low-precision levels (≤5 characters) prevent fine-grained location tracking.
- **Decentralized identity** – Each geohash maps to a derived public key, enabling permissionless channel participation without central registration.

## Frequently Asked Questions

### What precision levels does BitChat support for location channels?

BitChat supports six precision levels defined in `GeohashChannelLevel`: **region** (2 characters), **province** (3 characters), **city** (4 characters), **neighborhood** (5 characters), **block** (6 characters), and **building** (8 characters). Each level truncates the full geohash string to create increasingly specific geographic boundaries.

### How does BitChat protect privacy in location-based channels?

BitChat restricts presence heartbeats to low-precision channels (region, province, and city) where the geohash string is 5 characters or shorter. High-precision channels (neighborhood, block, building) never broadcast presence data, and the UI displays "`[? people]`" to indicate uncertainty about participant counts in these sensitive zones.

### What Nostr event kinds does BitChat use for geohash channels?

BitChat utilizes `kind 20000` for chat messages and the ephemeral `kind 20001` for presence heartbeats. Both event types include the `["g", "<geohash>"]` tag to enable geographic scoping on Nostr relays, as documented in [`docs/GeohashPresenceSpec.md`](https://github.com/permissionlesstech/bitchat/blob/main/docs/GeohashPresenceSpec.md).

### How does the presence heartbeat system prevent timing correlation?

The `GeohashPresenceService` implements randomized timing with two mechanisms: a 40–80 second interval between broadcast cycles and 2–5 second randomized delays between transmissions for different precision levels. This decorrelation prevents observers from linking presence events across different geographic scales to a single user.