I'm running a Streamlit app where I try to retrieve the user's geolocation in streamlit. However, when using geocoder.ip("me"), the coordinates returned are 45, -121, which point to Oregon, USA, rather than my actual location.
This is the function I use:
def get_lat_lon():
# Use geocoder to get the location based on IP
g = geocoder.ip('me')
if g.ok:
lat = g.latlng[0] # Latitude
lon = g.latlng[1] # Longitude
return lat, lon
else:
st.error("Could not retrieve location from IP address.")
return None, None
I would like to find a solution that can work in an streamlit app so by clicking a st.button
I can call a function that retrieves my lat, long.
Consider the following simple Streamlit app:
import streamlit as st
import requests
import geocoder
from typing import Optional, Tuple
def get_location_geocoder() -> Tuple[Optional[float], Optional[float]]:
"""
Get location using geocoder library
"""
g = geocoder.ip('me')
if g.ok:
return g.latlng[0], g.latlng[1]
return None, None
def get_location_ipapi() -> Tuple[Optional[float], Optional[float]]:
"""
Fallback method using ipapi.co service
"""
try:
response = requests.get('https://ipapi.co/json/')
if response.status_code == 200:
data = response.json()
lat = data.get('latitude')
lon = data.get('longitude')
if lat is not None and lon is not None:
# Store additional location data in session state
st.session_state.location_data = {
'city': data.get('city'),
'region': data.get('region'),
'country': data.get('country_name'),
'ip': data.get('ip')
}
return lat, lon
except requests.RequestException as e:
st.error(f"Error retrieving location from ipapi.co: {str(e)}")
return None, None
def get_location() -> Tuple[Optional[float], Optional[float]]:
"""
Tries to get location first using geocoder, then falls back to ipapi.co
"""
# Try geocoder first
lat, lon = get_location_geocoder()
# If geocoder fails, try ipapi
if lat is None:
st.info("Primary geolocation method unsuccessful, trying alternative...")
lat, lon = get_location_ipapi()
return lat, lon
def show_location_details():
"""
Displays the additional location details if available
"""
if 'location_data' in st.session_state:
data = st.session_state.location_data
st.write("Location Details:")
col1, col2 = st.columns(2)
with col1:
st.write("📍 City:", data['city'])
st.write("🏘️ Region:", data['region'])
with col2:
st.write("🌍 Country:", data['country'])
st.write("🔍 IP:", data['ip'])
def main():
st.title("IP Geolocation Demo")
st.write("This app will attempt to detect your location using IP geolocation.")
if st.button("Get My Location", type="primary"):
with st.spinner("Retrieving your location..."):
lat, lon = get_location()
if lat is not None and lon is not None:
st.success("Location retrieved successfully!")
# Create two columns for coordinates
col1, col2 = st.columns(2)
with col1:
st.metric("Latitude", f"{lat:.4f}")
with col2:
st.metric("Longitude", f"{lon:.4f}")
# Show additional location details if available
show_location_details()
# Display location on a map
st.write("📍 Location on Map:")
st.map(data={'lat': [lat], 'lon': [lon]}, zoom=10)
else:
st.error("Could not determine your location. Please check your internet connection and try again.")
if __name__ == "__main__":
main()
The app above allows users to click a button to generate their location using the geocoder
library. I added a back up in case geocoder
doesn't work as expected, using the ipapi.co api, it has been proven to be more accurate at times. There will be some limitations with this approach, it will be less accurate relative to browser geolocation API alternatives. Nevertheless it works on all devices and doesn't require permissions, whereas the browser geolocation api version will. Below are some pictures of what the app from above looks like. The geolocation button:
And the resulting output:
Depending on how exact the location requirements are it might be worth exploring the browser geolocation api alternative.