# How Location-Based Channels Work in BitChat: Geohash Precision Explained

> Discover how BitChat uses geohash precision for location-based channels. Explore building-level to regional chat rooms with this innovative technology.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: deep-dive
- Published: 2026-08-02

---

**BitChat implements hierarchical location-based channels by encoding GPS coordinates into base-32 geohash strings and truncating them to six distinct precision levels, enabling scoped chat rooms from building-level to regional-wide coverage.**

Location-based channels in BitChat leverage geohash encoding to enable proximity-based communication on the Nostr network. The `permissionlesstech/bitchat` repository implements a tiered precision system that converts latitude and longitude coordinates into base-32 geohash strings of varying lengths, creating distinct chat scopes from individual buildings to entire regions.

## Mapping Precision Levels to Geographic Scope

BitChat defines six distinct precision levels that map geohash string lengths to real-world geographic areas. These levels are enumerated in [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift) as `GeohashChannelLevel`.

### The GeohashChannelLevel Enum

The `GeohashChannelLevel` enum establishes a direct correlation between precision integers and geographic scope:

```swift
// bitchat/Protocols/LocationChannel.swift
enum GeohashChannelLevel: CaseIterable, Codable, Equatable {
    case building, block, neighborhood, city, province, region
    
    var precision: Int {                     // ↔ geohash length
        switch self {                        // 8‑char = building, …, 2‑char = region
        case .building:      return 8
        case .block:         return 7
        case .neighborhood:  return 6
        case .city:          return 5
        case .province:      return 4
        case .region:        return 2
        }
    }
}

```

Each precision value corresponds to the exact character length of the geohash string. **For example**, `.city` uses 5 characters (e.g., "9q8yy"), while `.building` uses 8 characters (e.g., "9q8yyz29").

## Encoding Coordinates to Channel Identifiers

The geohash generation logic resides in [`bitchat/Protocols/Geohash.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/Geohash.swift), which provides the lightweight encoder used by [`LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationStateManager.swift).

### Geohash Generation in Swift

The encoder converts latitude and longitude into base-32 strings of specified lengths:

```swift
// bitchat/Protocols/Geohash.swift
static func encode(latitude: Double, longitude: Double, precision: Int) -> String { … }

```

When [`LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationStateManager.swift) needs to generate a channel for a user's current position, it calls this method with the selected level's precision value. A precision of 8 produces a string precise enough to identify individual buildings, while precision 2 covers massive regional areas.

## Constructing the Channel Architecture

BitChat wraps geohash data in the `GeohashChannel` struct and exposes it through the `ChannelID` enum for Nostr protocol integration.

### The GeohashChannel Struct

Defined in [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift), this structure creates unique channel identifiers by combining the precision level with the truncated geohash:

```swift
// bitchat/Protocols/LocationChannel.swift
struct GeohashChannel: Codable, Equatable, Hashable, Identifiable {
    let level: GeohashChannelLevel
    let geohash: String               // already truncated to `level.precision`
    
    var id: String { "\(level)-\(geohash)" }
    var displayName: String { "\(level.displayName) • \(geohash)" }
}

```

The `displayName` property provides user-facing labels rendered in [`LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsSheet.swift), while [`LocationChannelsModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsModel.swift) manages the mapping between geohash lengths and their human-readable titles.

### ChannelID and Nostr Integration

The `ChannelID` enum distinguishes between mesh networks and location-based channels, exposing the geohash via the `nostrGeohashTag` property:

```swift
// bitchat/Protocols/LocationChannel.swift
enum ChannelID: Equatable, Codable {
    case mesh
    case location(GeohashChannel)      // ↔ Nostr “#g” tag
    
    var nostrGeohashTag: String? {
        switch self { case .mesh: nil; case .location(let ch): ch.geohash }
    }
}

```

When publishing messages, BitChat extracts the geohash string and includes it as a `g` tag in Nostr events, ensuring only subscribers within the same geographic cell receive the messages.

## End-to-End Implementation Example

To create a location-based channel at city-level precision, implement the following workflow as used in [`LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationStateManager.swift):

```swift
import BitChat

// 1️⃣ Choose a precision level (e.g., city)
let level = GeohashChannelLevel.city          // precision = 5

// 2️⃣ Encode current coordinates to a geohash of that length
let latitude = 37.7749
let longitude = -122.4194
let geohash = Geohash.encode(latitude: latitude,
                             longitude: longitude,
                             precision: level.precision)   // → "9q8yy"

// 3️⃣ Build the channel object
let cityChannel = GeohashChannel(level: level, geohash: geohash)

// 4️⃣ Activate it
let activeChannel = ChannelID.location(cityChannel)

// 5️⃣ Retrieve the Nostr tag for publishing
if let tag = activeChannel.nostrGeohashTag {
    // publish event with `tags: [["g", tag]]`
    // Only peers in San Francisco area receive this
}

```

## Summary

BitChat's location-based channel system operates through these core mechanisms:

- **Six precision levels** defined in `GeohashChannelLevel`, mapping geohash string lengths from 2 characters (region) to 8 characters (building)
- **Base-32 geohash encoding** via `Geohash.encode()` in [`bitchat/Protocols/Geohash.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/Geohash.swift), converting GPS coordinates into hierarchical grid identifiers
- **Structured channel wrapping** through `GeohashChannel`, which pairs precision metadata with truncated geohash strings for `ChannelID.location`
- **Nostr protocol integration** using `nostrGeohashTag`, which exposes geohash data as `g` tags for geographic message routing

## Frequently Asked Questions

### What geohash precision should I use for neighborhood-level chat?

Use `GeohashChannelLevel.neighborhood`, which corresponds to a 6-character geohash precision. According to the implementation in [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift), this level creates a channel covering approximately 1.2km by 600m, suitable for community discussions within a few city blocks.

### How does BitChat convert my GPS coordinates into a channel ID?

The app passes your latitude and longitude to `Geohash.encode(latitude:longitude:precision:)` along with the selected level's precision integer. The resulting geohash string gets wrapped in a `GeohashChannel` struct, which generates a unique `id` property combining the level name and geohash value as implemented in [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift).

### Can users switch between different geohash precision levels dynamically?

Yes. The [`LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsSheet.swift) UI component allows users to select any of the six `GeohashChannelLevel` cases. When a user switches levels, [`LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationStateManager.swift) recalculates the geohash using the new precision value and emits an updated `ChannelID.location`, immediately re-scoping messages to the new geographic cell.

### Why does BitChat use geohash instead of raw coordinates for location channels?

Geohash provides a standardized, hierarchical encoding that converts complex latitude/longitude pairs into short, shareable strings with predictable precision boundaries. The base-32 algorithm enables BitChat to truncate coordinates to specific lengths, creating discrete geographic "cells" that function as stable channel identifiers across the distributed Nostr relay network without exposing exact user locations.