# BitChat Geohash Location Channel Precision Levels: From Region to Building

> Explore BitChat's six geohash precision levels for Nostr location channels. Discover how BitChat narrows down chat channels from broad regions to specific buildings with geohash.

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

---

**BitChat implements six discrete geohash precision levels—from 2-character regions to 8-character buildings—to create hierarchical geographic chat channels on Nostr.**

BitChat, an open-source decentralized chat application developed by permissionlesstech, uses geohash-based location channels to scope conversations to specific geographic areas. The system defines distinct **BitChat geohash location channel precision levels** that map directly to geohash string lengths, enabling users to create chat rooms at granularities ranging from entire continents down to individual structures.

## How BitChat Maps Precision Levels to Geographic Granularity

The hierarchy of location channels is defined in [`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift). Here, the `GeohashChannelLevel` enum establishes the relationship between human-readable geographic concepts and their corresponding geohash character counts.

### The GeohashChannelLevel Enum

This enumeration provides six cases, each specifying an exact geohash length. Shorter strings cover larger geographic areas, creating a nested hierarchy of chat channels:

```swift
// 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
        }
    }
}

```

The precision values are not arbitrary: each increment in geohash length subdivides the parent cell into 32 smaller cells, allowing **building**-level channels to target roughly 20-meter squares while **region**-level channels encompass thousands of kilometers.

## Encoding Coordinates into Geohash Strings

Before a channel can be created, raw latitude and longitude coordinates must be converted into a truncated base-32 geohash string. This conversion is handled by the lightweight encoder in [`bitchat/Protocols/Geohash.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/Geohash.swift).

### The Base-32 Geohash Encoder

The static method `Geohash.encode(latitude:longitude:precision:)` generates a geohash string of the exact length required by the selected precision level:

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

```

This function performs the coordinate-to-string conversion that enables the spatial indexing necessary for geographic message routing.

## Constructing Channel IDs for Nostr Publication

Once encoded, the geohash string and its corresponding level are bundled into a concrete channel structure suitable for Nostr publication.

### GeohashChannel and ChannelID Implementation

The `GeohashChannel` struct stores the precision level and the already-truncated geohash string. It conforms to `Identifiable` and `Hashable` for use in SwiftUI lists and as dictionary keys:

```swift
// 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)" }
}

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 a user selects a location channel, the app instantiates `ChannelID.location(GeohashChannel)`, which exposes the `nostrGeohashTag` property. This tag is then attached to Nostr events as a `g` tag, ensuring only peers subscribed to the same geohash cell receive the messages.

## Practical Workflow for Location Channel Activation

The integration of these components occurs in [`bitchat/Services/LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/LocationStateManager.swift) and the UI layer in [`bitchat/Views/LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/LocationChannelsSheet.swift). The following pattern demonstrates how to activate a location channel at a specific precision:

```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]]`
}

```

The [`bitchat/App/LocationChannelsModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/LocationChannelsModel.swift) file handles the reverse mapping, resolving a geohash length back to its `GeohashChannelLevel` and providing human-readable display names for each precision tier.

## Summary

- **Six precision levels** are available, ranging from 2-character **region** to 8-character **building** granularity.
- The `GeohashChannelLevel` enum in [`LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannel.swift) defines the mapping between geographic concepts and geohash string lengths.
- `Geohash.encode(latitude:longitude:precision:)` converts GPS coordinates to the appropriate precision level.
- `GeohashChannel` bundles the level and truncated geohash, while `ChannelID.location` generates the Nostr `g` tag for message scoping.
- UI components use [`LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsSheet.swift) and [`LocationChannelsModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/LocationChannelsModel.swift) to present and manage the different precision options.

## Frequently Asked Questions

### What is the most precise location channel available in BitChat?

The **building** level provides the highest precision using **8-character geohashes**, typically corresponding to an area of approximately 20 meters by 20 meters. This allows users to create hyper-local chat rooms specific to a particular structure or address.

### How does BitChat convert GPS coordinates into a channel identifier?

The app calls `Geohash.encode(latitude:longitude:precision:)` with the precision integer obtained from the selected `GeohashChannelLevel`. The resulting string is stored in a `GeohashChannel` struct, which is then wrapped in `ChannelID.location` to generate the Nostr "g" tag used for message routing.

### Why does the region level use only 2 characters while building uses 8?

Geohash length inversely correlates with geographic coverage—each additional character subdivides the parent cell into 32 smaller cells. BitChat maps shorter strings (2 characters) to **region**-scale areas and longer strings (8 characters) to **building**-scale areas, creating an appropriate chat room granularity for each use case.

### Which source files handle the geohash precision logic?

[`bitchat/Protocols/LocationChannel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/LocationChannel.swift) defines the precision levels and channel structures, [`bitchat/Protocols/Geohash.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Protocols/Geohash.swift) implements the base-32 encoder, and [`bitchat/Services/LocationStateManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/LocationStateManager.swift) manages the generation workflow for active channels. The UI presentation logic resides in [`bitchat/Views/LocationChannelsSheet.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Views/LocationChannelsSheet.swift) and [`bitchat/App/LocationChannelsModel.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/App/LocationChannelsModel.swift).