iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
0
Modern Web

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

[5-4] 環域查詢 - 完結篇

本篇文章請搭配
[5-1] 環域與繪圖工具 - 以Leaflet Draw實現
[5-2] Callback & Promise - 解決request非同步的四種解法
[5-3] 點線面的接口 - 以配接器模式 Adapter Design Pattern 重構


今天要來把環域查詢剩下的程式寫完。
今天搭配的資料為桃園市政府 觀光旅遊局 開放資料
使用的是景觀資料、飯店資料、店家資料。

Step1. 匯入資料庫

把csv檔透過SSMS可以將資料匯入SQL Server,
或是自己開資料表組Insert語法,這部分不是今天的重點就不細講。
https://ithelp.ithome.com.tw/upload/images/20200930/20130604d3nHNnWOqk.jpg
↑ 匯完的資料如上,有很多欄位資訊,我們要篩選我們需要使用的部分,如果還要保存原始資料的話,建議可以建view,之後只需要讀取view即可。

CREATE VIEW [dbo].[V_TY_Landscape] AS
SELECT [Name] AS '名稱'
	  ,[Add] AS '地址'
      ,[Opentime] AS '開放時間'
      ,[Parkinginfo] AS '停車資訊'
      ,[Px] AS 'x'
      ,[Py] AS 'y'
      ,[Toldescribe] AS '簡介'
      ,[Remarks] AS '備註'
      ,[Tel] AS '電話'
      ,[Fax] AS '傳真'
	  ,'/Build/img/TYTest/landscape.png' AS [icon]
	   FROM TY_Landscape
GO

↑ 這邊特別建立欄位[icon],用以讀取圖台顯示icon的路徑,如果不同的資料要用不同的icon,只需要修改view就好,不必去程式加判斷。

https://ithelp.ithome.com.tw/upload/images/20200930/20130604WbB8XBmEjO.jpg
↑ 建好的view,欄位名稱改為我們想顯示的中文名稱,之後前端只需要做迭帶把它們通通顯示就好。如此一來,修改名稱也只要改view,不必改程式。

Step2. UI

初始化地圖以及刻一個簡單的UI,提供checkbox可以選擇想要查詢的資訊。

  • 介面

    <div id="lmap"></div>
    <div class="search">
        <label for="Landscape">景觀</label>
        <input type="checkbox" name="searchType" value="Landscape" id="Landscape" />
        <label for="Hotel">飯店</label>
        <input type="checkbox" name="searchType" value="Hotel" id="Hotel" />
        <label for="Store">店家</label>
        <input type="checkbox" name="searchType" value="Store" id="Store" />
        <button id="btnSearch">繪圖查詢</button>
    </div>

https://ithelp.ithome.com.tw/upload/images/20200930/20130604n3xqdhARf9.jpg
↑ 刻好介面如上(css就不附了,大家可以自行發揮) 其實我有偷偷抄bootstrap

  • 新增事件

        var typeArr = [];
        $(function () {
            $('input[name="searchType"]').click(function () {
                let type = $(this).val();

                if ($(this)[0].checked) {
                    typeArr.push(type);
                    typeArr.filter((item, index) => 
                                        typeArr.indexOf(item) !== index);
                } else {
                    typeArr = typeArr.filter((item) => item !== type);
                }
            });
        });

↑ 這邊用JQ來寫,用陣列typeArr來儲存欲查詢資訊的類別。
如果勾選就把勾選項目加入陣列,
然後用filter去除重複(也可以用ES6 Set,有機會來介紹)。
如果取消勾選則把它從陣列中去除。

        $('#btnSearch').click(function () {
            var drawing = new Drawing(LMap, 'Circle', {});
            drawing.StartWithAdapter(adaptee.LCircleToObject)
                .then(res => GetTYTravelData(res.x, res.y, res.radius, typeArr))
                .then(res => {
                    console.log(res)
                    ShowMultiPoint(res, LMap);
                });
        });

↑ 按鈕點擊事件,點擊後開啟我們昨天寫的繪圖事件drawing.StartWithAdapter並畫圓,
畫完圓後的回傳資訊加入參數呼叫GetTYTravelData(),
目的是跑一個axios去呼叫後端API,
最後則是跑ShowMultiPoint(),把回傳資訊秀在地圖上。

Step3. axios 呼叫 API

  • axios

        var GetTYTravelData = (x, y, radius, typeArr) => {
            return new Promise(function (resolve, reject) {
                axios.post('/WebService/BufferSearch.ashx', { method: 'GetTYData', longitude: x, latitude: y, radius: radius, type: JSON.stringify(typeArr) })
                    .then(function (response) {
                        let data = response.data.dataList;
                        resolve(data);
                    })
                    .catch(function (error) {
                        console.log(error);
                        reject(error);
                    })
                    .finally(function () {
                        console.log('finally');
                    });
            });
        };

↑ 使用axios呼叫API,並回傳Promise物件。
這邊API端使用ASP.NET的ashx泛型處理常式,
原本想要建.NET Core WebAPI的Controller的,只是我不熟只好先放棄QQ。
後端C#程式就不附上了,大家可以依自己熟悉的方式建API。

  • Query

 Declare @p geography  
 SET @p = geography::STGeomFromText('POINT(121.218235004344 25.0257789866682)', 4326)  
 SELECT * FROM (  
	SELECT * FROM V_TY_Hotel UNION  
	SELECT * FROM V_TY_Store UNION  
	SELECT * FROM V_TY_Landscape 
	) a WHERE @p.STDistance(geography::STGeomFromText('POINT(' + CAST(CAST([x] AS DECIMAL(20,7)) AS VARCHAR(20)) + ' ' + CAST(CAST([y] AS DECIMAL(20,7)) AS VARCHAR(20)) +')', 4326)) < 700

↑ SQL語法大致長這個樣子,由中心點到座標點的距離有沒有小於環域半徑來篩選資料,MSSQL空間查詢語法詳見[番外篇] MSSQL Spatial 地理空間資訊查詢

Step4. 秀出環域查詢結果

        var ShowMultiPoint = (dataList = [], map) => {
            let layers = [];
            if (dataList.length > 0) {
                dataList.forEach(function (item) {
                    let marker = L.marker([item.y, item.x], {
                        icon: L.icon({
                            iconUrl: item.icon,
                            iconSize: [20, 20],
                            iconAnchor: [5, 5],
                        })
                    }).addTo(map);

                    SetInfoWindow(marker, item);
                    layers.push(marker);
                });
            }
        }

↑ 建立ShowMultiPoint函式可以把所有點資料呈現在圖台上,並根據每個點資料的icon url,來顯示不同的icon,最後再用SetInfoWindow綁上資訊視窗。

        var SetInfoWindow = (marker, data = {}) => {
            let field = Object.keys(data);
            let str = "";

            field = field.filter((item) => !["icon"].includes(item));
            field.forEach((item) => {
                str += `<p>${item}: ${data[item]} </p>`;
            });
            marker.bindPopup(str);
        }

↑ 利用Object.keys的方式找到物件中所有欄位的名稱,
再把所有欄位名稱及值顯示在資訊視窗中。
利用filter((item) => !["icon"].includes(item)); 額外篩掉不想顯示的欄位。

https://ithelp.ithome.com.tw/upload/images/20200930/20130604zWFTKtt6vv.jpg

↑ 環域結果
可以找到環域範圍中的旅遊資訊,並且根據勾選的項目來顯示景觀、飯店、店家資料,並且點擊後可以把詳細資料秀出來。
https://ithelp.ithome.com.tw/upload/images/20200930/20130604nDa5IFvdEY.jpg
↑ 也可以查詢桃園市中心密集的旅店分佈情形。
密集的點資料群聚,可以計算後用一個圖標去代替一群點,透過顏色、大小去顯示及區別,讓群聚效應更加視覺化。(之後會有一個單元特別講群聚)


今天終於把環域查詢完成啦!(撒花
不過今天做的是圓的環域,多邊形的環域大同小異,
可以依靠MSSQL的空間查詢方法做到,就交給大家實做看看啦!

不知不覺Day15了,痛苦的日子還剩一半
下一篇要開始介紹WebGIS的其他資料呈現格式。
準備好放中秋連假啦!/images/emoticon/emoticon37.gif


上一篇
[5-3] 點線面的接口 - 以配接器模式 Adapter Design Pattern 重構
下一篇
[6-1] 圖層套疊 - WMS & WMTS
系列文
《你的地圖會說話? WebGIS與JavaScript的情感交織》30

尚未有邦友留言

立即登入留言