Skip to content

Commit d5caf5d

Browse files
author
Tom Schimansky
committed
added polygons, added data attribute for marker, path and polygon, cleaned code a bit, added polygon to readme
1 parent eeb737f commit d5caf5d

13 files changed

Lines changed: 333 additions & 113 deletions

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ https://pypi.org/project/tkintermapview/
3232

3333
# Documentation / Tutorial
3434

35+
- [Importing](#Importing)
36+
- [Create the widget](#Create the widget)
37+
- [Set coordinate position](#Set coordinate position)
38+
- [Set address position](#Set address position)
39+
- [Set position with marker](#Set position with marker)
40+
- [Create position markers](#Create position markers)
41+
- [Create path from position list](#Create path from position list)
42+
- [Create polygon from position list](#Create polygon from position list)
43+
- [Mouse events on the map](#Mouse events on the map)
44+
- [Utility methods](#Utility methods)
45+
- [Use other tile servers](#Use other tile servers)
46+
- [Use offline tiles](#Use offline tiles)
47+
48+
---
3549
### Importing
3650

3751
Import tkinter as normal and from tkintermapview import the TkinterMapView widget.
@@ -141,6 +155,36 @@ path_1 = map_widget.set_path([marker_2.position, marker_3.position, (52.57, 13.4
141155
# path_1.delete()
142156
````
143157
---
158+
### Create polygon from position list
159+
160+
To create a polygon on the map call the ``map_widget.set_polygon()`` function
161+
and pass a list of coordinate tuples from which the polygon will be created.
162+
You can edit the appearance with the following arguments: ``fill_color, outline_color, border_width``.
163+
You can also set a command function which will be called when the polygon gets clicked and
164+
which will get the polygon object as an argument.
165+
````python
166+
def polygon_click(polygon):
167+
print(f"polygon clicked - text: {polygon.name}")
168+
169+
polygon_1 = map_widget.set_polygon([(46.0732306, 6.0095215),
170+
...
171+
(46.3772542, 6.4160156)],
172+
# fill_color=None,
173+
# outline_color="red",
174+
# border_width=12,
175+
command=polygon_click,
176+
name="switzerland_polygon")
177+
178+
# polygon_1.remove_position(46.3772542, 6.4160156)
179+
# polygon_1.add_position(0, 0, index=5)
180+
# polygon_1.delete()
181+
````
182+
183+
In ``examples/map_view_polygon_example.py`` you can find the full example program,
184+
which results in the following:
185+
186+
![](documentation_images/map_view_polygon_example.png)
187+
---
144188
### Mouse events on the map
145189

146190
When you click on the map with the right mouse button, a menu pops up, where you can view the
2.24 MB
Loading
File renamed without changes.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import tkinter
2+
import tkintermapview
3+
4+
# create tkinter window
5+
root_tk = tkinter.Tk()
6+
root_tk.geometry(f"{1000}x{700}")
7+
root_tk.title("map_view_polygon_example.py")
8+
9+
# create map widget
10+
map_widget = tkintermapview.TkinterMapView(root_tk, width=1000, height=700, corner_radius=0)
11+
map_widget.pack(fill="both", expand=True)
12+
13+
14+
def polygon_click(polygon):
15+
print(f"polygon clicked - text: {polygon.name}")
16+
17+
18+
switzerland_marker = map_widget.set_address("Switzerland", marker=True, text="Switzerland")
19+
map_widget.set_zoom(8)
20+
21+
polygon_1 = map_widget.set_polygon([(46.0732306, 6.0095215),
22+
(46.3393433, 6.2072754),
23+
(46.5890691, 6.1083984),
24+
(46.7624431, 6.4270020),
25+
(47.2717751, 7.0312500),
26+
(47.4726629, 6.9982910),
27+
(47.4057853, 7.3718262),
28+
(47.5468716, 7.9650879),
29+
(47.5691138, 8.4045410),
30+
(47.7540980, 8.6242676),
31+
(47.5691138, 9.4482422),
32+
(47.1897125, 9.5581055),
33+
(46.9352609, 9.8327637),
34+
(46.9727564, 10.4150391),
35+
(46.6418940, 10.4479980),
36+
(46.4605655, 10.0744629),
37+
(46.2786312, 10.1513672),
38+
(46.3469276, 9.5581055),
39+
(46.4454275, 9.3493652),
40+
(45.8211434, 8.9538574),
41+
(46.1037088, 8.6352539),
42+
(46.3696741, 8.3496094),
43+
(45.9740604, 7.9321289),
44+
(45.8900082, 7.0971680),
45+
(46.1417827, 6.8664551),
46+
(46.4151388, 6.7236328),
47+
(46.3772542, 6.4160156)],
48+
# fill_color=None,
49+
# outline_color="red",
50+
# border_width=12,
51+
command=polygon_click,
52+
name="switzerland_polygon")
53+
54+
# polygon_1.remove_position(46.3772542, 6.4160156)
55+
# polygon_1.add_position(0, 0, index=5)
56+
# polygon_1.delete()
57+
58+
root_tk.mainloop()

tkintermapview/canvas_path.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,38 @@
55
if TYPE_CHECKING:
66
from .map_widget import TkinterMapView
77

8-
from .coordinate_convert_functions import deg2num, num2deg
8+
from .utility_functions import decimal_to_osm, osm_to_decimal
99

1010

1111
class CanvasPath:
12-
def __init__(self, map_widget: "TkinterMapView", position_list, color="#3E69CB", command=None, name=None):
12+
def __init__(self,
13+
map_widget: "TkinterMapView",
14+
position_list, color="#3E69CB",
15+
command=None,
16+
name=None,
17+
data: any = None):
18+
1319
self.map_widget = map_widget
1420
self.position_list = position_list
1521
self.canvas_line_positions = []
16-
self.connection_list = []
1722
self.deleted = False
1823

1924
self.path_color = color
2025
self.command = command
2126
self.canvas_line = None
2227
self.name = name
28+
self.data = data
2329

2430
self.last_upper_left_tile_pos = None
2531
self.last_position_list_length = len(self.position_list)
2632

27-
def __del__(self):
28-
self.map_widget.canvas.delete(self.canvas_line)
29-
self.canvas_line = None
30-
3133
def delete(self):
32-
self.__del__()
34+
if self in self.map_widget.canvas_path_list:
35+
self.map_widget.canvas_path_list.remove(self)
3336

34-
def appear(self):
35-
self.deleted = False
36-
self.draw()
37+
self.map_widget.canvas.delete(self.canvas_line)
38+
self.canvas_line = None
39+
self.deleted = True
3740

3841
def add_position(self, deg_x, deg_y, index=-1):
3942
if index == -1:
@@ -47,7 +50,7 @@ def remove_position(self, deg_x, deg_y):
4750
self.draw()
4851

4952
def get_canvas_pos(self, position, widget_tile_width, widget_tile_height):
50-
tile_position = deg2num(*position, round(self.map_widget.zoom))
53+
tile_position = decimal_to_osm(*position, round(self.map_widget.zoom))
5154

5255
canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width
5356
canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height

tkintermapview/canvas_polygon.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import tkinter
2+
import sys
3+
from typing import TYPE_CHECKING, Callable
4+
5+
if TYPE_CHECKING:
6+
from .map_widget import TkinterMapView
7+
8+
from .utility_functions import decimal_to_osm, osm_to_decimal
9+
10+
11+
class CanvasPolygon:
12+
def __init__(self,
13+
map_widget: "TkinterMapView",
14+
position_list: list,
15+
outline_color: str = "#3e97cb",
16+
fill_color: str = "gray95",
17+
border_width: int = 5,
18+
command: Callable = None,
19+
name: str = None,
20+
data: any = None):
21+
22+
self.map_widget = map_widget
23+
self.position_list = position_list # list with decimal positions
24+
self.canvas_polygon_positions = [] # list with canvas coordinates positions
25+
self.canvas_polygon = None
26+
self.deleted = False
27+
28+
self.name = name
29+
self.data = data
30+
self.outline_color = outline_color
31+
self.fill_color = fill_color # can also be None for transparent fill
32+
self.border_width = border_width
33+
self.command = command
34+
35+
self.last_upper_left_tile_pos = None
36+
self.last_position_list_length = len(self.position_list)
37+
38+
def delete(self):
39+
self.map_widget.canvas.delete(self.canvas_polygon)
40+
41+
if self in self.map_widget.canvas_polygon_list:
42+
self.map_widget.canvas_polygon_list.remove(self)
43+
44+
self.canvas_polygon = None
45+
self.deleted = True
46+
47+
def add_position(self, deg_x, deg_y, index=-1):
48+
if index == -1:
49+
self.position_list.append((deg_x, deg_y))
50+
else:
51+
self.position_list.insert(index, (deg_x, deg_y))
52+
self.draw()
53+
54+
def remove_position(self, deg_x, deg_y):
55+
self.position_list.remove((deg_x, deg_y))
56+
self.draw()
57+
58+
def mouse_enter(self, event=None):
59+
if sys.platform == "darwin":
60+
self.map_widget.canvas.config(cursor="pointinghand")
61+
elif sys.platform.startswith("win"):
62+
self.map_widget.canvas.config(cursor="hand2")
63+
else:
64+
self.map_widget.canvas.config(cursor="hand2") # not tested what it looks like on Linux!
65+
66+
def mouse_leave(self, event=None):
67+
self.map_widget.canvas.config(cursor="arrow")
68+
69+
def click(self, event=None):
70+
if self.command is not None:
71+
self.command(self)
72+
73+
def get_canvas_pos(self, position, widget_tile_width, widget_tile_height):
74+
tile_position = decimal_to_osm(*position, round(self.map_widget.zoom))
75+
76+
canvas_pos_x = ((tile_position[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width
77+
canvas_pos_y = ((tile_position[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height
78+
79+
return canvas_pos_x, canvas_pos_y
80+
81+
def draw(self, move=False):
82+
# check if number of positions in position_list has changed
83+
new_line_length = self.last_position_list_length != len(self.position_list)
84+
self.last_position_list_length = len(self.position_list)
85+
86+
# get current tile size of map widget
87+
widget_tile_width = self.map_widget.lower_right_tile_pos[0] - self.map_widget.upper_left_tile_pos[0]
88+
widget_tile_height = self.map_widget.lower_right_tile_pos[1] - self.map_widget.upper_left_tile_pos[1]
89+
90+
# if only moving happened and len(self.position_list) did not change, shift current positions, else calculate new position_list
91+
if move is True and self.last_upper_left_tile_pos is not None and new_line_length is False:
92+
x_move = ((self.last_upper_left_tile_pos[0] - self.map_widget.upper_left_tile_pos[0]) / widget_tile_width) * self.map_widget.width
93+
y_move = ((self.last_upper_left_tile_pos[1] - self.map_widget.upper_left_tile_pos[1]) / widget_tile_height) * self.map_widget.height
94+
95+
for i in range(0, len(self.position_list) * 2, 2):
96+
self.canvas_polygon_positions[i] += x_move
97+
self.canvas_polygon_positions[i + 1] += y_move
98+
else:
99+
self.canvas_polygon_positions = []
100+
for position in self.position_list:
101+
canvas_position = self.get_canvas_pos(position, widget_tile_width, widget_tile_height)
102+
self.canvas_polygon_positions.append(canvas_position[0])
103+
self.canvas_polygon_positions.append(canvas_position[1])
104+
105+
if not self.deleted:
106+
if self.canvas_polygon is None:
107+
self.map_widget.canvas.delete(self.canvas_polygon)
108+
self.canvas_polygon = self.map_widget.canvas.create_polygon(self.canvas_polygon_positions,
109+
width=self.border_width,
110+
outline=self.outline_color,
111+
joinstyle=tkinter.ROUND,
112+
stipple="gray25",
113+
tag="polygon")
114+
if self.fill_color is None:
115+
self.map_widget.canvas.itemconfig(self.canvas_polygon, fill="")
116+
else:
117+
self.map_widget.canvas.itemconfig(self.canvas_polygon, fill=self.fill_color)
118+
119+
if self.command is not None:
120+
self.map_widget.canvas.tag_bind(self.canvas_polygon, "<Enter>", self.mouse_enter)
121+
self.map_widget.canvas.tag_bind(self.canvas_polygon, "<Leave>", self.mouse_leave)
122+
self.map_widget.canvas.tag_bind(self.canvas_polygon, "<Button-1>", self.click)
123+
else:
124+
self.map_widget.canvas.coords(self.canvas_polygon, self.canvas_polygon_positions)
125+
else:
126+
self.map_widget.canvas.delete(self.canvas_polygon)
127+
self.canvas_polygon = None
128+
129+
self.map_widget.manage_z_order()
130+
self.last_upper_left_tile_pos = self.map_widget.upper_left_tile_pos

tkintermapview/canvas_position_marker.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
import tkinter
22
import sys
3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, Callable
44

55
if TYPE_CHECKING:
66
from .map_widget import TkinterMapView
77

8-
from .coordinate_convert_functions import deg2num, num2deg
8+
from .utility_functions import decimal_to_osm, osm_to_decimal
99

1010

1111
class CanvasPositionMarker:
12-
def __init__(self, map_widget: "TkinterMapView", position, text=None, text_color="#652A22", font=None,
13-
marker_color_circle="#9B261E", marker_color_outside="#C5542D", command=None, image=None,
14-
image_zoom_visibility=(13, float("inf"))):
12+
def __init__(self,
13+
map_widget: "TkinterMapView",
14+
position: tuple,
15+
text: str = None,
16+
text_color: str = "#652A22",
17+
font=None,
18+
marker_color_circle: str = "#9B261E",
19+
marker_color_outside: str = "#C5542D",
20+
command: Callable = None,
21+
image=None,
22+
image_zoom_visibility: tuple = (13, float("inf")),
23+
data: any = None):
24+
1525
self.map_widget = map_widget
1626
self.position = position
1727
self.text_color = text_color
@@ -21,9 +31,9 @@ def __init__(self, map_widget: "TkinterMapView", position, text=None, text_color
2131
self.image = image
2232
self.image_hidden = False
2333
self.image_zoom_visibility = image_zoom_visibility
24-
self.connection_list = []
2534
self.deleted = False
2635
self.command = command
36+
self.data = data
2737

2838
self.polygon = None
2939
self.big_circle = None
@@ -38,15 +48,15 @@ def __init__(self, map_widget: "TkinterMapView", position, text=None, text_color
3848
else:
3949
self.font = font
4050

41-
def __del__(self):
51+
def delete(self):
52+
if self in self.map_widget.canvas_marker_list:
53+
self.map_widget.canvas_marker_list.remove(self)
54+
4255
self.map_widget.canvas.delete(self.polygon, self.big_circle, self.canvas_text)
4356
self.polygon, self.big_circle, self.canvas_text = None, None, None
4457
self.deleted = True
4558
self.map_widget.canvas.update()
4659

47-
def delete(self):
48-
self.__del__()
49-
5060
def set_position(self, deg_x, deg_y):
5161
self.position = (deg_x, deg_y)
5262
self.draw()
@@ -75,7 +85,7 @@ def click(self, event=None):
7585
self.command(self)
7686

7787
def get_canvas_pos(self, position):
78-
tile_position = deg2num(*position, round(self.map_widget.zoom))
88+
tile_position = decimal_to_osm(*position, round(self.map_widget.zoom))
7989

8090
widget_tile_width = self.map_widget.lower_right_tile_pos[0] - self.map_widget.upper_left_tile_pos[0]
8191
widget_tile_height = self.map_widget.lower_right_tile_pos[1] - self.map_widget.upper_left_tile_pos[1]
@@ -132,7 +142,7 @@ def draw(self, event=None):
132142
self.map_widget.canvas.tag_bind(self.canvas_text, "<Leave>", self.mouse_leave)
133143
self.map_widget.canvas.tag_bind(self.canvas_text, "<Button-1>", self.click)
134144
else:
135-
self.map_widget.canvas.coords(self.canvas_text, canvas_pos_x, canvas_pos_y - 62)
145+
self.map_widget.canvas.coords(self.canvas_text, canvas_pos_x, canvas_pos_y - 56)
136146
self.map_widget.canvas.itemconfig(self.canvas_text, text=self.text)
137147
else:
138148
if self.canvas_text is not None:

0 commit comments

Comments
 (0)