DEV Community

Cover image for Introducing a Real-Time World Food Map — here's how it works (and how to query it with Python)
Djowda for Djowda

Posted on

Introducing a Real-Time World Food Map — here's how it works (and how to query it with Python)

👉 djowda.com/food-freedom-map

A real-time world map where anyone, anywhere on earth can list food, ask for it, or donate it — and be seen instantly, globally — with no account, no backend, and no central server.

This post covers:

  • What we built and why
  • The architecture behind it (Nostr + DIFP geo-grid)
  • A Python script to query any cell on the network right now
  • What's coming next (three-way trades + Trade on Map)

The problem we're trying to solve

The global food system is enormously centralized. Platforms extract rent from every transaction between producers and consumers. Farmers in a village can't be discovered by someone 10km away without paying a marketplace. During a disaster, there's no open channel to broadcast "I have food here" or "we need food here" without going through a gatekeeper.

We wanted to build the infrastructure layer that makes that possible. Open, permissionless, unstoppable.

The result is DIFP — the Djowda Interconnected Food Protocol. The Food Freedom Map is the first public UI on top of it.


What the map does right now

Open djowda.com/food-freedom-map, click anywhere on earth, and you can:

  • 📦 List — add a food product with a price
  • 🙋 Ask — broadcast that you need something
  • 🤝 Donate — offer something for free
  • 🗺️ Discover — browse nearby stores, farms, restaurants — see their catalog, ask list, and donations in real time
  • 🔍 Search — jump to any location by coordinates or Cell ID

Everything happens without an account. Your identity is a secp256k1 keypair auto-generated in your browser and stored in localStorage. Your pin is a signed Nostr event broadcast to wss://relay.damus.io.


The architecture

Layer 1 — The geo-grid (MinMax99)

The earth is divided into a flat 500m × 500m grid: 82,000 columns × 42,000 rows = ~3.4 billion cells. Any lat/lng maps to exactly one cell ID:

import math

EARTH_W   = 40_075_000  # metres
EARTH_H   = 20_000_000
CELL_SIZE = 500
NUM_ROWS  = 42_000
NUM_COLS  = 82_000

def geo_to_cell(lat, lng):
    x = (lng + 180) * (EARTH_W / 360)
    y = EARTH_H / 2 - math.log(
        math.tan(math.pi / 4 + (lat * math.pi) / 360)
    ) * (EARTH_H / (2 * math.pi))

    x_cell = max(0, min(int(x / CELL_SIZE), NUM_COLS - 1))
    y_cell = max(0, min(int(y / CELL_SIZE), NUM_ROWS - 1))

    return x_cell * NUM_ROWS + y_cell

# Example: Algiers
cell = geo_to_cell(36.737, 3.086)
print(cell)  # → 1711935606
Enter fullscreen mode Exit fullscreen mode

Cells are grouped into lobbies (41×41 cells ≈ 20km² each). Subscribing to a lobby ID means you get all components within that area — no bounding-box query needed, no spatial index on a server.

LOBBY_SIZE     = 41
NUM_LOBBY_ROWS = 1025  # ceil(42000 / 41)

def cell_to_lobby(cell_id):
    x_cell = cell_id // NUM_ROWS
    y_cell = cell_id % NUM_ROWS
    lx = x_cell // LOBBY_SIZE
    ly = y_cell // LOBBY_SIZE
    return lx * NUM_LOBBY_ROWS + ly
Enter fullscreen mode Exit fullscreen mode

Layer 2 — Nostr (NIP-01 + NIP-33)

Every component presence is a kind:30420 replaceable event (NIP-33). One pubkey = one component. Updating your presence replaces the old event on the relay — no accumulation of stale data.

The event tags that matter:

[
  ["d",     "main"],
  ["t",     "difp"],
  ["t",     "s"],
  ["cell",  "1711935606"],
  ["lobby", "40780"],
  ["t",     "lobby-40780"],
  ["status","1"]
]
Enter fullscreen mode Exit fullscreen mode

The #t: ["lobby-40780"] tag is what makes geo-discovery possible. Any client can subscribe to a lobby ID with a standard Nostr REQ — no custom relay extensions, no spatial query language. It's just tag filtering.

Layer 3 — Catalog encoding

Product listings, ask lists, and donations are separate kind:30421 events with a compact payload:

Listing:  1:29900;6:14900;21:9900    (productId:priceCents pairs)
Ask:      3;7;44                      (productIds)
Donation: 2;15;88                     (productIds)
Enter fullscreen mode Exit fullscreen mode

This keeps event sizes tiny and fits thousands of products in a single Nostr event.


Query the network right now — Python script

You don't need the map UI at all. Here's a minimal Python script to query any cell and print what's there:

import json
import websocket

# ── DIFP grid constants ─────────────────────────────────────
NUM_ROWS       = 42_000
LOBBY_SIZE     = 41
NUM_LOBBY_ROWS = 1_025

def cell_to_lobby(cell_id):
    x_cell = cell_id // NUM_ROWS
    y_cell = cell_id % NUM_ROWS
    lx = x_cell // LOBBY_SIZE
    ly = y_cell // LOBBY_SIZE
    return lx * NUM_LOBBY_ROWS + ly

# ── Input ───────────────────────────────────────────────────
cell_id  = int(input("Enter DIFP Cell ID: ").strip())
lobby_id = cell_to_lobby(cell_id)

print(f"\nCell ID  : {cell_id}")
print(f"Lobby ID : {lobby_id}")
print("Connecting to relay...\n")

# ── Connect & subscribe ─────────────────────────────────────
ws = websocket.create_connection("wss://relay.damus.io")

req = ["REQ", "difp-cell-query", {
    "kinds": [30420],
    "#t":   [f"lobby-{lobby_id}"],
    "limit": 500
}]

ws.send(json.dumps(req))

# ── Read results ────────────────────────────────────────────
found = 0

while True:
    msg = json.loads(ws.recv())

    if msg[0] == "EVENT":
        event     = msg[2]
        event_cell = next(
            (tag[1] for tag in event.get("tags", []) if tag[0] == "cell"),
            None
        )

        # Filter to exact cell match
        if event_cell != str(cell_id):
            continue

        found += 1
        content = {}
        try:
            content = json.loads(event["content"])
        except Exception:
            pass

        print("=" * 60)
        print(f"COMPONENT #{found}")
        print("=" * 60)
        print(f"PubKey : {event['pubkey']}")
        print(f"Name   : {content.get('n',  'Unknown')}")
        print(f"Type   : {content.get('cT', '?')}")
        print(f"Avatar : {content.get('aI', '?')}")
        print(f"Cell   : {event_cell}")

        listings = [t[1] for t in event.get("tags", []) if t[0] == "listing"]
        catalogs = [t[1] for t in event.get("tags", []) if t[0] == "catalog"]
        print(f"Listings : {listings}")
        print(f"Catalogs : {catalogs}")
        print()

    elif msg[0] == "EOSE":
        break

ws.close()
print(f"Done. Components found in cell {cell_id}: {found}")
Enter fullscreen mode Exit fullscreen mode

Install the dependency:

pip install websocket-client
Enter fullscreen mode Exit fullscreen mode

Run it:

python query_cell.py
# Enter DIFP Cell ID: 1711935606
Enter fullscreen mode Exit fullscreen mode

You'll see every registered component at that 500m cell — their name, type, avatar, and any catalog/listing data — pulled directly from the Nostr relay, no API key, no rate limit, no account.

Try it with Cell ID 1711935606 (Algiers) or drop your own pin on the map first, grab your Cell ID from the confirmation toast, then query it here.


Why this is interesting for AI

One of the things we're actively experimenting with: querying the DIFP network from an AI agent.

The geo-cell model maps cleanly to natural language queries:

  • "Find me a farm within 5km of these coordinates" → convert to cell → find nearby lobbies → subscribe → filter by type f
  • "What food donations are available near me right now?" → same pipeline, filter by donation events

The protocol is just Nostr tag filtering. Any model that can make a WebSocket connection and parse JSON can become a food discovery agent on the open network. No API contract. No terms of service. No SDK required.

We're planning to publish a reference agent implementation in the next sprint.


What's coming next

Three-way trades

The current version supports three independent broadcast types per component: listing (sell), ask (need), and donation (give). The next update will connect these across components — enabling matched trades:

  • A farmer broadcasts surplus tomatoes as a listing
  • A restaurant broadcasts tomatoes as an ask
  • The protocol surfaces the match
  • Both parties transact directly, peer-to-peer

No escrow platform. No commission. Just two signed events and a handshake.

Trade on Map

We're also prototyping a new concept: Trade on Map. Instead of navigating to a store profile and browsing a catalog, you see the trades themselves as map objects — color-coded by type (listing / ask / donation), filterable by product category, visible in real time as they're published to the relay.

Think of it as a live order book, but geographic. You can see at a glance where surpluses and shortfalls are clustering, and what's moving.

This is still early-stage. We're designing the UX now. If you have thoughts, we'd love to hear them.


Try it / get involved

The map is live: djowda.com/food-freedom-map

Drop your pin. Try the Python script. Share your Cell ID in the comments — we'll tell you what's in your neighborhood on the network.

We're aiming for 1,000,000 pins as a signal of demand for open food infrastructure. Not a vanity metric — a message to the food system that people are ready for something better.

The npm library if you want to build on the protocol:

npm install @djowda/difp
Enter fullscreen mode Exit fullscreen mode

npmjs.com/package/@djowda/difp

The protocol is open. The relay is public. Build something. 🌾


Part of the Djowda open food infrastructure project.
Protocol spec: DIFP v0.4-alpha | Built on Nostr NIP-01 + NIP-33

Top comments (0)