網頁

2022年7月18日 星期一

手機 App 程式設計- AppInventor2-計算距離和導航,Map+Marker +LineString+LocationSensor 的應用

 主題

設計一個可以測量二個地點的距離和簡易導航的 App。

學習目標

學習如何使用  Map(地圖)、Marker (標記)、LineString(線條字串)、LocationSensor (位置感測器 )這些元件的應用。

功能

  • 使用者使用ListPicker( 清單選擇器),分別選取起點和目的地(如果是選「目前位置」,使用手機定位的功能,查出目前的位置)。在起點和目的地畫一條線,並計算出其距離。
  • 按「導航」按鈕,在地圖上顯示標記,隨著位置的更改,動態更新,讓使用者可以看地圖,知道要走那一條路。
  • 在地圖上,按「目前位置的標記」(本程式是以橙色標記來表示),可顯示目前位置的latitude (緯度 )和 longitude (經度)。
  • 如果使用者選的起點和目的地有問題,會出現相關訊息。
  • 使用者可以選擇定位週期和歸位週期的值。

執行畫面

開始畫面:

選擇起點:

如果選擇「目前位置」,就會使用 LocationSensor(位置感測器 )偵測目前的位置。如果手機沒有開啟定位功能,會出現錯誤訊息:

計算出起點和目的地的距離:

按了「導航」按鈕,出現導航的畫面。這是簡易的導航功能,沒有路徑規劃的功能,也不會出現導引。要使用Navigation 元件,才有路徑規劃。
綠色標記表示目前的位置,會跟著使用者的位置移動,使用者要看地圖,決定走那一條路。

使用者祇選一個地點,無法計算距離:

使用者雖然有選起點和目的地,但其中一個是「目前位置」,如果沒有開啟定位功能,不知目前位置在那裏,所以無法計算距離。

定位週期可選擇幾秒自動重新定位,預設為60秒。如果在高速公路行駛,因為速度快,可選擇1秒。如果是走路,可選擇60秒。



歸位週期可選擇幾秒自動重新將地圖中心點歸位到目前位置,預設為40秒。

註:
  1. 由於使用到 LocationSensor,在模擬器執行,不是很正確。所以上述執行畫面,皆是手機執行的真正畫面。
  2. AppInventor 的Map 是使用 OpenStreetMap。OpenStreetMap 是開放資料的.可以自由地使用作任何用途,前提須標明作者為 OpenStreetMap 及其貢獻者。在沒有網路環境時,使用手機的cache(快取)來顯示地圖,所以先在有網路連線,瀏覽相關的路徑和地點,以便cache 在手機內。AppInventor 的定位可使用無線網路或GPS(在戶外),GPS是直接接收天上衛星的訊號,不用網路,就可運作。

如何將手機的執行畫面,傳到電腦上?

 我使用 華碩的Zenfone手機 ,根據官網的說明,截取畫面的方法為 :Press and hold the [Power key] and [Volume Down key] for 1 second 。

我發現,按手機下面右邊的 矩形 久一點,也可以截取螢幕畫面。


按分享,可把截取的畫面分享在 line 上 (可先在line  弄出 「一人暫存區 」聊天室)。在電腦上開啟line,將圖片下載。

素材

適用於 AppInventor 的 icon(圖示),大小是 50x50 pixels。 本 app的 icon (圖示),可自己繪製或網路搜尋。以下是阿旺師使用 PhotoImpact 繪製的圖片:
calculate_distance.png

如果使用網路的作品,請注意著作權。如果是 CC0 ,就比較沒有限制。

如果是 svg檔,可在AppInventor 中更改標記圖形的顏色,jpg或 png 檔就不行。
「目前位置」標記使用的向量圖,我使用 Illustrator 繪製,開新檔 30x30pixels。利用橢圓形工具畫二個圓,「物件/變形」 。「視窗/圖層」,新增圖層,再畫一個矩形。使用「視窗/路徑管理員」,減去上層。將剩下的圖形全部放在第一層,將第二層刪除。另存新檔,存檔類型要選 svg,例如:man.svg。(如果有二層,在執行 App 時無法更改原圖片的顏色)
我把 Marker_CurrentLocation 此標記 ,FillColor 屬性設為Orange(橙色)。本來是黑色,但在執行時變成橙色。



導航時 Marker_CurrentLocation 標記的圖形,我是使用 Illustrator 繪製。開新檔 25x25pixels,外圓的大小是 20x20 pixels,內圓的大小是 15x15 pixels。另存新檔,類型是 svg (向量圖)。
因為 Google Blogger 不能上傳 svg圖檔,所以無法放此類型檔案。
man.png

navigate.png
png或jpg 此種點陣圖在AppInventor 中無法更改顏色。

進入 AppInventor 

Google 輸入 appinventor,選 「MIT App Inventor」,選 「Create Apps」,進入建置App的畫面。網址為 :http://ai2.appinventor.mit.edu/

Projects/Start new project,輸入新專案的名稱,如 calculate_distance,按 「OK」鈕 。專案的名稱只能使用大小寫字母、數字及「 _」符號,名稱的第一個字元必須是英文字母,不能使用中文字。

Designer  (畫面編排)

Screen1:




Screen1 元件相關屬性表

元件

類別

屬性

功能

Screen1

 

AppName: calculate_distance,可以改成「計算距離和導航」(出現在手機上面的App名稱)
Icon :calculate_distance.png
ScreenOrientation(
螢幕方向):Portrait(鎖定直式畫面)
Title:
計算距離和導航

1Screen元件就是1個螢幕 (畫面)ScreenOrientation設定為Portrait,避免使用者晃動手機造成螢幕方向轉換。

HorizontalArrangement_ListPicker

Layout(介面配置)

AlignVertical:Center:2(垂直對齊,居中:2 )
Width:Fill Parent (
填滿)

水平配置元件,讓裏面的元件可以水平對齊。

ListPicker_Source

User Interface

Text:起點

清單選擇器,

選擇起點。

ListPicker_Destination

User Interface

Text:目的地

清單選擇器,

選擇目的地。

VerticalArrangement_Message

Layout(介面配置)

Width:Fill Parent (填滿)

垂直配置元件,讓裏面的元件可以上下擺放。

Label_Marker_Meaning

User Interface

Text:-起點,紅-終點,橙-目前位置

 

標籤

Label_Distance

User Interface

Text:兩地距離為:0

標籤

Label_CurrentLocation

User Interface

Text:目前位置:

標籤

HorizontalArrangement_Cycle

Layout

AlignVertical:Center:2(垂直對齊,居中:2 )
Width:Fill Parent (
填滿)

水平配置元件

Label_LocatedCycle

User Interface

FontSize(字體大小):12
Text(
文字):定位週期:

標籤

Spinner_LocatedCycle

User Interface

ElementsFromString(元素字串):1,2,3,4,5,10,20,30,60
Prompt(
提示):每隔幾秒重新定位
Selection(選中項):60

下拉式選單

Label_MapToCurrentLocationCycle

User Interface

FontSize:12
Text:
歸位週期:

標籤

Spinner_ MapToCurrentLocationCycle

User Interface

ElementsFromString:20,30,40,50,60,70,80,90
Prompt:
每隔幾秒將地圖中心點歸位到目前位置
Selection:40

下拉式選單

HorizontalArrangement_Navigate

Layout(介面配置)

AlignVertical:Center:2(垂直對齊,居中:2 )
Width:Fill Parent (
填滿)

水平配置元件

Button_Navigate

User Interface

Text:導航

按鈕

Button_MapToCurrentLocation

User Interface

Text:地圖歸位

按鈕,調整地圖中心點為目前的位置。

Map1

Maps (地圖)

CenterFromString(中心字串): 24.1374212,120.6869897 (台中火車站的位置)
Width:Fill Parent
Height:Fill Parent

地圖

Marker_Source

Maps

FillColor(填色):Black
ImageAsset(
圖像資料):None
Visible(
可見性):取消勾選

標記,表示起點。

Marker_Destination

Maps

FillColor:Red (或是 Default)
ImageAsset:None
Visible:
取消勾選

標記,表示目的地。

Marker_CurrentLocation

Maps

FillColor:Orange()
ImageAsset(
圖像資料):man.svg
Visible:
取消勾選

標記,表示目前位置。

LineString1

Maps

StrokeWidth(線寬):4
Visible:
取消勾選

線條字串

LocationSensor1

Sensors(感測器)

TimeInterval:(計時間隔):60000

位置感測器,每601次。

Clock1

Sensors(感測器)

TimerEnabled(啟用計時):不勾選
TimerInterval(
計時間隔):1000 (毫秒)

計時器,每隔1秒觸發計時事件程序。

Clock_MapToCurrentLocation

Sensors(感測器)

TimerEnabled(啟用計時):不勾選TimerInterval(計時間隔):40000 (毫秒)

計時器,每隔40秒地圖歸位一次,將地圖中心點歸位到目前位置。

Notifier1

User Interface

 

對話框


FillColor 若要設為 Custom自訂顏色),使用方式如下:

輸入文字

各地點的 latitude [ˋlætə͵tjud](緯度)、longitude [ˋlɑndʒəˋtjud](經度):

台中火車站,24.1374212,120.6869897
宮原眼科,24.1379912,120.6835587
臺中刑務所演武場,24.1346163,120.6740372
審計新村,24.1447624,120.6626369
文化部文化資產園區,24.1334379,120.6817622
國家歌劇院,24.1626642,120.6403031
自然科學博物館,24.1572964,120.6660567
秋紅谷,24.1676610,120.6396951

如何得出某個地點的位置值(緯度,經度)?

到 Google Map ,輸入:台中火車站,移到紅色標記,按右鍵,會出現火車站的位置值,按下去複製。




Blocks(程式設計)拼塊

跟元件有關的拼塊,要先點選該元件後,再選擇是那一個拼塊;不是元件的拼塊,可以根據拼塊的顏色,判斷是屬於那一個類別。

Screen1:
----------------------------------------------------------------
說明:
全域性變數 NavigateF,其值若為 true,表示導航中。
全域性變數 Marker_List,標記清單,含有 Marker_Source、Marker_Destination 二個元件。
-----------------------------------------------------------------------------------



----------------------------------------------------------------
說明:
全域性變數 Location,位置清單,含有二個元素。第1個元素是緯度清單(含有8個元素,如果第1個元素為0,表示目前位置尚未定位,等到定位後,再把值填入此位置),第2個元素是經度清單(含有8個元素)。
-----------------------------------------------------------------------------------
---------------------------------------------------------------
說明:
全域性變數 Place_Name,地點名稱清單。
---------------------------------------------------------------

----------------------------------------------------------------
說明:
CalculateDistance_DrawLine 此程序是計算二點的距離和畫直線。使用Marker_Source(起點標記)和 Marker_Destination(目的地標記)的 latitude(緯度)、longitude(經度),來求結果。
----------------------------------------------------------------
---------------------------------------------------------------
說明:
ListPicker_AfterPicking此程序,當清單選擇器挑選完成後要執行的動作。
ListPicker_Source 和ListPicker_Destination被挑選後,要執行的程序差不多,所以就寫成一個程序來處理。Which_ListPicker 表示那一個 ListPicker (清單選擇器),1 表示 ListPicker_Source ,2 表示 ListPicker_Destination。
區域性變數 CannotLocateF  此旗標表示不能定位,先設為 false ,即可以定位。
根據 Which_ListPicker 的值,如果是1 將ListPicker_Source設為不可見(將Visible此屬性設為 false) ,2 表示將ListPicker_Destination設為不可見 。

如果 Selection_Index =1 , 表示選到第1項,即目前位置。然後再判斷位置感測器定位的結果,如果latitude(緯度 )=0 ,表示手機沒有開啟定位的功能,否則將位置清單的第1個元素(即 緯度清單)中的第1個元素的值 (即目前位置的緯度),用 位置感測器定位到的 緯度來取代。將位置清單的第2個元素(即 經度清單)中的第1個元素的值 (即目前位置的度),用 位置感測器定位到的度來取代。

如果可以定位,將標記移到定位到的位置。並將此標記設為可見(將Visible此屬性設為 true)

如果 起點和目的地二個相同,就顯示「起點和目的地一樣」的訊息。
如果 ListPicker_Source.SelectionIndex或 ListPicker_Destination.SelectionIndex ,有一個為0 (表示有一個清單選擇器沒被點選 ),然後 顯示「祇有選1個地點,無法算出距離」錯誤訊息 。

如果位置清單的第1個元素(即緯度清單)中的 SelectionIndex 此位置的值為0,表示選到「目前位置」但因無法定位,所以緯度仍為0。顯示「不知目前位置的值,所以無法算出距離」訊息。 
都沒有錯誤, 呼叫 CalculateDistance_DrawLine此程序,以算出距離和畫線。
---------------------------------------------------------------

---------------------------------------------------------------
說明:
Screen1_initialize (初始化):當Screen1 出現(螢幕初始畫面),要執行的動作。把地點
(Place_Name)清單的資料丟給ListPicker_Source和ListPicker_Destination,當其元素來源。
---------------------------------------------------------------
---------------------------------------------------------------
說明:
ListPicker_Source 或 ListPicker_Destination  選擇完成後要執行的動作。
---------------------------------------------------------------

---------------------------------------------------------------
說明:
MapToCurrentLocation 程序的功能是 將地圖中心點歸位到台中火車站(如果無法定位)或是 目前位置。。
LocationSensor1.Latitude =0 , 表示無法定位,找不到目前位置。那就把 Location清單變數的第1個元素(緯度清單)的第2個位置的值 (即台中火車站的緯度)加上 Location清單變數的第2個元素(經度清單)的第2個位置的值 (即台中火車站的經度),當作地圖的中心點。
---------------------------------------------------------------
---------------------------------------------------------------
說明:
當 Button_Navigate被點選(Click)(即使用者按了「導航」按鈕) ,要執行的動作。
如果 LocationSensor1.Latitude  ≠ 0 , 表示可以定位
 如果 狀態是未啟用導航,那就執行導航 的功能。
set  LocationSensor1.TimeInterval to 2000,設定 LocationSensor 的時間間隔為2秒,AppInventor 建議的數值是 20秒,不過若車子的速度很快,20秒可能已移動很長的距離了。
導航所使用的 Marker_CurrentLocation 標記的ImageAsset(圖像資料)是 navigate.svg,FillColor(填色)是 RGB(17, 102, 17),即深綠色。此值是透過以下的配色網站得來的。

配色網站
http://paletton.com/
找到深綠色。

按下去,出現深綠色的RGB值,有16進位和10進位值,使用10進位值 (17, 102, 17)。


make color  填上 (17, 102, 17)。

如果 狀態是啟用導航,那就執行停止導航 的功能。
 Marker_CurrentLocation 標記的ImageAsset(圖像資料)改為 man.svg,FillColor(填色)改為 Orange(橙色)。
如果 LocationSensor1.Latitude  = 0 , 表示無法定位,顯示「無法導航,請開啟定位功能!」的訊息。
---------------------------------------------------------------

---------------------------------------------------------------
說明:
當Clcok1 發生Timer(計時)事件,假設 TimerInterval(計時間隔) 為40000,即每40秒發生一次 Timer事件。
將Marker_CurrentLocation 標記 SetLocation (設定位置)為 LocationSensor(位置感測器)偵測到的值,也就是將 Marker_CurrentLocation 標記移到目前的位置。

當Clock_MapToCurrentLocation 發生計時事件, 呼叫 MapToCurrentLocation 程序(將地圖歸位)。
---------------------------------------------------------------

---------------------------------------------------------------
說明:
當 Marker_CurrentLocation 標記被點選時,如果不是在導航狀態時,顯示目前位置的值 。導航時,為了行車安全,不希望使用者隨意按按鈕。
---------------------------------------------------------------

---------------------------------------------------------------
說明:
當 Button_MapToCurrentLocation 按鈕被點選時, 呼叫 MapToCurrentLocation 程序。
---------------------------------------------------------------
---------------------------------------------------------------
說明:
Spinner在選單中會自動顯示上一次選取的結果,而 ListPicker不會。所以定位週期和歸位週期,就使用 Spinner 。
TimeInterval(計時間隔) 的單位是ms(毫秒),而Spinner在選單中顯示的是秒,所以 要把Selection(選中項,即選到的項目) *1000,秒 *1000 ,轉為毫秒。
定位週期一般是60秒一次,因為開車速度快,所以讓使用者可以把定位週期變小,如1秒定位一次,不夠週期愈短,愈耗電池。
---------------------------------------------------------------

心得與反思

        寫好的程式,若不能應用在日常生活,就變成很空虛。能務實致用,那才是最有成就感。本程式實際測試從台中市區走74號快速公路,再走國道3號高速公路到白河交流道,路線圖正常運作顯示。

       電池從 100%下降到 80%。經過這次的測試,發現若能讓使用者更改定位週期和歸位週期,會更符合人性,所以現在的版本已加入這些功能。
      以往的免費導航,常是使用 Google Map。現在也可以使用自己寫的程式,真是別有一番滋味在心頭。笛卡爾認為人是「我思故我在」,阿旺師認為程式是「我用故我在」。