Python GUI (Tkinter + tkintermapview)¶
This guide shows how to build a desktop Python application with an interactive map and Yoro code support using tkintermapview — a pure Python map widget.
What you'll build¶
A standalone desktop app where users can:
- View a map with different tile providers
- Click to encode a location → see the Yoro code
- Enter a code → jump to that location
- See the cell bounds drawn on the map
Install¶
No Django or web server needed — this is a pure desktop application.
Complete Application¶
"""
Yoro Desktop Map — standalone GUI application.
Click on the map to get a Yoro code, or enter a code to jump to its location.
"""
import tkinter as tk
from tkinter import ttk
import yoro
try:
from tkintermapview import TkinterMapView
except ImportError:
print("Install tkintermapview: pip install tkintermapview")
raise
class YoroMapApp:
def __init__(self, root):
self.root = root
self.root.title("Yoro — Geographic Addressing")
self.root.geometry("1000x700")
self.domain = "CI"
self.precision = 12
self.current_marker = None
self.current_polygon = None
self._build_ui()
def _build_ui(self):
# Top bar
top = ttk.Frame(self.root, padding=8)
top.pack(fill="x")
# Domain selector
ttk.Label(top, text="Domain:").pack(side="left")
self.domain_var = tk.StringVar(value="CI")
domain_combo = ttk.Combobox(
top, textvariable=self.domain_var,
values=sorted(yoro.DOMAINS.keys()), width=5, state="readonly"
)
domain_combo.pack(side="left", padx=(4, 16))
domain_combo.bind("<<ComboboxSelected>>", lambda e: self._update_domain())
# Precision selector
ttk.Label(top, text="Precision:").pack(side="left")
levels = yoro.precision_levels(domain=self.domain)
precision_values = [f"p={lv['precision']} ({lv['code_length']} chars, ~{lv['resolution_m']:.0f}m)" for lv in levels]
self.precision_var = tk.StringVar(value=precision_values[4]) # default p=12
precision_combo = ttk.Combobox(
top, textvariable=self.precision_var,
values=precision_values, width=25, state="readonly"
)
precision_combo.pack(side="left", padx=(4, 16))
precision_combo.bind("<<ComboboxSelected>>", lambda e: self._update_precision())
self.precision_values = precision_values
self.precision_levels = levels
# Code input
ttk.Label(top, text="Code:").pack(side="left")
self.code_var = tk.StringVar()
code_entry = ttk.Entry(top, textvariable=self.code_var, width=15, font=("Courier", 12))
code_entry.pack(side="left", padx=4)
code_entry.bind("<Return>", lambda e: self._decode_code())
ttk.Button(top, text="Go", command=self._decode_code).pack(side="left")
# Map widget
self.map_widget = TkinterMapView(self.root, corner_radius=0)
self.map_widget.pack(fill="both", expand=True)
self.map_widget.set_position(7.5, -5.5) # Cote d'Ivoire
self.map_widget.set_zoom(7)
# Click handler
self.map_widget.add_right_click_menu_command(
"Get Yoro Code", self._on_map_click, pass_coords=True
)
# Info bar
self.info_var = tk.StringVar(value="Right-click on the map to get a Yoro code")
ttk.Label(self.root, textvariable=self.info_var, padding=8,
font=("Courier", 11)).pack(fill="x")
def _update_domain(self):
self.domain = self.domain_var.get()
# Update precision options for this domain
levels = yoro.precision_levels(domain=self.domain)
self.precision_levels = levels
def _update_precision(self):
idx = self.precision_values.index(self.precision_var.get())
self.precision = self.precision_levels[idx]["precision"]
def _on_map_click(self, coords):
lat, lon = coords
try:
code = yoro.encode(lat, lon, precision=self.precision, domain=self.domain)
decoded = yoro.decode(code)
res = yoro.resolution(decoded["precision"], domain=self.domain)
self.code_var.set(code)
self.info_var.set(
f"{code} | {decoded['lat']:.6f}, {decoded['lon']:.6f} | "
f"~{res:.1f}m | p={decoded['precision']}"
)
self._draw_cell(decoded)
except ValueError as e:
self.info_var.set(f"Error: {e}")
def _decode_code(self):
code = self.code_var.get().strip()
if not code:
return
try:
decoded = yoro.decode(code)
res = yoro.resolution(decoded["precision"], domain=decoded["domain"])
self.info_var.set(
f"{code.upper()} | {decoded['lat']:.6f}, {decoded['lon']:.6f} | "
f"~{res:.1f}m | p={decoded['precision']}"
)
self.map_widget.set_position(decoded["lat"], decoded["lon"])
self.map_widget.set_zoom(15)
self._draw_cell(decoded)
except ValueError as e:
self.info_var.set(f"Error: {e}")
def _draw_cell(self, decoded):
# Remove previous marker/polygon
if self.current_marker:
self.current_marker.delete()
if self.current_polygon:
self.current_polygon.delete()
b = decoded["bounds"]
# Draw cell rectangle
self.current_polygon = self.map_widget.set_polygon(
[(b["lat_min"], b["lon_min"]),
(b["lat_min"], b["lon_max"]),
(b["lat_max"], b["lon_max"]),
(b["lat_max"], b["lon_min"])],
outline_color="red",
fill_color=None,
)
# Place marker at center
self.current_marker = self.map_widget.set_marker(
decoded["lat"], decoded["lon"],
text=decoded.get("domain", "") + "-" + str(decoded.get("precision", ""))
)
if __name__ == "__main__":
root = tk.Tk()
app = YoroMapApp(root)
root.mainloop()
Running¶
Right-click on the map to encode a location. Use the top bar to change domain or precision. Type a code and press Enter to jump to it.
Adapting for Other GUI Frameworks¶
PyQt / PySide¶
Replace tkintermapview with QtWebEngineWidgets embedding a Leaflet map:
from PySide6.QtWebEngineWidgets import QWebEngineView
class MapWidget(QWebEngineView):
def __init__(self):
super().__init__()
# Load Leaflet HTML template
self.setHtml(LEAFLET_HTML)
def encode_position(self, lat, lon):
code = yoro.encode(lat, lon, domain="CI")
# Call JavaScript to update the map
self.page().runJavaScript(f"showCode('{code}')")
Streamlit¶
import streamlit as st
import folium
from streamlit_folium import st_folium
import yoro
st.title("Yoro Code Explorer")
col1, col2 = st.columns([3, 1])
with col2:
domain = st.selectbox("Domain", sorted(yoro.DOMAINS.keys()), index=list(sorted(yoro.DOMAINS.keys())).index("CI"))
code_input = st.text_input("Decode a code", placeholder="CI-PV9XD")
precision = st.select_slider("Precision", options=[lv["precision"] for lv in yoro.precision_levels(domain=domain)], value=12)
with col1:
m = folium.Map(location=[7.5, -5.5], zoom_start=7)
if code_input:
try:
decoded = yoro.decode(code_input)
b = decoded["bounds"]
folium.Rectangle(
bounds=[[b["lat_min"], b["lon_min"]], [b["lat_max"], b["lon_max"]]],
color="red", weight=2, fill=True, fill_opacity=0.1,
).add_to(m)
folium.Marker([decoded["lat"], decoded["lon"]], popup=code_input).add_to(m)
m.location = [decoded["lat"], decoded["lon"]]
m.zoom_start = 14
except ValueError as e:
st.error(str(e))
result = st_folium(m, height=500)
if result and result.get("last_clicked"):
lat = result["last_clicked"]["lat"]
lon = result["last_clicked"]["lng"]
code = yoro.encode(lat, lon, precision=precision, domain=domain)
st.success(f"**{code}**")
decoded = yoro.decode(code)
res = yoro.resolution(decoded["precision"], domain=domain)
st.caption(f"{decoded['lat']:.6f}, {decoded['lon']:.6f} — ~{res:.1f}m resolution")
All approaches work offline
Since yoro.encode() and yoro.decode() are pure math with zero dependencies, they work in any Python environment — desktop, mobile (Kivy/BeeWare), embedded, even WebAssembly (via Pyodide).