iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 8
0
Modern Web

《你的地圖會說話? WebGIS與JavaScript的情感交織》系列 第 8

[4-1] 線、面資料圖徵 - 以行政區定位及導航為例

線、面資料圖徵

繼之前介紹向量模式的點資料圖徵後,今天要來介紹線資料圖徵及面資料圖徵。線及面資料圖徵其實都是一群點所組成,並且有方向性,唯一的差別在於面資料的頭尾會相連,線則頭尾不會相連。

繼續來使用 Here Maps API

        <div id="hmap"></div>

↑ 建個地圖的div。

        var platform = new H.service.Platform({
            'apikey': yourkey
        });
        var defaultLayers = platform.createDefaultLayers();
        var HMap = new H.Map(
            document.getElementById('hmap'),
            defaultLayers.vector.normal.map,
            {
                zoom: 7,
                center: { lat: 23.5, lng: 121 },
                pixelRatio: 1
            });
        var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(HMap));
        var ui = H.ui.UI.createDefault(HMap, defaultLayers);

↑ Here Maps API 起手式,初始化地圖。

  • PolyLine

        var lineString = new H.geo.LineString();

        lineString.pushPoint({ lat: 23, lng: 120 });
        lineString.pushPoint({ lat: 23.2, lng: 120.5 });
        lineString.pushPoint({ lat: 23.4, lng: 120.7 });
        lineString.pushPoint({ lat: 24, lng: 121 });
        lineString.pushPoint({ lat: 24.5, lng: 121.8 });
        HMap.addObject(new H.map.Polyline(
            lineString, { style: { lineWidth: 4 } }
        ));

↑ new一個LineString物件,並且把每個點座標以物件的方式push進去,再把LineString傳入Polyline中,最後把新建的Polyline加入地圖中就大功告成啦!注意!這裡的pushPoint並不是Array.prototype.push方法,而是Here Maps的LineString的方法!
https://ithelp.ithome.com.tw/upload/images/20200923/20130604oUoCkRdNNP.jpg
↑ 成功建立一條線在地圖上。
然而,我們不可能每次呼叫都把一個一個點輸入LineString,因此我們把它改寫成function吧!這次用ES6語法。

        var ShowLine = (pointList = [], map, style) => {
            let lineString = new H.geo.LineString();
            pointList.forEach(item => { 
                lineString.pushPoint({ lat: item.y, lng: item.x });
            });
            map.addObject(new H.map.Polyline(  // 新增Polyline並加入地圖
                lineString, { style: style }
            ));
            map.getViewModel().setLookAtData({  // 設定視線畫面為線的邊界
                bounds: lineString.getBoundingBox()
            });
        };

↑ 預設傳入pointList陣列,並且假設陣列格式為每一筆都是帶有x、y屬性的物件,EX:
var pointList = [{ x: 121.5, y: 24 }, { x: 121.2, y: 23.8 }, { x: 121, y: 23.5 }];
我們只要傳入點座標陣列、地圖物件,並設定屬性,就可以畫出線圖徵。

       ShowLine(pointList, HMap, { lineWidth: 8, strokeColor: '#E488AE' });

↑ 呼叫ShowLine
https://ithelp.ithome.com.tw/upload/images/20200923/20130604DlmdPSsbIU.jpg
↑ style作為參數的好處,每次呼叫時都可以重新決定它的樣式,不必寫死在程式中。

  • Polygon

        var lineString = new H.geo.LineString();

        lineString.pushPoint({ lat: 23, lng: 120 });
        lineString.pushPoint({ lat: 23.2, lng: 120.5 });
        lineString.pushPoint({ lat: 23.4, lng: 120.7 });
        lineString.pushPoint({ lat: 24, lng: 121 });
        lineString.pushPoint({ lat: 24.5, lng: 121.8 });

        HMap.addObject(
            new H.map.Polygon(lineString, {
                style: {
                    fillColor: '#FFFFCC',
                    strokeColor: '#829',
                    lineWidth: 8
                }
            })
        );

↑ Polygon與PolyLine的寫法大同小異,都是先建立一個LineString物件,並且把點資料都加入LineString中,最後新建Polygon並加入地圖。
https://ithelp.ithome.com.tw/upload/images/20200923/20130604jh9EO1xYAR.jpg
↑ 成功畫出Polygon,這就是我現在的表情!(誤

        var ShowPolygon = (pointList = [], map, style) => {
            let lineString = new H.geo.LineString();
            pointList.forEach(item => {
                lineString.pushPoint({ lat: item.y, lng: item.x });
            });
            map.addObject(new H.map.Polygon(
                lineString, { style: style }
            ));
        };
        ShowPolygon(pointList, HMap, {  // 呼叫,建立一個面資料圖徵
            fillColor: '#99CEFF',
            strokeColor: '#E5A596',
            lineWidth: 3
        });

↑ 一樣可以把它改寫成function。

Here Maps API Routing 導航

Here Maps API有很多很有趣的加值功能,當時看到這個Routing功能,就躍躍欲試,也是我最近一直玩Here Maps API的原因之一。今天用的功能是Map with Driving Route from A to B,也就是汽車導航,只要輸入起始座標點及終點座標點,就可以進行路網分析,找出最佳的路線。
先來看看Routing Service要怎麼使用吧!

        function calculateRouteFromAtoB(platform) {
            var router = platform.getRoutingService(null, 8),
                routeRequestParams = {
                    routingMode: 'fast',
                    transportMode: 'car',
                    origin: '25.031,121.543',   // 大安
                    destination: '25.041,121.566',   // 市政府
                    return: 'polyline,turnByTurnActions,actions,instructions,travelSummary'
                };
            router.calculateRoute(routeRequestParams, function (result) {
                console.log(arguments);
            });
        }

↑ 建立getRoutingService後,並且設定它的參數,包括車速快慢、交通工具類型、起始點、終點等等,是不是很有趣呢?最後呼叫calculateRoute來計算,讓我們看看它的回調函式傳給我們什麼。
https://ithelp.ithome.com.tw/upload/images/20200923/20130604SRQtBLbZ6I.jpg
看到這裡的瞬間,心理OS:它callback arguments結構也太複雜了吧!
不光是結構複雜,我找半天它的點座標,竟然找不到!
後來查了文件之後才知道,竟然是那一長串的polyline,而且座標組竟是加密過後的!
第一次看到有座標還有加密之後傳遞的,優勢除了資訊安全提高外,也避免座標組過多時傳遞很長的陣列。
但說真的不直覺!

↓ 知道它回傳結構後,重新修改calculateRoute的callback function

            router.calculateRoute(routeRequestParams, function (result) {
                let poly =  H.geo.LineString.fromFlexiblePolyline(  // 解密
                    result.routes[0].sections[0].polyline).getLatLngAltArray();
                let arr = [];
                for (i = 0; i < poly.length; i += 3) {  // 陣列重組
                    arr.push({ y: poly[i], x: poly[i + 1] });
                }
                // 畫出導航路線
                ShowLine(arr, HMap, { lineWidth: 8, strokeColor: 'rgba(0, 128, 255, 0.7)' });
            });

可以利用fromFlexiblePolyline方法把加密過後的字串,解密為座標組合,再利用for迴圈提取資料存成陣列,最後呼叫ShowLine把導航的路線畫出來!
https://ithelp.ithome.com.tw/upload/images/20200923/20130604A3fsOzz6tu.jpg
↑ 大致是從大安附近一路到東區,沿途都是走大馬路,還算OK的導航路線。導航功能還有每個轉彎點的方向及下個轉彎點的距離,不過過程太繁瑣了就先不列上來,因為都再拆解參數。期待未來能加入車流量分析,透過監視器即時影像辨識判斷車流擁擠度,並找尋最佳的路線。

同場加映: TGOS 行政區定位

今天既然講了面資料結構,那怎麼可以沒有面資料結構的例子呢?因此找來了TGOS API行政區定位,並且同樣以Here Maps的圖台呈現。

        <script type="text/javascript" src="https://api.tgos.tw/TGOS_API/tgos?ver=2&AppID=yourID&APIKey=yourkey" charset="utf-8"></script>

↑ 引入TGOS API

        var LocateByDistrict = (district, callback) => {
            var locator = new TGOS.TGLocateService();
            var callback = callback || function () { }
            var result = {
                pointList: [],
                errorMessage: ''
            }

            locator.locateWGS84({ district: district }, function (e, status) {
                if (status != TGOS.TGLocatorStatus.OK) {
                    console.log('查無行政區');
                    result.errorMessage = '查無行政區';
                } else {
                    result.pointList = e[0].geometry.geometry
                        .rings_[0].linestring.path.map(function (item) {
                        return {
                            x: item.x,
                            y: item.y
                        }
                    });
                }

                callback(result);
            });
            this.result = result;
        }

↑ 這邊直接附上寫好的function,透過TGOS的定位服務TGLocateService,並呼叫locateWGS84方法,傳入行政區,做簡單的資料重組後,即能在callback function中回傳點資料陣列。

        LocateByDistrict('台北市', (result) => {
            ShowPolygon(result.pointList, HMap, {
                fillColor: 'rgba(224, 197, 83, 0.3)',
                strokeColor: '#4439E7',
                lineWidth: 3,
            });
        });

↑ 呼叫行政區定位,並在呼叫結束時用剛剛寫好的ShowPolygon以面資料的方式,顯示在Here Maps上。
https://ithelp.ithome.com.tw/upload/images/20200923/20130604kmOso0Xp5o.jpg
↑ 這樣就能繪製出行政區邊界囉!


小結

今日簡單介紹了線資料圖徵以及面資料圖徵,用Here Maps API Routing來呈現線資料,
以及TGOS API行政區定位示範呈現面資料。
大家有沒有發現一個共通點,不管是線還是面都要先new H.geo.LineString(),
一樣的function寫兩次,造成程式碼重複外,奇檬子還不太爽,
身為programmer的我們,沒事就是要一直重構阿!明天,會講解用原型鏈(prototype chain)的方式,把它們重新包裝,並且用ES5語法實作繼承,讓重複的程式碼只寫一次,其它通通都用繼承的!
是不是很期待呀?!/images/emoticon/emoticon39.gif


上一篇
[3-2] Scope Chain & IIFE 問題與解法 - 以Here Maps API展點為例
下一篇
[4-2] prototype chain 原型鏈、建構子與繼承 - 以Here Maps API為例
系列文
《你的地圖會說話? WebGIS與JavaScript的情感交織》30

尚未有邦友留言

立即登入留言