iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
1
Modern Web

WebGIS入門學習 - 以Openlayers實作系列 第 26

Day 26. 如何在地圖上畫Pokemon #3:還原使用者儲存之繪圖

昨天完成了儲存繪圖圖形,今天就是要將那些儲存的圖形列表撈出來,並讓使用者套疊和管理。

今天的大綱

  1. 撈出儲存列表之API建立
  2. 還原使用者儲存之繪圖小工具
  3. 前端建立使用者儲存清單並介接API
  4. GeoJSON Circle 問題
  5. 畫一隻Pokemon

1. 撈出儲存列表之API建立

STEP1. 跟前面一樣,首先要先建立Model
BasicModels.cs 類別頁面新增 UserDrawCaseOutput,為輸出的欄位。

public class UserDrawCaseOutput
{
    public string drawsaveid { get; set; }
    public string DDate { get; set; }
    public string title { get; set; }
    public string info { get; set; }
}

STEP2. 接著建立Infrastructure裡的function
BasicInfoFunc.cs 新增 getUserDrawCaseList() 用來取的登入的使用者的繪圖儲存列表,由於我是依照每個feature下去進行儲存,因此同一筆儲存案會有很多列的feature在資料表內,在撈取的時候要取 drawsaveid不重複的資料,撈取同一個drawsaveid,但也要撈出最早的 DDate

public static async Task<List<UserDrawCaseOutput>> getUserDrawCaseList(string userid)
{
    SqlDataReader reader = null;
    SqlConnection myConnection = new SqlConnection();
    string Constr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
    myConnection.ConnectionString = Constr;
    SqlCommand sqlCmd = new SqlCommand();
    string sqlStr;
    sqlStr = "SELECT * FROM (SELECT [drawsaveid],[DDate],[title],[info],ROW_NUMBER() OVER (PARTITION BY [drawsaveid] ORDER BY [DDate]) AS RowNumber FROM [OLDemo].[dbo].[UserDrawSave] where [userid]=@userid ) AS a WHERE a.RowNumber = 1";
    sqlCmd.Parameters.AddWithValue("@userid", userid.Trim());
    sqlCmd.CommandText = sqlStr;
    sqlCmd.CommandType = CommandType.Text;
    sqlCmd.Connection = myConnection;
    List<UserDrawCaseOutput> data = new List<UserDrawCaseOutput> { };
    try
    {
        myConnection.Open();
        reader = sqlCmd.ExecuteReader();
        while (reader.Read())
        {
            data.Add(new UserDrawCaseOutput()
            {
                drawsaveid = reader["drawsaveid"].ToString(),
                DDate = reader["DDate"].ToString(),
                title = reader["title"].ToString(),
                info = reader["info"].ToString()
            });
        }
        myConnection.Close();
        myConnection.Dispose();
        return data;
    }
    catch (Exception ex)
    {
        throw;
    }
}

STEP3. 建立Controller,開發獲得使用者儲存繪圖列表的API getUserDrawCase()

/// <summary>
/// 獲得使用者儲存繪圖列表
/// </summary>
/// <returns></returns>
[Route("getUserDrawCase")]
[HttpGet]
[SwaggerResponse(HttpStatusCode.OK, "OK", typeof(List<UserDrawCaseOutput>))]
public async Task<HttpResponseMessage> getUserDrawCase()
{
    string token = authFunc.parseTokenFromHeader(this.Request);
    string userid = authFunc.getUserNameByToken_true(token);
    try
    {
        return Request.CreateResponse(HttpStatusCode.OK, await BasicInfoFunc.getUserDrawCaseList(userid));
    }
    catch (Exception SqlException)
    {
        return Request.CreateResponse(HttpStatusCode.InternalServerError, "Internal Server Error");
    }
}

2. 還原使用者儲存之繪圖小工具

為了要將儲存的geometry和style還原,寫了一個小工具,建立 js/GraphicTrans.js 程式碼檔。

  • GraphicFromString():分別將geom和style兩個字串,利用 GraphicGeomTrans() 建立feature,並利用 GraphicStyleTrans() 將style設定回去。
    • GraphicGeomTrans():從GeoJSON還原geom字串。
    • GraphicStyleTrans():還原樣式並存為符合Openlayers的style格式。
var GraphicTrans = function () {
    function GraphicGeomTrans(geostring) {
        var geoObj = new ol.format.GeoJSON().readGeometry(geostring);
        var newgeo = new ol.Feature({
            geometry: geoObj
        });
        return newgeo;
    }
    
    function GraphicStyleTrans(stylestring) {
        var styleObj = JSON.parse(stylestring);
        var stylejson = createStyle(styleObj);
        var newstyle = new ol.style.Style(stylejson);
        function createStyle(Obj1) {
            var styleArr = ["fill", "stroke", "image", "geometry", "renderer", "text"];
            var styleAll = {};
            if (Obj1 === null) {
                styleAll = null;
            } else {
                Object.keys(Obj1).forEach(function (item, idx) {
                    var newkey = item.replace(/\_$/g, '');
                    if (Obj1[item] !== null) {
                        if (typeof Obj1[item] === 'object' && Array.isArray(Obj1[item]) === false) {
                            if (newkey === "fill") {
                                styleAll[newkey] = new ol.style.Fill(createStyle(Obj1.fill_));
                            } else if (newkey === "stroke") {
                                styleAll[newkey] = new ol.style.Stroke(
                                createStyle(Obj1.stroke_));
                            } else if (newkey === "image") {
                                styleAll[newkey] = new ol.style.Circle(
                                createStyle(Obj1.image_));
                            } else if (newkey === "geometry") {
                                styleAll[newkey] = new ol.style.Circle(
                                createStyle(Obj1.geometry_));
                            } else if (newkey === "renderer") {
                                styleAll[newkey] = new ol.style.Renderer(
                                createStyle(Obj1.renderer_));
                            } else if (newkey === "text") {
                                styleAll[newkey] = new ol.style.Text(createStyle(Obj1.text_));
                            } else {
                                styleAll[newkey] = createStyle(Obj1[item]);
                            }
                        } else {
                            styleAll[newkey] = Obj1[item];
                        }
                    } else {
                        styleAll[newkey] = null;
                    }
                });
            }
            return styleAll;
        }
        return newstyle;
    }
    
    function GraphicFromString(geostring, stylestring) {
        var feature = GraphicTrans.GraphicGeomTrans(geostring);
        feature.setStyle(GraphicTrans.GraphicStyleTrans(stylestring));
        return feature;
    }
    return {
        GraphicGeomTrans: GraphicGeomTrans,
        GraphicStyleTrans: GraphicStyleTrans,
        GraphicFromString: GraphicFromString
    };
}();

3. 前端建立使用者儲存清單並介接API

在前端 Draw.html 新增繪圖儲存的tab頁面。

<div class="ui segment" style="display:none;">
    <p style="margin:0px 0px 10px 0px;">使用者已儲存列表:</p>
    <div class="ui small cards" id="drawcasescards">
    </div>
</div>

jDraw.js 建立撈取使用者儲存的圖形列表 getUserDrawCaseList(),並於畫面上顯示標題、內容、時間與兩個按鈕,分別為:

  1. 加到地圖 getUserDrawGeom()
  2. 刪除 delUserDrawCase()
function getUserDrawCaseList() {
    $("#drawcasescards").html("");
    $.ajax({
        type: "GET",
        url: config_OLMapWebAPI + "/Basic/getUserDrawCase",
        headers: {
            "Authorization": localStorage["token"]
        },
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function (d) {
            //var data = $.parseJSON(d.d);
            var data = d;
            console.log(data);
            data.forEach(function (item, idx) {
                var cardstr = '<div class="card" id="' + item.drawsaveid + '"><div class="content"><div class="header">' + item.title + '</div><div class="meta"><span>' + item.DDate + '</span></div><div class="description">' + item.info + '</div></div><div class="extra content"><div class="ui two buttons"><div class="ui basic green button" onclick="draw.getUserDrawGeom(\'' + item.drawsaveid + '\')">加到地圖</div><div class="ui basic red button" onclick="draw.delUserDrawCase(\'' + item.drawsaveid + '\')">刪除</div></div></div></div>';
                $("#drawcasescards").append(cardstr);
            });
            $("#drawcasescards").parents(" div.segment").show();
        },
        error: function (jqXHR, exception) {
            ajaxError(jqXHR, exception);
        }
    });
}

https://ithelp.ithome.com.tw/upload/images/20201004/20108631u0bwJtgqvy.png

getUserDrawGeom() 為從資料庫撈取儲存的 geomstr 和 stylestr 兩者,篩選符合相對應 drawsaveid 的圖形,並以迴圈執行 GraphicTrans.GraphicFromString() 還原features。

function getUserDrawGeom(drawsaveid) {
    var drawsaveobj = {
        "SQLtype": "Select",
        "title": "",
        "info": "",
        "features": []
    };
    $.ajax({
        type: "POST",
        url: config_OLMapWebAPI + "/Basic/UserDrawFeatures_SQL",
        headers: {
            "Authorization": localStorage["token"]
        },
        data: JSON.stringify(drawsaveobj),
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function (d) {
            var data = d;
            console.log(data);
            var onecasegeom = data.filter(o => o.drawsaveid === drawsaveid);
            onecasegeom.forEach(function (item, idx) {
                var f = GraphicTrans.GraphicFromString(item.geom, item.style);
                f.id = drawsaveid;
                map.e_getLayer("drawLyr").getSource().addFeature(f);
                if (onecasegeom.length-1 === idx) {
                    var extent = map.e_getLayer("drawLyr").getSource().getExtent();
                    map.getView().fit(extent, map.getSize());
                }
            });
            console.log(onecasegeom);
        },
        error: function (jqXHR, exception) {
            ajaxError(jqXHR, exception);
        }
    });
}

點選刪除按鈕後,執行 delUserDrawCase() 透過 UserDrawFeatures_SQL ,SQLType設為Del,進行刪除。

function delUserDrawCase(drawsaveid) {
    if (confirm("確定刪除此儲存圖形?")) {
        var drawsaveobj = {
            "SQLtype": "Del",
            "title": "",
            "info": "",
            "drawsaveid": drawsaveid,
            "features": []
        };
        $.ajax({
            type: "POST",
            url: config_OLMapWebAPI + "/Basic/UserDrawFeatures_SQL",
            headers: {
                "Authorization": localStorage["token"]
            },
            data: JSON.stringify(drawsaveobj),
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            success: function (d) {
                var data = d;
                console.log(data);
                draw.getUserDrawCaseList();
                alert("已刪除完成!");
            },
            error: function (jqXHR, exception) {
                ajaxError(jqXHR, exception);
            }
        });
    }
}

繪圖儲存後,顯示列表示意圖:
https://ithelp.ithome.com.tw/upload/images/20201004/20108631PG3rmDWFlw.png

4. GeoJSON Circle 問題

根據 GeoJSON技術文件 可以得知GeoJSON的格式並不包含Circle,因此若將畫圓圈的圖形存入,則會造成無法轉換為GeoJSON的問題存在,自然也無法還原圖形。
這部分我尚未實際下去debug解決,但有Google到不少解決方法,大家可以找找看。

5. 畫一隻Pokemon

太多隻了,只好找一隻顏色比較單調、比較好畫的神奇寶貝寶可夢「海豹球」當作範例,我知道很醜大家請見諒QAQ:
https://ithelp.ithome.com.tw/upload/images/20201004/20108631iUOUgmQwMo.png

將上述的圖案存到資料庫會長這樣:

https://ithelp.ithome.com.tw/upload/images/20201004/20108631Xv3wbiJPxb.png


小結

到了 Day26 終於完成了為期3天的繪圖功能的建置,WebGIS已完成了90%了,任何的功能都可以大概依照這種模式進行建立。
明天是最後一次的 不寫程式改來學知識 系列,身為一個毫無美感的工程師,若也缺乏精通前端介面的同事,前端UI/UX的套件是不可或缺的,明天來討論有哪些好用的圖表套件和RWD介面可供套用。


上一篇
Day 25. 如何在地圖上畫Pokemon #2:儲存繪圖API建立
下一篇
Day 27. 今天不寫程式改來學知識 #5:前端RWD與圖表套件
系列文
WebGIS入門學習 - 以Openlayers實作30

尚未有邦友留言

立即登入留言