pvbang commited on
Commit
3340f36
·
verified ·
1 Parent(s): f3f8e56

Delete map.py

Browse files
Files changed (1) hide show
  1. map.py +0 -477
map.py DELETED
@@ -1,477 +0,0 @@
1
- import re
2
- import streamlit as st
3
- import folium
4
- from folium import Marker, PolyLine, DivIcon
5
- from geopy.distance import geodesic
6
- import requests
7
- import polyline
8
- import math
9
- import streamlit.components.v1 as components
10
- from streamlit_folium import st_folium # Cài đặt: pip install streamlit-folium
11
-
12
- # Lệnh này phải là dòng đầu tiên trong script của bạn
13
- st.set_page_config(layout="wide", page_title="Làm Map cho Chị")
14
-
15
- # Thêm FontAwesome CSS để hiển thị icon
16
- st.markdown(
17
- '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">',
18
- unsafe_allow_html=True,
19
- )
20
-
21
- # -----------------------------
22
- # Hàm mở rộng link và trích xuất tọa độ từ URL
23
- # -----------------------------
24
- def extract_coords_from_url(url):
25
- try:
26
- # Mở URL để lấy URL đích sau redirect
27
- resp = requests.get(url, allow_redirects=True, timeout=5)
28
- final_url = resp.url
29
- # Tìm kiếm tọa độ theo mẫu "@lat,lon"
30
- match = re.search(r'@([\d\.\-]+),([\d\.\-]+)', final_url)
31
- if match:
32
- return (float(match.group(1)), float(match.group(2)))
33
- # Nếu không tìm thấy, thử mẫu "!3dlat!4dlon"
34
- match2 = re.search(r'!3d([\d\.\-]+)!4d([\d\.\-]+)', final_url)
35
- if match2:
36
- return (float(match2.group(1)), float(match2.group(2)))
37
- except Exception as e:
38
- st.error("Lỗi khi mở link: " + str(e))
39
- return None
40
-
41
-
42
- # -----------------------------
43
- # Danh sách icon (FontAwesome)
44
- # -----------------------------
45
- icon_list = [
46
- "glass", "music", "search", "envelope-o", "heart", "star", "star-o", "user",
47
- "film", "th-large", "th", "th-list", "check", "times", "search-plus", "search-minus",
48
- "power-off", "signal", "cog", "trash-o", "home", "file-o", "clock-o", "road", "download",
49
- "arrow-circle-o-down", "arrow-circle-o-up", "inbox", "play-circle-o", "repeat", "refresh",
50
- "list-alt", "lock", "flag", "headphones", "volume-off", "volume-down", "volume-up",
51
- "qrcode", "barcode", "tag", "tags", "book", "bookmark", "print", "camera", "font", "bold",
52
- "italic", "text-height", "text-width", "align-left", "align-center", "align-right",
53
- "align-justify", "list", "outdent", "indent", "video-camera", "picture-o", "pencil",
54
- "map-marker", "adjust", "tint", "pencil-square-o", "share", "check-square-o", "arrows",
55
- "step-backward", "fast-backward", "backward", "play", "pause", "stop", "forward",
56
- "fast-forward", "step-forward", "eject", "chevron-left", "chevron-right", "plus-circle",
57
- "minus-circle", "times-circle", "check-circle", "question-circle", "info-circle",
58
- "crosshairs", "times-circle-o", "check-circle-o", "ban", "arrow-left", "arrow-right",
59
- "arrow-up", "arrow-down", "share-square-o", "expand", "compress", "plus", "minus",
60
- "asterisk", "exclamation-circle", "gift", "leaf", "fire", "eye", "eye-slash",
61
- "exclamation-triangle", "plane", "calendar", "random", "comment", "magnet", "chevron-up",
62
- "chevron-down", "retweet", "shopping-cart", "folder", "folder-open", "arrows-v",
63
- "arrows-h", "bar-chart", "twitter-square", "facebook-square", "camera-retro", "key",
64
- "cogs", "comments"
65
- ]
66
-
67
- # -----------------------------
68
- # Khởi tạo dữ liệu địa chỉ mẫu trong session_state
69
- # -----------------------------
70
- if "addresses" not in st.session_state:
71
- st.session_state.addresses = [
72
- {
73
- "name": "11 Trương Hữu Hoàng",
74
- "latitude": 16.491013,
75
- "longitude": 107.622923,
76
- "marker_icon": "home",
77
- "route_color": "#FF4500",
78
- "is_home": True,
79
- "visible": True
80
- },
81
- {
82
- "name": "KQH Vinh Vệ",
83
- "latitude": 16.493565250557587,
84
- "longitude": 107.6274186,
85
- "marker_icon": "map-marker",
86
- "route_color": "#8A2BE2",
87
- "is_home": False,
88
- "visible": True
89
- },
90
- {
91
- "name": "UBND Xã Phú Mỹ",
92
- "latitude": 16.49506178728241,
93
- "longitude": 107.6298651576717,
94
- "marker_icon": "building",
95
- "route_color": "#1E90FF",
96
- "is_home": False,
97
- "visible": True
98
- },
99
- {
100
- "name": "KQH Chiết Bi",
101
- "latitude": 16.49102143751169,
102
- "longitude": 107.60960879999999,
103
- "marker_icon": "map-marker",
104
- "route_color": "#8A2BE2",
105
- "is_home": False,
106
- "visible": True
107
- },
108
- {
109
- "name": "Trường Tiểu học Phú Thượng",
110
- "latitude": 16.491607862678077,
111
- "longitude": 107.60640322883584,
112
- "marker_icon": "school",
113
- "route_color": "#32CD32",
114
- "is_home": False,
115
- "visible": True
116
- },
117
- {
118
- "name": "Trường THCS Phú Thượng",
119
- "latitude": 16.491657525074864,
120
- "longitude": 107.60404525582075,
121
- "marker_icon": "school",
122
- "route_color": "#0000FF",
123
- "is_home": False,
124
- "visible": True
125
- },
126
- {
127
- "name": "Trường UKA - UK Academy Huế",
128
- "latitude": 16.493026837831497,
129
- "longitude": 107.60477552883586,
130
- "marker_icon": "graduation-cap",
131
- "route_color": "#FF8C00",
132
- "is_home": False,
133
- "visible": True
134
- },
135
- {
136
- "name": "Trường Nghiệp Vụ Thuế",
137
- "latitude": 16.4956182,
138
- "longitude": 107.60249869999998,
139
- "marker_icon": "graduation-cap",
140
- "route_color": "#800080",
141
- "is_home": False,
142
- "visible": True
143
- },
144
- {
145
- "name": "Trường Tiểu học Thuỷ Vân",
146
- "latitude": 16.489429637257857,
147
- "longitude": 107.62618450000001,
148
- "marker_icon": "school",
149
- "route_color": "#32CD32",
150
- "is_home": False,
151
- "visible": True
152
- },
153
- {
154
- "name": "Chợ Hôm (Dạ Lê Chánh)",
155
- "latitude": 16.490582399855406,
156
- "longitude": 107.6265241,
157
- "marker_icon": "shopping-cart",
158
- "route_color": "#FFA500",
159
- "is_home": False,
160
- "visible": True
161
- },
162
- {
163
- "name": "UBND Phường Thuỷ Vân",
164
- "latitude": 16.48642452451863,
165
- "longitude": 107.6235033,
166
- "marker_icon": "building",
167
- "route_color": "#1E90FF",
168
- "is_home": False,
169
- "visible": True
170
- },
171
- {
172
- "name": "Trường THCS Phú Mỹ",
173
- "latitude": 16.494371725363457,
174
- "longitude": 107.62332647116415,
175
- "marker_icon": "school",
176
- "route_color": "#0000FF",
177
- "is_home": False,
178
- "visible": True
179
- }
180
- ]
181
- else:
182
- for addr in st.session_state.addresses:
183
- if "visible" not in addr:
184
- addr["visible"] = True
185
-
186
- # -----------------------------
187
- # Sidebar: Cài đặt kích thước icon và text
188
- # -----------------------------
189
- st.sidebar.subheader("Cài đặt kích thước")
190
- home_icon_size = st.sidebar.slider("Kích thước icon chính (Home)", min_value=16, max_value=48, value=28)
191
- marker_icon_size = st.sidebar.slider("Kích thước icon địa chỉ (khác)", min_value=12, max_value=32, value=16)
192
- home_text_size = st.sidebar.slider("Kích thước chữ địa chỉ Home", min_value=12, max_value=36, value=24)
193
- marker_text_size = st.sidebar.slider("Kích thước chữ địa chỉ (khác)", min_value=10, max_value=24, value=16)
194
-
195
- # -----------------------------
196
- # Sidebar: Chọn chế độ hiển thị đường đi
197
- # -----------------------------
198
- route_mode = st.sidebar.radio(
199
- "Chọn chế độ hiển thị đường đi",
200
- ("Tuyến đường ngắn nhất", "Đường thẳng"),
201
- index=1
202
- )
203
-
204
- # Chọn icon và hiển thị preview để cập nhật ngay
205
- selected_icon = st.sidebar.selectbox("Chọn icon cho địa chỉ", options=icon_list,
206
- index=icon_list.index("map-marker") if "map-marker" in icon_list else 0)
207
- st.sidebar.markdown(
208
- f"<div style='font-size:15px; margin-bottom:10px;'>Xem trước icon: <i class='fa fa-{selected_icon}'></i></div>",
209
- unsafe_allow_html=True
210
- )
211
-
212
- # -----------------------------
213
- # Sidebar: Form thêm địa chỉ mới với live preview icon
214
- # -----------------------------
215
- st.sidebar.header("Thêm Địa Chỉ Mới")
216
-
217
- # Nhập link Google Map (có thể là link rút gọn, ví dụ: https://maps.app.goo.gl/oMS72uF8bP2b6gmbA)
218
- google_map_link = st.sidebar.text_input("Nhập tọa độ từ link Google Map", value="", placeholder="https://maps.app.goo.gl/oMS72uF8bP2b6gmbA")
219
-
220
- if google_map_link:
221
- coords = extract_coords_from_url(google_map_link)
222
- if coords:
223
- st.session_state.selected_coords = coords
224
- st.sidebar.success(f"Tọa độ được trích xuất: {coords[0]}, {coords[1]}")
225
- else:
226
- st.sidebar.error("Không thể trích xuất tọa độ từ link này.")
227
-
228
- # Sử dụng tọa độ đã chọn làm giá trị mặc định cho vĩ độ và kinh độ
229
- default_coords = st.session_state.get("selected_coords", (16.460000, 107.580000))
230
- with st.sidebar.form("add_address_form"):
231
- name = st.text_input("Tên địa chỉ")
232
- latitude = st.number_input("Vĩ độ", value=default_coords[0], format="%.6f")
233
- longitude = st.number_input("Kinh độ", value=default_coords[1], format="%.6f")
234
- route_color = st.color_picker("Chọn màu cho đường đi", value="#3388ff")
235
- is_home = st.checkbox("Đặt làm địa chỉ chính (Home)", value=False)
236
- submitted = st.form_submit_button("Thêm địa chỉ")
237
- if submitted:
238
- if not name:
239
- st.sidebar.error("Vui lòng nhập tên địa chỉ!")
240
- else:
241
- if is_home:
242
- for addr in st.session_state.addresses:
243
- addr["is_home"] = False
244
- new_address = {
245
- "name": name,
246
- "latitude": latitude,
247
- "longitude": longitude,
248
- "marker_icon": selected_icon,
249
- "route_color": route_color,
250
- "is_home": is_home,
251
- "visible": True
252
- }
253
- st.session_state.addresses.append(new_address)
254
- st.sidebar.success("Đã thêm địa chỉ mới.")
255
-
256
- # -----------------------------
257
- # Sidebar: Danh sách địa chỉ (có khả năng xóa và ẩn/hiện)
258
- # -----------------------------
259
- st.sidebar.header("Danh sách địa chỉ")
260
- for idx, addr in list(enumerate(st.session_state.addresses)):
261
- label = f"{addr['name']} ({'Home' if addr['is_home'] else 'Địa chỉ'})"
262
- st.sidebar.write(label)
263
- col1, col2 = st.sidebar.columns(2)
264
- if col1.button("Xóa", key=f"delete_{idx}"):
265
- st.session_state.addresses.pop(idx)
266
- st.experimental_rerun()
267
- toggle_label = "Ẩn" if addr.get("visible", True) else "Hiện"
268
- if col2.button(toggle_label, key=f"toggle_{idx}"):
269
- st.session_state.addresses[idx]["visible"] = not addr.get("visible", True)
270
- st.experimental_rerun()
271
-
272
- # -----------------------------
273
- # Xác định địa chỉ Home (nếu không có thì chọn địa chỉ đầu tiên)
274
- # -----------------------------
275
- if st.session_state.addresses:
276
- home_address = next((addr for addr in st.session_state.addresses if addr["is_home"]),
277
- st.session_state.addresses[0])
278
- else:
279
- st.error("Chưa có địa chỉ nào. Vui lòng thêm địa chỉ.")
280
- st.stop()
281
-
282
- home_coords = [home_address["latitude"], home_address["longitude"]]
283
-
284
- # -----------------------------
285
- # Hàm lấy lộ trình từ API OSRM (cho tuyến đường ngắn nhất)
286
- # -----------------------------
287
- def get_route(start, end):
288
- url = f"http://router.project-osrm.org/route/v1/driving/{start[1]},{start[0]};{end[1]},{end[0]}?overview=full&geometries=polyline"
289
- response = requests.get(url)
290
- if response.ok:
291
- data = response.json()
292
- if data["routes"]:
293
- route_geometry = data["routes"][0]["geometry"]
294
- return polyline.decode(route_geometry)
295
- return None
296
-
297
- # -----------------------------
298
- # Hàm tính vector offset (chuyển offset từ mét sang độ)
299
- # -----------------------------
300
- def compute_offset_vector(home, dest, offset_meters):
301
- lat1, lon1 = home
302
- lat2, lon2 = dest
303
- mean_lat = math.radians((lat1 + lat2) / 2)
304
- meters_per_deg_lat = 111111
305
- meters_per_deg_lon = 111111 * math.cos(mean_lat)
306
-
307
- d_lat = (lat2 - lat1) * meters_per_deg_lat
308
- d_lon = (lon2 - lon1) * meters_per_deg_lon
309
-
310
- perp_x = -d_lat
311
- perp_y = d_lon
312
- norm = math.sqrt(perp_x ** 2 + perp_y ** 2)
313
- if norm == 0:
314
- return (0, 0)
315
- unit_x = perp_x / norm
316
- unit_y = perp_y / norm
317
- offset_x = unit_x * offset_meters
318
- offset_y = unit_y * offset_meters
319
- offset_lat = offset_y / meters_per_deg_lat
320
- offset_lon = offset_x / meters_per_deg_lon
321
- return (offset_lat, offset_lon)
322
-
323
- def apply_offset_to_polyline(polyline_pts, offset_meters, home, dest):
324
- offset = compute_offset_vector(home, dest, offset_meters)
325
- return [[pt[0] + offset[0], pt[1] + offset[1]] for pt in polyline_pts]
326
-
327
- # -----------------------------
328
- # Xử lý các địa chỉ có tọa độ gần nhau để tránh vẽ đường trùng lặp
329
- # -----------------------------
330
- def get_overlap_key(addr):
331
- return (round(addr["latitude"], 4), round(addr["longitude"], 4))
332
-
333
- overlap_groups = {}
334
- for idx, addr in enumerate(st.session_state.addresses):
335
- if not addr["is_home"]:
336
- key = get_overlap_key(addr)
337
- overlap_groups.setdefault(key, []).append(idx)
338
-
339
- offset_mapping = {}
340
- for group in overlap_groups.values():
341
- n = len(group)
342
- for j, addr_index in enumerate(group):
343
- offset_mapping[addr_index] = (j - (n - 1) / 2) * 10
344
-
345
- # -----------------------------
346
- # Tạo bản đồ với Folium hiển thị marker và tuyến đường
347
- # -----------------------------
348
- m = folium.Map(location=home_coords, zoom_start=14, tiles="CartoDB Positron")
349
-
350
- # Vẽ marker cho từng địa chỉ (chỉ những địa chỉ visible)
351
- for idx, addr in enumerate(st.session_state.addresses):
352
- if not addr.get("visible", True):
353
- continue
354
- coords = [addr["latitude"], addr["longitude"]]
355
- if addr["is_home"]:
356
- icon_html = f'''
357
- <div style="display: inline-flex; align-items: center; font-family: 'Lora', serif;">
358
- <div style="background: white; border: 2px solid {addr["route_color"]}; border-radius: 50%; padding: 8px;">
359
- <i class="fa fa-{addr["marker_icon"]}" style="font-size:{home_icon_size}px; color:{addr["route_color"]};"></i>
360
- </div>
361
- <div style="margin-left: 8px; font-size:{home_text_size}px; font-weight:bold; color:black; white-space: nowrap;">
362
- {addr["name"]}
363
- </div>
364
- </div>
365
- '''
366
- Marker(
367
- location=coords,
368
- tooltip=addr["name"],
369
- icon=DivIcon(html=icon_html),
370
- draggable=True
371
- ).add_to(m)
372
- else:
373
- icon_html = f'''
374
- <div style="text-align:center; font-family: 'Lora', serif;">
375
- <div style="background: white; border: 2px solid {addr["route_color"]}; border-radius: 50%; display: inline-block; padding: 8px;">
376
- <i class="fa fa-{addr["marker_icon"]}" style="font-size:{marker_icon_size}px; color:{addr["route_color"]};"></i>
377
- </div>
378
- <div style="margin-top:4px; font-size:{marker_text_size}px; color:black; white-space: nowrap;">
379
- {addr["name"]}
380
- </div>
381
- </div>
382
- '''
383
- Marker(
384
- location=coords,
385
- tooltip=addr["name"],
386
- icon=DivIcon(html=icon_html),
387
- draggable=True
388
- ).add_to(m)
389
-
390
- # Vẽ tuyến đường từ Home đến các địa chỉ khác (chỉ với địa chỉ visible)
391
- for idx, addr in enumerate(st.session_state.addresses):
392
- if addr["is_home"] or not addr.get("visible", True):
393
- continue
394
- dest_coords = [addr["latitude"], addr["longitude"]]
395
- offset_m = offset_mapping.get(idx, 0)
396
- if route_mode == "Tuyến đường ngắn nhất":
397
- route_pts = get_route(home_coords, dest_coords)
398
- if route_pts:
399
- if offset_m != 0:
400
- route_pts = apply_offset_to_polyline(route_pts, offset_m, home_coords, dest_coords)
401
- distance = geodesic(home_coords, dest_coords).kilometers
402
- popup_text = f"{addr['name']} - {distance:.1f} Km"
403
- PolyLine(
404
- locations=route_pts,
405
- color=addr["route_color"],
406
- weight=3,
407
- opacity=0.5,
408
- popup=popup_text
409
- ).add_to(m)
410
- mid_point = route_pts[len(route_pts) // 2]
411
- Marker(
412
- location=mid_point,
413
- draggable=True,
414
- icon=DivIcon(
415
- html=f'''<div style="font-family: 'Lora', serif; font-size:12pt; color:{addr["route_color"]}; font-weight:bold;
416
- background-color:rgba(255,255,255,0.7); padding:2px 4px; border-radius:4px; text-align:center; white-space: nowrap;">
417
- {distance:.1f} Km
418
- </div>'''
419
- )
420
- ).add_to(m)
421
- elif route_mode == "Đường thẳng":
422
- route_pts = [home_coords, dest_coords]
423
- if offset_m != 0:
424
- route_pts = apply_offset_to_polyline(route_pts, offset_m, home_coords, dest_coords)
425
- distance = geodesic(home_coords, dest_coords).kilometers
426
- popup_text = f"{addr['name']} - {distance:.1f} Km"
427
- PolyLine(
428
- locations=route_pts,
429
- color=addr["route_color"],
430
- weight=3,
431
- opacity=0.5,
432
- popup=popup_text
433
- ).add_to(m)
434
- mid_point = [(home_coords[0] + dest_coords[0]) / 2, (home_coords[1] + dest_coords[1]) / 2]
435
- Marker(
436
- location=mid_point,
437
- draggable=True,
438
- icon=DivIcon(
439
- html=f'''<div style="font-family: 'Lora', serif; font-size:12pt; color:{addr["route_color"]}; font-weight:bold;
440
- background-color:rgba(255,255,255,0.7); padding:2px 4px; border-radius:4px; text-align:center; white-space: nowrap;">
441
- {distance:.1f} Km
442
- </div>'''
443
- )
444
- ).add_to(m)
445
-
446
- # -----------------------------
447
- # Render map chính
448
- # -----------------------------
449
- map_html = m.get_root().render()
450
- full_html = f"""
451
- <html>
452
- <head>
453
- <meta charset="utf-8">
454
- <link href="https://fonts.googleapis.com/css2?family=Lora&display=swap" rel="stylesheet">
455
- <style>
456
- html, body {{
457
- margin: 0;
458
- padding: 0;
459
- height: 100%;
460
- width: 100%;
461
- font-family: 'Lora', serif;
462
- }}
463
- #map_container {{
464
- height: 100vh;
465
- width: 100vw;
466
- }}
467
- </style>
468
- </head>
469
- <body>
470
- <div id="map_container">
471
- {map_html}
472
- </div>
473
- </body>
474
- </html>
475
- """
476
-
477
- components.html(full_html, height=800, scrolling=True)