diff --git a/README.md b/README.md index 1c8f00c..3a99e21 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,8 @@ import tkintermapview Create the standard tkinter window and place a TkinterMapView in the middle of the window. The first argument must be the widgets master, then you specify the `width`, `height` and `corner_radius` -of the widget. +of the widget. The `user_agent` argument must be a string that identifies your application, as explained +in the [Nominatim Usage Policy](https://operations.osmfoundation.org/policies/nominatim/). ```python # create tkinter window root_tk = tkinter.Tk() @@ -70,7 +71,8 @@ root_tk.geometry(f"{800}x{600}") root_tk.title("map_view_example.py") # create map widget -map_widget = tkintermapview.TkinterMapView(root_tk, width=800, height=600, corner_radius=0) +map_widget = tkintermapview.TkinterMapView(root_tk, width=800, height=600, corner_radius=0, + user_agent="Example/1.0") map_widget.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER) ``` If you also call `root_tk.mainloop()` at the end, this is already a fully working example to test the map widget. diff --git a/examples/load_offline_tiles.py b/examples/load_offline_tiles.py index c20203e..7f2f147 100644 --- a/examples/load_offline_tiles.py +++ b/examples/load_offline_tiles.py @@ -1,5 +1,5 @@ -import tkintermapview import os +import tkintermapview # This scripts creates a database with offline tiles. @@ -15,7 +15,8 @@ database_path = os.path.join(script_directory, "offline_tiles_nyc.db") # create OfflineLoader instance -loader = tkintermapview.OfflineLoader(path=database_path) +loader = tkintermapview.OfflineLoader(path=database_path, + user_agent="TkinterMapViewLoadOfflineTiles/{tkintermapview.__version__}") # save the tiles to the database, an existing database will extended loader.save_offline_tiles(top_left_position, bottom_right_position, zoom_min, zoom_max) diff --git a/examples/map_view_demo.py b/examples/map_view_demo.py index 2fe8fe5..1c96064 100644 --- a/examples/map_view_demo.py +++ b/examples/map_view_demo.py @@ -1,12 +1,13 @@ import sys import tkinter import tkinter.messagebox -from tkintermapview import TkinterMapView +import tkintermapview class App(tkinter.Tk): APP_NAME = "map_view_demo.py" + USER_AGENT = f"TkinterMapViewDemo/{tkintermapview.__version__}" WIDTH = 800 HEIGHT = 750 @@ -38,7 +39,7 @@ def __init__(self, *args, **kwargs): self.search_bar_clear = tkinter.Button(master=self, width=8, text="Clear", command=self.clear) self.search_bar_clear.grid(row=0, column=2, pady=10, padx=10) - self.map_widget = TkinterMapView(width=self.WIDTH, height=600, corner_radius=0) + self.map_widget = tkintermapview.TkinterMapView(width=self.WIDTH, height=600, corner_radius=0, user_agent=self.USER_AGENT) self.map_widget.grid(row=1, column=0, columnspan=3, sticky="nsew") self.marker_list_box = tkinter.Listbox(self, height=8) diff --git a/examples/map_view_marker_example.py b/examples/map_view_marker_example.py index 2f9d2ff..41ccf52 100644 --- a/examples/map_view_marker_example.py +++ b/examples/map_view_marker_example.py @@ -1,15 +1,16 @@ -from PIL import Image, ImageTk -import tkinter import os -from tkintermapview import TkinterMapView +import tkinter +import tkintermapview +from PIL import Image, ImageTk # create tkinter window root_tk = tkinter.Tk() root_tk.geometry(f"{1000}x{700}") -root_tk.title("map_view_simple_example.py") +root_tk.title("map_view_marker_example.py") # create map widget -map_widget = TkinterMapView(root_tk, width=1000, height=700, corner_radius=0) +map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0, + user_agent=f"TkinterMapViewMarkerExample/{tkintermapview.__version__}") map_widget.pack(fill="both", expand=True) # load images in PhotoImage object diff --git a/examples/map_view_marker_icon_images.py b/examples/map_view_marker_icon_images.py index 2407f7a..dde4ca1 100644 --- a/examples/map_view_marker_icon_images.py +++ b/examples/map_view_marker_icon_images.py @@ -6,10 +6,11 @@ # create tkinter window root_tk = tkinter.Tk() root_tk.geometry(f"{1000}x{700}") -root_tk.title("map_view_simple_example.py") +root_tk.title("map_view_marker_icon_images.py") # create map widget -map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0) +map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0, + user_agent=f"TkinterMapViewMarkerIconImages/{tkintermapview.__version__}") map_widget.pack(fill="both", expand=True) # load images diff --git a/examples/map_view_polygon_example.py b/examples/map_view_polygon_example.py index 10e41c9..bbc17b8 100644 --- a/examples/map_view_polygon_example.py +++ b/examples/map_view_polygon_example.py @@ -7,7 +7,8 @@ root_tk.title("map_view_polygon_example.py") # create map widget -map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0) +map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0, + user_agent=f"TkinterMapViewPolygonExample/{tkintermapview.__version__}") map_widget.pack(fill="both", expand=True) diff --git a/examples/map_view_simple_example.py b/examples/map_view_simple_example.py index f72ad12..89bb4a1 100644 --- a/examples/map_view_simple_example.py +++ b/examples/map_view_simple_example.py @@ -7,7 +7,8 @@ root_tk.title("map_view_simple_example.py") # create map widget -map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0) +map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0, + user_agent=f"TkinterMapViewSimpleExample/{tkintermapview.__version__}") map_widget.pack(fill="both", expand=True) # set other tile server (standard is OpenStreetMap) diff --git a/examples/map_with_customtkinter.py b/examples/map_with_customtkinter.py index e3b5a78..8529453 100644 --- a/examples/map_with_customtkinter.py +++ b/examples/map_with_customtkinter.py @@ -1,5 +1,5 @@ import customtkinter -from tkintermapview import TkinterMapView +import tkintermapview customtkinter.set_default_color_theme("blue") @@ -7,6 +7,7 @@ class App(customtkinter.CTk): APP_NAME = "TkinterMapView with CustomTkinter" + USER_AGENT = f"TkinterMapViewWithCustomTkinter/{tkintermapview.__version__}" WIDTH = 800 HEIGHT = 500 @@ -70,7 +71,7 @@ def __init__(self, *args, **kwargs): self.frame_right.grid_columnconfigure(1, weight=0) self.frame_right.grid_columnconfigure(2, weight=1) - self.map_widget = TkinterMapView(self.frame_right, corner_radius=0) + self.map_widget = tkintermapview.TkinterMapView(self.frame_right, corner_radius=0, user_agent=self.USER_AGENT) self.map_widget.grid(row=1, rowspan=1, column=0, columnspan=3, sticky="nswe", padx=(0, 0), pady=(0, 0)) self.entry = customtkinter.CTkEntry(master=self.frame_right, diff --git a/examples/map_with_offline_tiles.py b/examples/map_with_offline_tiles.py index 352d0ff..f87a599 100644 --- a/examples/map_with_offline_tiles.py +++ b/examples/map_with_offline_tiles.py @@ -1,19 +1,19 @@ -import tkinter import os -from tkintermapview import TkinterMapView +import tkinter +import tkintermapview # create tkinter window root_tk = tkinter.Tk() root_tk.geometry(f"{1000}x{700}") -root_tk.title("map_view_simple_example.py") +root_tk.title("map_with_offline_tiles.py") # path for the database to use script_directory = os.path.dirname(os.path.abspath(__file__)) database_path = os.path.join(script_directory, "offline_tiles_nyc.db") # create map widget and only use the tiles from the database, not the online server (use_database_only=True) -map_widget = TkinterMapView(root_tk, width=1000, height=700, corner_radius=0, use_database_only=True, - max_zoom=17, database_path=database_path) +map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0, + database_path=database_path, use_database_only=True, max_zoom=17) map_widget.pack(fill="both", expand=True) map_widget.set_address("nyc") diff --git a/tkintermapview/map_widget.py b/tkintermapview/map_widget.py index b1f1780..8f27d50 100644 --- a/tkintermapview/map_widget.py +++ b/tkintermapview/map_widget.py @@ -32,6 +32,7 @@ def __init__(self, *args, database_path: str = None, use_database_only: bool = False, max_zoom: int = 19, + user_agent: str = None, **kwargs): super().__init__(*args, **kwargs) @@ -131,6 +132,9 @@ def __init__(self, *args, self.max_zoom = max_zoom # should be set according to tile server max zoom self.min_zoom: int = math.ceil(math.log2(math.ceil(self.width / self.tile_size))) # min zoom at which map completely fills widget + # user agent + self.user_agent = user_agent + # pre caching for smoother movements (load tile images into cache at a certain radius around the pre_cache_position) self.pre_cache_position: Union[Tuple[float, float], None] = None self.pre_cache_thread = threading.Thread(daemon=True, target=self.pre_cache) @@ -330,7 +334,7 @@ def set_address(self, address_string: str, marker: bool = False, text: str = Non """ Function uses geocode service of OpenStreetMap (Nominatim). https://geocoder.readthedocs.io/providers/OpenStreetMap.html """ - result = geocoder.osm(address_string) + result = geocoder.osm(address_string, headers={"User-Agent": self.user_agent}) if result.ok: @@ -492,11 +496,11 @@ def request_image(self, zoom: int, x: int, y: int, db_cursor=None) -> ImageTk.Ph # try to get the tile from the server try: url = self.tile_server.replace("{x}", str(x)).replace("{y}", str(y)).replace("{z}", str(zoom)) - image = Image.open(requests.get(url, stream=True, headers={"User-Agent": "TkinterMapView"}).raw) + image = Image.open(requests.get(url, stream=True, headers={"User-Agent": self.user_agent}).raw) if self.overlay_tile_server is not None: url = self.overlay_tile_server.replace("{x}", str(x)).replace("{y}", str(y)).replace("{z}", str(zoom)) - image_overlay = Image.open(requests.get(url, stream=True, headers={"User-Agent": "TkinterMapView"}).raw) + image_overlay = Image.open(requests.get(url, stream=True, headers={"User-Agent": self.user_agent}).raw) image = image.convert("RGBA") image_overlay = image_overlay.convert("RGBA") diff --git a/tkintermapview/offline_loading.py b/tkintermapview/offline_loading.py index e508854..b8bcbee 100644 --- a/tkintermapview/offline_loading.py +++ b/tkintermapview/offline_loading.py @@ -11,7 +11,7 @@ class OfflineLoader: - def __init__(self, path=None, tile_server=None, max_zoom=19): + def __init__(self, path=None, tile_server=None, max_zoom=19, user_agent=None): if path is None: self.db_path = os.path.join(os.path.abspath(os.getcwd()), "offline_tiles.db") else: @@ -24,6 +24,8 @@ def __init__(self, path=None, tile_server=None, max_zoom=19): self.max_zoom = max_zoom + self.user_agent = user_agent + self.task_queue = [] self.result_queue = [] self.thread_pool = [] @@ -68,7 +70,7 @@ def save_offline_tiles_thread(self): try: url = self.tile_server.replace("{x}", str(x)).replace("{y}", str(y)).replace("{z}", str(zoom)) - image_data = requests.get(url, stream=True, headers={"User-Agent": "TkinterMapView"}).content + image_data = requests.get(url, stream=True, headers={"User-Agent": self.user_agent}).content self.lock.acquire() self.result_queue.append((zoom, x, y, self.tile_server, image_data))