Skip to content

Infinite horizontal scroll #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions tkintermapview/canvas_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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

10 changes: 5 additions & 5 deletions tkintermapview/canvas_polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
6 changes: 3 additions & 3 deletions tkintermapview/canvas_position_marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
62 changes: 36 additions & 26 deletions tkintermapview/map_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -209,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):
Expand Down Expand Up @@ -257,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
Expand Down Expand Up @@ -432,16 +434,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
Expand All @@ -463,18 +470,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
Expand All @@ -492,11 +499,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")
Expand All @@ -511,11 +518,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:
Expand All @@ -525,10 +532,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:
Expand Down Expand Up @@ -864,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

Expand Down Expand Up @@ -907,19 +915,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)
Expand Down
8 changes: 8 additions & 0 deletions tkintermapview/utility_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down