From b1938199e508389f469031e92d7b46d4cf9e41d5 Mon Sep 17 00:00:00 2001 From: Anton Filimonov Date: Sat, 10 Feb 2024 15:31:16 +0100 Subject: [PATCH 1/3] infinite horizontal scroll --- tkintermapview/canvas_path.py | 10 +++--- tkintermapview/canvas_polygon.py | 10 +++--- tkintermapview/canvas_position_marker.py | 6 ++-- tkintermapview/map_widget.py | 45 ++++++++++++++---------- tkintermapview/utility_functions.py | 8 +++++ 5 files changed, 48 insertions(+), 31 deletions(-) diff --git a/tkintermapview/canvas_path.py b/tkintermapview/canvas_path.py index d0278af..bbfde21 100644 --- a/tkintermapview/canvas_path.py +++ b/tkintermapview/canvas_path.py @@ -59,8 +59,8 @@ def remove_position(self, deg_x, deg_y): def get_canvas_pos(self, position, widget_tile_width, widget_tile_height): tile_position = decimal_to_osm(*position, round(self.map_widget.zoom)) - canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width - canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height + canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos_bounded[0]) / widget_tile_width) * self.map_widget.width + canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos_bounded[1]) / widget_tile_height) * self.map_widget.height return canvas_pos_x, canvas_pos_y @@ -87,8 +87,8 @@ def draw(self, move=False): widget_tile_height = self.map_widget.lower_right_tile_pos[1] - self.map_widget.upper_left_tile_pos[1] if move is True and self.last_upper_left_tile_pos is not None and new_line_length is False: - x_move = ((self.last_upper_left_tile_pos[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width - y_move = ((self.last_upper_left_tile_pos[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height + x_move = ((self.last_upper_left_tile_pos[0] - self.map_widget.upper_left_tile_pos_bounded[0]) / widget_tile_width) * self.map_widget.width + y_move = ((self.last_upper_left_tile_pos[1] - self.map_widget.upper_left_tile_pos_bounded[1]) / widget_tile_height) * self.map_widget.height for i in range(0, len(self.position_list)* 2, 2): self.canvas_line_positions[i] += x_move @@ -119,5 +119,5 @@ def draw(self, move=False): self.canvas_line = None self.map_widget.manage_z_order() - self.last_upper_left_tile_pos = self.map_widget.upper_left_tile_pos + self.last_upper_left_tile_pos = self.map_widget.upper_left_tile_pos_bounded diff --git a/tkintermapview/canvas_polygon.py b/tkintermapview/canvas_polygon.py index 41065f7..8aafe00 100644 --- a/tkintermapview/canvas_polygon.py +++ b/tkintermapview/canvas_polygon.py @@ -73,8 +73,8 @@ def click(self, event=None): def get_canvas_pos(self, position, widget_tile_width, widget_tile_height): tile_position = decimal_to_osm(*position, round(self.map_widget.zoom)) - canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width - canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height + canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos_bounded[0]) / widget_tile_width) * self.map_widget.width + canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos_bounded[1]) / widget_tile_height) * self.map_widget.height return canvas_pos_x, canvas_pos_y @@ -89,8 +89,8 @@ def draw(self, move=False): # if only moving happened and len(self.position_list) did not change, shift current positions, else calculate new position_list if move is True and self.last_upper_left_tile_pos is not None and new_line_length is False: - x_move = ((self.last_upper_left_tile_pos[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width - y_move = ((self.last_upper_left_tile_pos[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height + x_move = ((self.last_upper_left_tile_pos[0] - self.map_widget.upper_left_tile_pos_bounded[0]) / widget_tile_width) * self.map_widget.width + y_move = ((self.last_upper_left_tile_pos[1] - self.map_widget.upper_left_tile_pos_bounded[1]) / widget_tile_height) * self.map_widget.height for i in range(0, len(self.position_list) * 2, 2): self.canvas_polygon_positions[i] += x_move @@ -127,4 +127,4 @@ def draw(self, move=False): self.canvas_polygon = None self.map_widget.manage_z_order() - self.last_upper_left_tile_pos = self.map_widget.upper_left_tile_pos + self.last_upper_left_tile_pos = self.map_widget.upper_left_tile_pos_bounded diff --git a/tkintermapview/canvas_position_marker.py b/tkintermapview/canvas_position_marker.py index 33228e2..8a0dc3d 100644 --- a/tkintermapview/canvas_position_marker.py +++ b/tkintermapview/canvas_position_marker.py @@ -124,9 +124,9 @@ def get_canvas_pos(self, position): widget_tile_width = self.map_widget.lower_right_tile_pos[0] - self.map_widget.upper_left_tile_pos[0] widget_tile_height = self.map_widget.lower_right_tile_pos[1] - self.map_widget.upper_left_tile_pos[1] - canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width - canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height - + canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos_bounded[0]) / widget_tile_width) * self.map_widget.width + canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos_bounded[1]) / widget_tile_height) * self.map_widget.height + return canvas_pos_x, canvas_pos_y def draw(self, event=None): diff --git a/tkintermapview/map_widget.py b/tkintermapview/map_widget.py index e98616f..a25ff9b 100644 --- a/tkintermapview/map_widget.py +++ b/tkintermapview/map_widget.py @@ -17,7 +17,7 @@ from .canvas_position_marker import CanvasPositionMarker from .canvas_tile import CanvasTile -from .utility_functions import decimal_to_osm, osm_to_decimal +from .utility_functions import decimal_to_osm, osm_to_decimal, unbounded_osm_to_osm from .canvas_button import CanvasButton from .canvas_path import CanvasPath from .canvas_polygon import CanvasPolygon @@ -109,6 +109,7 @@ def __init__(self, *args, # describes the tile layout self.zoom: float = 0 self.upper_left_tile_pos: Tuple[float, float] = (0, 0) # in OSM coords + self.upper_left_tile_pos_bounded: Tuple[float, float] = (0, 0) # in OSM coords self.lower_right_tile_pos: Tuple[float, float] = (0, 0) self.tile_size: int = 256 # in pixel self.last_zoom: float = self.zoom @@ -432,16 +433,21 @@ def pre_cache(self): # pre cache top and bottom row for x in range(self.pre_cache_position[0] - radius, self.pre_cache_position[0] + radius + 1): - if f"{zoom}{x}{self.pre_cache_position[1] + radius}" not in self.tile_image_cache: + tile_x, tile_y = unbounded_osm_to_osm(x, self.pre_cache_position[1] + radius, zoom) + if f"{zoom}{tile_x}{tile_y}" not in self.tile_image_cache: self.request_image(zoom, x, self.pre_cache_position[1] + radius, db_cursor=db_cursor) - if f"{zoom}{x}{self.pre_cache_position[1] - radius}" not in self.tile_image_cache: + tile_x, tile_y = unbounded_osm_to_osm(x, self.pre_cache_position[1] - radius, zoom) + if f"{zoom}{tile_x}{tile_y}" not in self.tile_image_cache: self.request_image(zoom, x, self.pre_cache_position[1] - radius, db_cursor=db_cursor) # pre cache left and right column for y in range(self.pre_cache_position[1] - radius, self.pre_cache_position[1] + radius + 1): - if f"{zoom}{self.pre_cache_position[0] + radius}{y}" not in self.tile_image_cache: + tile_x, tile_y = unbounded_osm_to_osm(self.pre_cache_position[0] + radius, y, zoom) + if f"{zoom}{tile_x}{tile_y}" not in self.tile_image_cache: self.request_image(zoom, self.pre_cache_position[0] + radius, y, db_cursor=db_cursor) - if f"{zoom}{self.pre_cache_position[0] - radius}{y}" not in self.tile_image_cache: + + tile_x, tile_y = unbounded_osm_to_osm(self.pre_cache_position[0] - radius, y, zoom) + if f"{zoom}{tile_x}{tile_y}" not in self.tile_image_cache: self.request_image(zoom, self.pre_cache_position[0] - radius, y, db_cursor=db_cursor) # raise the radius @@ -463,18 +469,18 @@ def pre_cache(self): del self.tile_image_cache[key] def request_image(self, zoom: int, x: int, y: int, db_cursor=None) -> ImageTk.PhotoImage: - + tile_x, tile_y = unbounded_osm_to_osm(x, y, zoom) # if database is available check first if tile is in database, if not try to use server if db_cursor is not None: try: db_cursor.execute("SELECT t.tile_image FROM tiles t WHERE t.zoom=? AND t.x=? AND t.y=? AND t.server=?;", - (zoom, x, y, self.tile_server)) + (zoom, tile_x, tile_y, self.tile_server)) result = db_cursor.fetchone() if result is not None: image = Image.open(io.BytesIO(result[0])) image_tk = ImageTk.PhotoImage(image) - self.tile_image_cache[f"{zoom}{x}{y}"] = image_tk + self.tile_image_cache[f"{zoom}{tile_x}{tile_y}"] = image_tk return image_tk elif self.use_database_only: return self.empty_tile_image @@ -492,11 +498,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)) + url = self.tile_server.replace("{x}", str(tile_x)).replace("{y}", str(tile_y)).replace("{z}", str(zoom)) image = Image.open(requests.get(url, stream=True, headers={"User-Agent": "TkinterMapView"}).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)) + url = self.overlay_tile_server.replace("{x}", str(tile_x)).replace("{y}", str(tile_y)).replace("{z}", str(zoom)) image_overlay = Image.open(requests.get(url, stream=True, headers={"User-Agent": "TkinterMapView"}).raw) image = image.convert("RGBA") image_overlay = image_overlay.convert("RGBA") @@ -511,11 +517,11 @@ def request_image(self, zoom: int, x: int, y: int, db_cursor=None) -> ImageTk.Ph else: return self.empty_tile_image - self.tile_image_cache[f"{zoom}{x}{y}"] = image_tk + self.tile_image_cache[f"{zoom}{tile_x}{tile_y}"] = image_tk return image_tk except PIL.UnidentifiedImageError: # image does not exist for given coordinates - self.tile_image_cache[f"{zoom}{x}{y}"] = self.empty_tile_image + self.tile_image_cache[f"{zoom}{tile_x}{tile_y}"] = self.empty_tile_image return self.empty_tile_image except requests.exceptions.ConnectionError: @@ -525,10 +531,11 @@ def request_image(self, zoom: int, x: int, y: int, db_cursor=None) -> ImageTk.Ph return self.empty_tile_image def get_tile_image_from_cache(self, zoom: int, x: int, y: int): - if f"{zoom}{x}{y}" not in self.tile_image_cache: + tile_x, tile_y = unbounded_osm_to_osm(x, y, zoom) + if f"{zoom}{tile_x}{tile_y}" not in self.tile_image_cache: return False else: - return self.tile_image_cache[f"{zoom}{x}{y}"] + return self.tile_image_cache[f"{zoom}{tile_x}{tile_y}"] def load_images_background(self): if self.database_path is not None: @@ -907,19 +914,21 @@ def mouse_zoom(self, event): def check_map_border_crossing(self): diff_x, diff_y = 0, 0 - if self.upper_left_tile_pos[0] < 0: - diff_x += 0 - self.upper_left_tile_pos[0] + # if self.upper_left_tile_pos[0] < 0: + # diff_x += 0 - self.upper_left_tile_pos[0] + # if self.lower_right_tile_pos[0] > 2 ** round(self.zoom): + # diff_x -= self.lower_right_tile_pos[0] - (2 ** round(self.zoom)) if self.upper_left_tile_pos[1] < 0: diff_y += 0 - self.upper_left_tile_pos[1] - if self.lower_right_tile_pos[0] > 2 ** round(self.zoom): - diff_x -= self.lower_right_tile_pos[0] - (2 ** round(self.zoom)) if self.lower_right_tile_pos[1] > 2 ** round(self.zoom): diff_y -= self.lower_right_tile_pos[1] - (2 ** round(self.zoom)) self.upper_left_tile_pos = self.upper_left_tile_pos[0] + diff_x, self.upper_left_tile_pos[1] + diff_y self.lower_right_tile_pos = self.lower_right_tile_pos[0] + diff_x, self.lower_right_tile_pos[1] + diff_y + self.upper_left_tile_pos_bounded = unbounded_osm_to_osm(self.upper_left_tile_pos[0], self.upper_left_tile_pos[1], self.zoom) + def button_zoom_in(self): # zoom into middle of map self.set_zoom(self.zoom + 1, relative_pointer_x=0.5, relative_pointer_y=0.5) diff --git a/tkintermapview/utility_functions.py b/tkintermapview/utility_functions.py index 78d0390..3e38acf 100644 --- a/tkintermapview/utility_functions.py +++ b/tkintermapview/utility_functions.py @@ -2,6 +2,14 @@ import math from typing import Union +def unbounded_osm_to_osm(tile_x: int, tile_y: int, zoom: int) -> int: + osm_tile_x = tile_x + max_x = int(2.0**zoom) + if osm_tile_x < 0: + osm_tile_x = (max_x - abs(tile_x) % max_x) % max_x + else: + osm_tile_x = tile_x % max_x + return osm_tile_x, tile_y def decimal_to_osm(lat_deg: float, lon_deg: float, zoom: int) -> tuple: """ converts decimal coordinates to internal OSM coordinates""" From a62a67d4e0c4c13a0dda68cd4fe165ea5cbea046 Mon Sep 17 00:00:00 2001 From: Anton Filimonov Date: Sat, 10 Feb 2024 15:46:30 +0100 Subject: [PATCH 2/3] fix canvas coordinates conversion for mouse clicks --- tkintermapview/map_widget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tkintermapview/map_widget.py b/tkintermapview/map_widget.py index a25ff9b..e0a7dd0 100644 --- a/tkintermapview/map_widget.py +++ b/tkintermapview/map_widget.py @@ -210,7 +210,8 @@ def convert_canvas_coords_to_decimal_coords(self, canvas_x: int, canvas_y: int) tile_mouse_x = self.upper_left_tile_pos[0] + (self.lower_right_tile_pos[0] - self.upper_left_tile_pos[0]) * relative_mouse_x tile_mouse_y = self.upper_left_tile_pos[1] + (self.lower_right_tile_pos[1] - self.upper_left_tile_pos[1]) * relative_mouse_y - coordinate_mouse_pos = osm_to_decimal(tile_mouse_x, tile_mouse_y, round(self.zoom)) + bounded_x, bounded_y = unbounded_osm_to_osm(tile_mouse_x, tile_mouse_y, round(self.zoom)) + coordinate_mouse_pos = osm_to_decimal(bounded_x, bounded_y, round(self.zoom)) return coordinate_mouse_pos def mouse_right_click(self, event): From 1f836bb56e913d62715d1f7d300151ecacd365df Mon Sep 17 00:00:00 2001 From: Anton Filimonov Date: Sat, 10 Feb 2024 15:54:09 +0100 Subject: [PATCH 3/3] use bounded tile coordinates in decimal conversion --- tkintermapview/map_widget.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tkintermapview/map_widget.py b/tkintermapview/map_widget.py index e0a7dd0..e35d260 100644 --- a/tkintermapview/map_widget.py +++ b/tkintermapview/map_widget.py @@ -259,10 +259,10 @@ def set_tile_server(self, tile_server: str, tile_size: int = 256, max_zoom: int def get_position(self) -> tuple: """ returns current middle position of map widget in decimal coordinates """ - - return osm_to_decimal((self.lower_right_tile_pos[0] + self.upper_left_tile_pos[0]) / 2, - (self.lower_right_tile_pos[1] + self.upper_left_tile_pos[1]) / 2, - round(self.zoom)) + center_x = (self.lower_right_tile_pos[0] + self.upper_left_tile_pos[0]) / 2, + center_y = (self.lower_right_tile_pos[1] + self.upper_left_tile_pos[1]) / 2 + bounded_x, bounded_y = unbounded_osm_to_osm(center_x, center_y, round(self.zoom)) + return osm_to_decimal(bounded_x, bounded_y, round(self.zoom)) def fit_bounding_box(self, position_top_left: Tuple[float, float], position_bottom_right: Tuple[float, float]): # wait 200ms till method is called, because dimensions have to update first @@ -872,9 +872,9 @@ def set_zoom(self, zoom: int, relative_pointer_x: float = 0.5, relative_pointer_ mouse_tile_pos_x = self.upper_left_tile_pos[0] + (self.lower_right_tile_pos[0] - self.upper_left_tile_pos[0]) * relative_pointer_x mouse_tile_pos_y = self.upper_left_tile_pos[1] + (self.lower_right_tile_pos[1] - self.upper_left_tile_pos[1]) * relative_pointer_y - - current_deg_mouse_position = osm_to_decimal(mouse_tile_pos_x, - mouse_tile_pos_y, + bounded_x, bounded_y = unbounded_osm_to_osm(mouse_tile_pos_x, mouse_tile_pos_y, round(self.zoom)) + current_deg_mouse_position = osm_to_decimal(bounded_x, + bounded_y, round(self.zoom)) self.zoom = zoom