Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import tkinter as tk
- from tkinter import ttk, messagebox
- import urllib.request
- import urllib.parse
- import urllib.error
- import json
- import math
- # =============================================================================
- # Helper Function: Haversine Formula
- # =============================================================================
- def haversine(lat1, lon1, lat2, lon2):
- """
- Calculate the great-circle distance between two points on Earth (in kilometers).
- """
- R = 6371.0 # Radius of the Earth in kilometers
- phi1 = math.radians(lat1)
- phi2 = math.radians(lat2)
- delta_phi = math.radians(lat2 - lat1)
- delta_lambda = math.radians(lon2 - lon1)
- a = math.sin(delta_phi/2)**2 + \
- math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda/2)**2
- c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
- distance = R * c
- return distance
- # =============================================================================
- # Data Source Base Class & Overpass Implementation Using urllib
- # =============================================================================
- class PlaceDataSource:
- """
- Abstract interface for a place data source.
- """
- def get_restaurants(self, lat, lon, radius_m):
- raise NotImplementedError
- def get_hotels(self, lat, lon, radius_m):
- raise NotImplementedError
- class OverpassSource(PlaceDataSource):
- """
- Data source using the OpenStreetMap Overpass API.
- This queries for restaurants (amenity=restaurant) and hotels (tourism=hotel).
- """
- def __init__(self):
- self.api_url = "http://overpass-api.de/api/interpreter"
- def query_overpass(self, query):
- """
- Submit a query to the Overpass API using urllib and return the JSON result.
- """
- data = urllib.parse.urlencode({'data': query}).encode('utf-8')
- req = urllib.request.Request(self.api_url, data=data, method="POST")
- try:
- with urllib.request.urlopen(req) as response:
- # Check that we received a successful status code (200)
- if response.getcode() == 200:
- response_data = response.read().decode('utf-8')
- return json.loads(response_data)
- else:
- raise Exception(f"Overpass API error: {response.getcode()}")
- except urllib.error.URLError as e:
- raise Exception(f"Overpass API error: {e}")
- def get_restaurants(self, lat, lon, radius_m):
- query = f"""
- [out:json];
- (
- node["amenity"="restaurant"](around:{radius_m},{lat},{lon});
- way["amenity"="restaurant"](around:{radius_m},{lat},{lon});
- relation["amenity"="restaurant"](around:{radius_m},{lat},{lon});
- );
- out center;
- """
- data = self.query_overpass(query)
- return self._parse_elements(data)
- def get_hotels(self, lat, lon, radius_m):
- query = f"""
- [out:json];
- (
- node["tourism"="hotel"](around:{radius_m},{lat},{lon});
- way["tourism"="hotel"](around:{radius_m},{lat},{lon});
- relation["tourism"="hotel"](around:{radius_m},{lat},{lon});
- );
- out center;
- """
- data = self.query_overpass(query)
- return self._parse_elements(data)
- def _parse_elements(self, data):
- """
- Parse the returned JSON data to extract a list of places.
- Each place is a dict with name, coordinates, and address (if available).
- """
- places = []
- for element in data.get("elements", []):
- # For ways and relations, get center coordinates; for nodes, use lat/lon directly.
- if element.get("type") in ["way", "relation"]:
- center = element.get("center", {})
- lat_val = center.get("lat")
- lon_val = center.get("lon")
- else:
- lat_val = element.get("lat")
- lon_val = element.get("lon")
- if lat_val is None or lon_val is None:
- continue
- tags = element.get("tags", {})
- name = tags.get("name", "Unknown")
- # Try to assemble an address from available tags.
- address_parts = []
- if "addr:housenumber" in tags:
- address_parts.append(tags["addr:housenumber"])
- if "addr:street" in tags:
- address_parts.append(tags["addr:street"])
- if "addr:city" in tags:
- address_parts.append(tags["addr:city"])
- address = ", ".join(address_parts)
- places.append({
- "name": name,
- "lat": lat_val,
- "lon": lon_val,
- "address": address,
- "tags": tags
- })
- return places
- # =============================================================================
- # GUI Functions
- # =============================================================================
- def update_table(treeview, places):
- """
- Clear the given Treeview and insert rows for each place.
- """
- for row in treeview.get_children():
- treeview.delete(row)
- for place in places:
- treeview.insert("", "end", values=(
- place['name'],
- f"{place['distance']:.2f}",
- place['address']
- ))
- def search_places():
- """
- Called when the Search button is pressed.
- Reads the input, queries data sources for restaurants and hotels,
- processes the data, and updates the GUI tables.
- """
- status_label.config(text="Searching...")
- search_button.config(state="disabled")
- try:
- # Validate and parse inputs.
- lat = float(lat_entry.get())
- lon = float(lon_entry.get())
- distance_km = float(distance_entry.get())
- radius_m = int(distance_km * 1000) # Overpass API expects meters
- # Initialize our data source.
- data_source = OverpassSource()
- # Query for restaurants and hotels.
- restaurants = data_source.get_restaurants(lat, lon, radius_m)
- hotels = data_source.get_hotels(lat, lon, radius_m)
- # Compute distance from the input coordinate for each result.
- for place in restaurants:
- place['distance'] = haversine(lat, lon, place['lat'], place['lon'])
- for place in hotels:
- place['distance'] = haversine(lat, lon, place['lat'], place['lon'])
- # Sort by distance and select the top 20 results.
- restaurants = sorted(restaurants, key=lambda x: x['distance'])[:20]
- hotels = sorted(hotels, key=lambda x: x['distance'])[:20]
- # Update the tables in the GUI.
- update_table(restaurant_treeview, restaurants)
- update_table(hotel_treeview, hotels)
- status_label.config(text="Search complete.")
- except Exception as e:
- messagebox.showerror("Error", str(e))
- status_label.config(text="Error during search.")
- finally:
- search_button.config(state="normal")
- # =============================================================================
- # Build the GUI
- # =============================================================================
- root = tk.Tk()
- root.title("Nearby Places Finder")
- # --- Input Frame ---
- input_frame = ttk.Frame(root, padding="10")
- input_frame.pack(pady=10, padx=10, fill="x")
- ttk.Label(input_frame, text="Latitude:").grid(row=0, column=0, sticky="e", padx=5, pady=5)
- lat_entry = ttk.Entry(input_frame, width=20)
- lat_entry.grid(row=0, column=1, padx=5, pady=5)
- ttk.Label(input_frame, text="Longitude:").grid(row=1, column=0, sticky="e", padx=5, pady=5)
- lon_entry = ttk.Entry(input_frame, width=20)
- lon_entry.grid(row=1, column=1, padx=5, pady=5)
- ttk.Label(input_frame, text="Distance (km):").grid(row=2, column=0, sticky="e", padx=5, pady=5)
- distance_entry = ttk.Entry(input_frame, width=20)
- distance_entry.grid(row=2, column=1, padx=5, pady=5)
- search_button = ttk.Button(input_frame, text="Search", command=search_places)
- search_button.grid(row=3, column=0, columnspan=2, pady=10)
- # --- Status Label ---
- status_label = ttk.Label(root, text="Ready", relief="sunken", anchor="w")
- status_label.pack(fill="x", padx=10, pady=(0, 10))
- # --- Results Frame (Two Tables) ---
- results_frame = ttk.Frame(root, padding="10")
- results_frame.pack(padx=10, pady=10, fill="both", expand=True)
- # Frame for Restaurants
- restaurants_frame = ttk.Labelframe(results_frame, text="Restaurants", padding="5")
- restaurants_frame.pack(side="left", fill="both", expand=True, padx=5)
- restaurant_treeview = ttk.Treeview(
- restaurants_frame,
- columns=("Name", "Distance (km)", "Address"),
- show="headings",
- selectmode="browse"
- )
- restaurant_treeview.heading("Name", text="Name")
- restaurant_treeview.heading("Distance (km)", text="Distance (km)")
- restaurant_treeview.heading("Address", text="Address")
- restaurant_treeview.column("Name", width=150)
- restaurant_treeview.column("Distance (km)", width=100, anchor="center")
- restaurant_treeview.column("Address", width=200)
- restaurant_treeview.pack(fill="both", expand=True, side="left")
- # Vertical scrollbar for restaurants table.
- restaurant_scrollbar = ttk.Scrollbar(restaurants_frame, orient="vertical", command=restaurant_treeview.yview)
- restaurant_treeview.configure(yscrollcommand=restaurant_scrollbar.set)
- restaurant_scrollbar.pack(side="right", fill="y")
- # Frame for Hotels
- hotels_frame = ttk.Labelframe(results_frame, text="Hotels", padding="5")
- hotels_frame.pack(side="right", fill="both", expand=True, padx=5)
- hotel_treeview = ttk.Treeview(
- hotels_frame,
- columns=("Name", "Distance (km)", "Address"),
- show="headings",
- selectmode="browse"
- )
- hotel_treeview.heading("Name", text="Name")
- hotel_treeview.heading("Distance (km)", text="Distance (km)")
- hotel_treeview.heading("Address", text="Address")
- hotel_treeview.column("Name", width=150)
- hotel_treeview.column("Distance (km)", width=100, anchor="center")
- hotel_treeview.column("Address", width=200)
- hotel_treeview.pack(fill="both", expand=True, side="left")
- # Vertical scrollbar for hotels table.
- hotel_scrollbar = ttk.Scrollbar(hotels_frame, orient="vertical", command=hotel_treeview.yview)
- hotel_treeview.configure(yscrollcommand=hotel_scrollbar.set)
- hotel_scrollbar.pack(side="right", fill="y")
- # =============================================================================
- # Start the GUI event loop.
- # =============================================================================
- if __name__ == "__main__":
- root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement