ทำแผนที่ง่ายๆด้วย Brython — Flask

replace Javascript with Python as the scripting language for web browsers.

4 min readMar 26, 2020

--

สวัสดีครับ 😀 Blog นี้ผมจะมาแนะนำการสร้างแผนที่ด้วย brython ง่ายๆ ร่วมกับ flask นะครับ และแน่นอนแผนที่นั้นยังเป็น leaflet เหมือนเดิมครับ

Brython

replace Javascript with Python, as the scripting language for web browsers.

brython เป็นการนำโค้ดภาษาไพทอน มาปรับแต่งให้รันบนเว็บบราวเซอร์แทนจาวาสคริป แล้วสามารถอินเทอร์เฟซไปยัง DOM objects และ events ได้ รองรับไลบรารีต่าง ๆ ของภาษาไพทอน และสามารถใช้ภาษาไพทอนทำงานร่วมกันกับไลบรารีของ Javascript ได้

ที่จริง Blog ที่แล้ว ๆ ผมได้ทำการสร้างแผนที่ง่ายๆด้วย folium ไปแล้ว แต่ครั้งนี้ผมจะใช้ brython แทนนะครับ ซึ่งก็น่าจะมีบางคนงงว่ามันต่างกันอย่างไรทั้งๆที่ใช้ leaflet เป็นแผนที่แล้วก็ยังเขียนด้วย python เหมือนกันอีก 🤔 …. อ๋อ ถ้าอยากรู้เรื่องราวให้กระจ่าง พวกเราก็พร้อมที่จะแถลงไข เพื่อปกป้องไม่ให้โลกถูกทำลาย เอะ ผิดๆๆ 😅

ถามว่าต่างกับ folium ตรงไหน

ต่างตรงที่ folium นั้นจะทำการเร็นเดอร์แล้วส่งออกมาเป็น html เฉย ๆ ในขณะที่ brython นั้นจะเป็นเหมือนการฝังลงไปในหน้าเว็บแทนตัว javascript เลย !!

การเขียนก็จะคล้ายกับ javascript เลยครับเพียงแต่ต้องปรับ syntax นิดหน่อยให้ถูกหลัก python เท่านั้นเอง ดังนั้นเราอ้างอิง Docs ของ leaflet เป็นพิเศษครับผม
(เขียนลงไปในไฟล์ html โดยตรงเลย)

<สำหรับบล็อกเกี่ยวกับ flask นะครับจิ้มลิ้งด้านล่างได้เลย> 😘😘😘

<ส่วนอันนี้คือ folium รันบนเว็บด้วย flask เหมือนกัน>

หลังจากติดตั้ง flask แล้วเรามาลง brython กับ leaflet กันดีกว่า

ติดตั้ง brython

โดยเราจะติดตั้งลงใน static โฟลเดอร์ด้วย npm นะครับผม

npm install leaflet brython

ก็ได้จะได้ ไฟล์ node_modules ดังรูปด้านล่าง

แล้วจากนั้นสร้างไฟล html ไว้ใน templates โฟลเดอร์ ชื่อ
base-map.html, map.html

หน้าตา directory ก็จะออกมาประมาณนี้นะครับ

(ในที่นี่ผมเขียนตัว flask ไว้ใน app.py ตัวเดียวเลย)

โค้ดตัวอย่าง (มีแบบที่เสร็จแล้วแต่อยู่ท้าย Blog)
base-map.html

base-map.html

โค้ดส่วนใหญ่ในนี้จะเป็นการเซ็ตอัพต่างทั้งพวก brython และ leaflet
<body onload=”brython()”>
ใช้ในการรันไพธอนสคริปมาใช้ ซึ่งสำศัญมากต้องมี! 😤 อีกทั้งในวงเล็บยังสามารถใส่ option เพิ่มเติมได้อีกด้วย เพิ่มเติม…

map.html

maphtml

map.html นี่จะเป็นไฟล์หลักของเราเลย เราจะเขียนทุกอย่างเกี่ยวกับ map ลงไประหว่าง <script type=”text/python”> ในรูปแบบภาษาไพธอนแต่อยู่ใน html

อธิบาย

center =  (7.0166666, 100.4666648) # hatyai coords

ให้ตัวแปร center มีค่าเท่ากับละติดจูดลองจิจูดศูนย์กลางของอำเภอหาดใหญ่

leaflet = window.L

สร้าง object leaflet

ul = html.UL(id="nav")
ul <= html.LI(f'latitude: {center[0]}')
ul <= html.LI(f'longitude: {center[1]}')
document["coords"] <= ul

รูปแบบ
html.(แท็ก html คือ การสร้างแท็กของ html หรือ element นั้นเอง
(<=) คือ การแทรก element เข้าไป
document[“coords”] คือการชี้ไปยังแท็ก html ที่มี id = “coords”

จากบรรทัดแรก หมายความว่าสร้างแท็ก ul ที่มี id = “nav”
บรรทัดสองและสาม สร้างแท็ก li ที่มีข้อความ latitude และ longitude แล้วแทรกเข้าไปในตัวแปร ul แล้วเอาไปแทรกในแท็ก html ที่ id = “coords”
จะมีลักษณะประมาณนี้

<p id="coords">
<ul id="nav">
<li>latitude: 7.0166666</li>
<li>longitude: 100.4666648</li>
</ul>
</p>

จากนั้นเรามาสร้างแผนที่ ด้วย leaflet.map()

mymap = leaflet.map('mapid').setView(center, zoom)leaflet.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?' \f'access_token={access_token}', data).addTo(mymap)

จากโค้ดข้างบนคือ การสร้างแผนที่ขึ้นมาโดยชี้ไปยังแท็ก html มีที่ id = “mapid” บรรทัดต่อมาคือ กำหนดรูปแบบของแผนที่ (tilelayer) แล้วเพิ่มลงในแผนที่ที่เรากำหนดโดยใช้คำสั่ง .addTop(แผนที่ของเรา) จากโค้ดจะเห็นได้ว่า data จะอยู่ในรูปของ dictionary ใช้สำหรับกำหนด license ของรูปแบบแผนที่นั้นๆ ที่จะแสดงอย่างมุมขวาล่างของแผนที่ของเรา (รูปแบบ license แต่ละแผนที่จะไม่เหมือนกัน) สามารถดูตัวอย่างแผนที่แล้วนำมาใช้ตามที่ชอบได้ที่นี่เลย (แก้ syntax ให้เป็นของไพธอนก่อนนะ ห้ามลืม)

license จะอยู่ตรงนี้จ้า

และสร้างหมุดด้วย leaflet.marker()

leaflet.marker([center[0],center[1]]).addTo(mymap)

สร้างหมุดขึ้นมาโดย อิงละติจูดกับลองจิจุด ในที่นี้ใช้ของ center
แต่เรายังสามารถใส่ option เพิ่มเติมได้อีกเช่น การเปลี่ยน icon
ตย.
leaflet.marker([51.5, -0.09], {“icon”: greenIcon}).addTo(map)
โดย greenIcon นั้นจะอยู่ในรูปแบบของ dictionary เหมือนกัน เช่น

greenIcon = leaflet.icon(dict(
iconUrl= f'/static/images/marks/{types[type]}.svg',
iconSize=[46, 48],
iconAnchor=[22,40],
popupAnchor=[0,-26]
))

ในที่นี้เราไม่ได้ใส่ icon อะไรไว้มันเลยใช้ default ของ leaflet นั้นเอง (สีฟ้า)

สามารถดูตัวอย่างและ option เพิ่มเติมได้ที่ Docs ของ leaflet เลย เพียงแต่ต้องเปลี่ยน syntax นิดหน่อยนะเพราะ เดิมที่ leaflet นั้นเป็น javascript จิ้มตรงนี้

app.py

ก็แค่นี้เลย render_template ไปยัง map.html

app.py

แล้วเข้า path ของ /map

ก็จะออกมาประมาณนี้ เยี่ยยยยม! ได้แล้วนี่ ง่ายเนอะ

“เอะกด หมุด แล้วไม่เกิดอะไรขึ้นเลย” อยากให้มันแสดง popup ขึ้นมาต้องทำไงอะ 🤔 … อ๋อใช้นี้สิ มาจิกคลีนสะอาดไม่ต้องร….ผิดๆๆ

bindPopup

กำหนดต่อท้ายที่ที่เราใช้สร้างหมุดเลยแล้วตามด้วยข้อความที่อยากแสดงขึ้นมา

leaflet.marker([center[0],center[1]]).addTo(mymap).bindPopup('ที่นี่คือใจกลางหาดใหญ่')

หรือ แบบนี้ก็ได้ เลือกที่ชอบเลยฮะ

marker = leaflet.marker([center[0],center[1]]).addTo(mymap)
marker.bindPopup('ที่นี่คือใจกลางหาดใหญ่')

ก็จะได้ประมาณนี้

อีกทั้งเรายังสามารถใช้แท็ก html ร่วมได้ด้วยนะเออ

leaflet.marker([center[0],center[1]]).addTo(mymap).bindPopup(f'''ใจกลางหาดใหญ่ คือ <b>{center}</b> </br>ไก่ทอดอร่อยมาก แต่ชานมไข่มุกอร่อยกว่า''')

แล้วเรายังสามารถสั่งให้มันเปิดเลยโดยที่เราไม่ต้องกดด้วย .openPopup()
การเปิด popup นั้น leaflet จะทำการปิดของก่อนหน้าโดยอัตโนมัติ ดังนั้นเราจะเปิดได้แค่ อันเดียว เท่านั้นนะ

marker.openPopup()

จบแล้ว 😆 เราสามารถสร้างแผนที่ได้แล้ว
ที่เหลือก็จะเป็นการตกแต่งต่อเติมตามที่คุณต้องการเลยนะครับ

แต่ผมยังไม่จบผมอยากจะต่อเติมอะไรเพิ่มหน่อย555 เพิ่มตัวอย่างให้ทุกคนด้วย~

“ผมไม่ชอบรูปแบบแผนที่อันนี้เท่าไรเลย ชอบ ตัวนี้ มากกว่า”

ที่ผมต้องทำก็คือ เอาโค้ดตรง Plain javascript มาแก้ syntax นิดหน่อยให้แล้วเพิ่มลงในโค้ดของผมแบบนี้

leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
"maxZoom": 19,
"attribution": '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(mymap)

ส่วนตัวเก่าก็ลบไปเช่น acess_token เพราะไม่ได้ใช้และ data ก็เหมือนกัน

แผนที่ก็จะออกมาประมาณนี้ ดูดีขึ้นใช่ไหม~

โค้ดแผนที่จนถึงตอนนี้ map.html

ที่นี้ผมอยากรู้ตำแหน่งของตัวเองด้วย
อยากรู้ว่าตัวเองอยู่ตรงไหนของแผนที่ ก็ใช้
window.navigator.geolocation.getCurrentPosition(success, error)
เป็นการดึงตำแหน่งของผู้ใช้มาแล้วส่งต่อไปยังฟังก์ชั่นชื่อ success แต่ถ้าเกิดข้อผิดพลาดจะไปทำฟังก์ชั่น error แทน

map.html

เพิ่มแล้วจะมีหมุดที่เป็นตำแหน่งของคุณเพิ่มขึ้นมา จะเป็นประมาณนี้
(ถ้าอยู่ หาดใหญ่อะนะ555)

ต่อมาผมอยากสร้างหมุดเพิ่มอีก 2 หมุดที่ เชียงใหม่, กรุงเทพ แล้วสร้างปุ่มที่ทำให้บินไปที่หมุดนั้นๆได้! 😉 ช่วงนี้อยากไปเที่ยวมากแต่ต้องอยู่บ้าน 🤧

ผมจะใช้วิธีการลูปในการเซ็ตหมุดดังนั้นเพื่อให้ง่ายต่อการเรียกใช้ ผมจะเปลี่ยนข้อมูลของละติจูลองจิจุด และรายละเอียดของ popup เป็น dictionary ให้หมด
(บรรทัดที่ 14–27)

จากนั้นผมเปลี่ยน center ของแผนที่เป็นของประเทศไทยแทน
คือ (13.736717, 100.523186) และ zoom ให้เหลือ 6 จะได้เห็นครอบคลุม
(บรรทัดที่ 12)

map.html

เราจะบินไปเที่ยวกันด้วยคำสั่ง flyTo(coords, zoom) (บรรทัดที่ 50–51)

map.html

(บรรทัดที่ 66–68) ขออธิบายก่อนว่าทำ ไมต้องใช้ lambda แล้วฟังก์ชั่นต้องมีตัวแปร ev ?? ev คือ eventของเมาส์การกระทำ action ใส่ปุ่มนั้นตัวแปรพวก pixel ของเมาส์ ฯลฯ จะอยู่ในตัวแปร ev หมดเลยและ brython ไม่ยอมให้ผ่านถ้าไม่มีและการจะใส่ตัวแปรตัวที่สองจำเป็นต้องรู้ตัวที่หนึ่งแต่ ev นั้นมันมาจากโปรแกรมเราไม่รู้!แต่ผมต้องการจะส่งชื่อจังหวัดไปด้วยเลยจำเป็นต้องเอา lambda มาช่วยนั้นเองครับ (ยุ่งยากอะ555)

จริงๆ เขียนแบบนี้ก็ได้ไม่ต้องส่ง ev ไป เพราะในที่นี้เราไม่ได้ใช้ ev เลย555
แต่ในฟังก์ชั่นก็ต้องเอาออกด้วยนะเออ

# ลบ ev ออกเพราะไม่ได้ใช้
def flyto(province_name):
mymap.flyTo(center[province_name], 14)
document['chiangmai'].bind('click', lambda ev:flyto('chiangmai'))
document['hatyai'].bind('click', lambda ev: flyto('hatyai'))
document['bankkok'].bind('click', lambda ev: flyto('bankkok'))

ที่นี้แค่กดปุ่มเราก็จะบินไปเที่ยวได้แล้วเย่~

ขอบคุณที่อ่านจบนะครับ ถ้าชอบกดไลค์ เอ้ย ปรบมือ 5555

โค้ดที่เสร็จแล้ว

สำหรับคนที่สนใจ folium จิ้มเลย~

--

--

Piravit Chenpittaya
Piravit Chenpittaya

Written by Piravit Chenpittaya

call me karn | Computer of Engineering : PSU | IG: karn.svg | git: https://github.com/karnzx /

No responses yet