訂餐系統之地圖訂餐
離上一篇部落格已經是1月有餘了,這中間清楚的認識到自己幾年的程式設計下來(大學4年,入行3載),能拿出手的東西真的屈指可數,也許真的是,一直以來都是為實現功能來寫程式碼,所以終無大的突破!每每得空便思考自己到底差在哪?是不瞭解框架?於是學習EF;是不瞭解node.js?安裝後,寫了些demo; 是沒學習新知識?於是學了下WFP... 然而,瞭解這些東西並沒讓自己有所收穫,反而越發不知所措了。思考還在繼續,”探索“還在繼續... 於是有一天看到了設計模式,看到《大話設計模式》 才明白自己差在哪,差在對面向物件的理解!於是才開始正式的去了解什麼是面向物件,為了加深理解,又細細看了《你必須知道的.net(第二版)》中的OO大原則,OO大智慧,OO之美...而後,再一次對《大話設計模式》中內容細細品味...這時,我覺得自己才開始真正的沉澱了!羅嗦了半天,下面開始今天的正文吧!
所謂地圖訂餐就使用者直接在地圖上確定自己的位置後,搜尋出附近商家後,直接選擇商家開始訂餐。目前市面上也有很多這樣的功能,下面我就把自己實現方式介紹下吧。上篇部落格《訂餐系統之按距離[根據經緯度]排序、搜尋》中所提到的功能,在地圖訂餐中就是一個應用。
注:以下程式碼都是。google API變成3.0後,語法變了太多了,大部分專案都變成百度地圖了。
商家定位所謂定位,就是在地圖上標註商家的位置,成為使用者搜尋的來源,如圖1:
圖1
定們程式碼比較簡單,js部分程式碼如下:
<script type="text/javascript"> var gzoom = 15; var marker = null; var map = new BMap.Map("map_canvas"); // 建立地圖例項 map.enableScrollWheelZoom(); var myGeo = new BMap.Geocoder(); var _lat = parseFloat($("#hidLat").val()); var _lng = parseFloat($("View Code 商家設定配送範圍#hidLng").val()); var initpoint = new BMap.Point(_lng, _lat); // 建立點座標 //圖示 var myIcon = new BMap.Icon("http://www.ihangjing.com/images/marker50.png", new BMap.Size(20, 34), { anchor: new BMap.Size(10, 0) }); marker = new BMap.Marker(initpoint, { icon: myIcon }); map.addOverlay(marker); marker.enableDragging(); marker.setTitle("拖動修改位置"); map.centerAndZoom(initpoint, gzoom); // 初始化地圖,設定中心點座標和地圖級別 map.addControl(new BMap.NavigationControl()); //縮放工具 EventWrapper.addListener(map, "click", function(e) { map.clearOverlays(); initpoint = e.point; map.removeOverlay(marker); marker = new BMap.Marker(initpoint, { icon: myIcon }); map.addOverlay(marker); marker.enableDragging(); marker.openInfoWindow(infoWindow); setLatLng(initpoint); marker.addEventListener("dragend", function(e) { initpoint = e.point; marker = new BMap.Marker(initpoint); this.openInfoWindow(infoWindow); setLatLng(initpoint); }); marker.addEventListener("dragstart", function(e) { map.closeInfoWindow() }); }); marker.addEventListener("dragend", function(e) { initpoint = e.point; marker = new BMap.Marker(initpoint); this.openInfoWindow(infoWindow); setLatLng(initpoint); }); marker.addEventListener("dragstart", function(e) { map.closeInfoWindow() }); var opts = { width: 250, // 資訊視窗寬度 height: 50 // 資訊視窗高度 } var winhtml = " <div><p>確定地圖位置後,點選按鈕“確認位置”進行儲存</p>"; winhtml += "<p style=\" float:right; padding-right:10px;\"><input type=\"button\" value=\"確認位置\" onclick='map.closeInfoWindow()' /></p></div>"; var infoWindow = new BMap.InfoWindow(winhtml, opts); // 建立資訊視窗物件 function setLatLng(point) { document.getElementById("hidLat").value = point.lat; document.getElementById("hidLng").value = point.lng; return true; } function setPlace() { var hfcity = $("#hfcity").val(); var address = document.getElementById("keyaddress").value.trim(); var local = new BMap.LocalSearch(hfcity, { renderOptions: { map: map, autoViewport: true, selectFirstResult: false } }); local.search(address); } </script>
所謂設定配送範圍,就在地圖上通過標註,繪製出一個多邊形區域,如圖2:
圖2
以下為繪製多邊形,js部分程式碼。頁面中只多了幾個HiddenField,用來儲存座標、原多邊形資訊以及一個用於放地圖的div。
<script type="text/javascript"> var gzoom = 15; var points = new Array(); var markers = new Array(); var map = new BMap.Map("map"); // 建立地圖例項 map.enableScrollWheelZoom(); var myPolygon = null; var count = 0; var _lat = parseFloat($("#hidLat").val()); var _lng = parseFloat($("#hidLng").val()); var initpoint = new BMap.Point(_lng, _lat); // 建立點座標 map.centerAndZoom(initpoint, gzoom); // 初始化地圖,設定中心點座標和地圖級別 map.addControl(new BMap.NavigationControl()); //縮放工具 var myIcon = new BMap.Icon("http://www.ihangjing.com/images/marker50.png", new BMap.Size(20, 34), { anchor: new BMap.Size(10, 0) }); marker = new BMap.Marker(initpoint, { icon: myIcon }); map.addOverlay(marker); //map.addEventListener("click", mapclick); EventWrapper.addListener(map, "click", mapclick); //繪製多邊形 function drawPolygon() { if (myPolygon) { map.removeOverlay(myPolygon); } points.length = 0; //從marksers填充pints陣列 for (i = 0; i < markers.length; i++) { points.push(markers[i].getPosition()); } // points.push(markers[0].getPosition()); myPolygon = new BMap.Polygon(points, { strokeColor: "red", strokeWeight: 1, strokeOpacity: 1 }); map.addOverlay(myPolygon); } function mapclick(target) { //flag == 0表示點選的是標註 //flag > 0表示點選的是地圖 var flag = target.pixel.x; if (target && flag > 0) { count++ var myIcon = new BMap.Icon("http://www.ihangjing.com/images/mm_20_red.png", new BMap.Size(14, 22), { anchor: new BMap.Size(7, 22) }); var _marker = new BMap.Marker(target.point, { icon: myIcon }); map.addOverlay(_marker); markers.push(_marker); _marker.enableDragging(); _marker.setTitle("point" + count + " lat:" + target.point.lat); _marker.addEventListener("dragging", function(e) { drawOverlay(); }); _marker.addEventListener("dragend", function(e) { drawOverlay(); }); _marker.addEventListener("dragstart", function(e) { drawOverlay(); }); // Click listener to remove a marker _marker.addEventListener("click", function() { var n = 0; for (n = 0; n < markers.length; n++) { if (markers[n] == _marker) { map.removeOverlay(markers[n]); break; } } // 指定從陣列中移除元素的開始位置,這個位置是從 0 開始計算的。 // 刪除從標n開始的,一個元素 markers.splice(n, 1); if (markers.length == 0) { count = 0; } else { count = markers.length; drawOverlay(); } }); drawPolygon(); } } function drawOverlay() { drawPolygon(); } //獲取每個點的座標,以: lat1,lng1|lat2,lng2..儲存 function GetPolygon() { var html = ""; for (var i = 0; i < points.length; i++) { html += points[i].lat + "," + points[i].lng + "|"; } var temp = html.replace(/\|$/, ""); $("#hidPolygon").val(temp); if (html == "") { alert("請設定配置範圍"); return false; } return true; } //初始化多邊形 function initPolygon() { var hidPolygon = $("#hidPolygon").val(); if (hidPolygon == "") { return; } var oldPolygon = hidPolygon.split('|'); for (var i = 0; i < oldPolygon.length; i++) { count++; var latlng = oldPolygon[i].split(','); var _mypoint = new BMap.Point(latlng[1], latlng[0]); var myIcon = new BMap.Icon("http://www.ihangjing.com/images/mm_20_red.png", new BMap.Size(14, 22), { anchor: new BMap.Size(7, 22) }); var _marker = new BMap.Marker(_mypoint, { icon: myIcon }); map.addOverlay(_marker); markers.push(_marker); _marker.enableDragging(); _marker.setTitle("point" + count + " lat:" + _mypoint.lat); count++; addinitmarker(_marker); } drawPolygon(); } function addinitmarker(_marker_init) { _marker_init.addEventListener("dragging", function(e) { drawOverlay(); }); _marker_init.addEventListener("dragend", function(e) { drawOverlay(); }); _marker_init.addEventListener("dragstart", function(e) { drawOverlay(); }); // Click listener to remove a marker EventWrapper.addListener(_marker_init, "click", function() { var n = 0; for (n = 0; n < markers.length; n++) { if (markers[n] == _marker_init) { map.removeOverlay(markers[n]); break; } } // 指定從陣列中移除元素的開始位置,這個位置是從 0 開始計算的。 // 刪除從標n開始的,一個元素 markers.splice(n, 1); if (markers.length == 0) { count = 0; } else { count = markers.length; drawOverlay(); } }); } initPolygon();; </script>View Code
設定好商家的位置,範圍後,使用者就可以在地圖上定好自己的位置,開始搜尋商家。搜尋分兩種方式:1.以當前使用者地址為中心,搜尋N公里內的商家;2.搜尋當前座標在商家設定的多邊形內的商家。以下的地圖拉框搜尋以及按配送範圍搜尋就分別是用這種方式來實現的。
地圖拉框搜尋所謂拉框搜尋,就是在地圖上拉出一個框,只搜尋這個框內的商家。當然拉出的是一個長方形,搜尋是轉換成一個圓,按半徑搜尋。如圖3:
圖3
拉框部分主要用了api之RectangleZoom.js 只是修改了完成時的事件,這個是從一個論壇中認識的朋友那得來的---程式設計師真是大多數是好人吶!在548行左右,程式碼如下:
var startlng = Math.min(northEastPoint.lng,southWestPoint.lng); var endlng = Math.max(northEastPoint.lng,southWestPoint.lng); var startlat = Math.min(northEastPoint.lat,southWestPoint.lat); var endlat = Math.max(northEastPoint.lat,southWestPoint.lat); var startlgt = endlng - (endlng - startlng)/2; var startlat = endlat - (endlat - startlat)/2; var radius = Math.max((endlng - startlng)/2,(endlat - startlat)/2)*111; GetMapInfo('1',startlgt,startlat,radius);//這裡呼叫自己獲取資料方法修改部分程式碼
然後再加上API之RichMarker_min.js,這個使用比較簡單,基本程式碼如下:
//地圖上新增marker //引數說明:togoname , picture,address,reviewcont,BookPercapita(人均).shopid function createMarker(index, togoname, picture, address, reviewcont, BookPercapita, shopid, point) { var infohtml = ""; var htm1 = "<div class='RichmarkerContainer' title=\"" + togoname + "\"><h4 class='Richmarker' id=\"h4_" + parseInt(index) + "\"><span class=\"numberL\">" + parseInt(index) + "</span></h4><lable id=\"name_" + parseInt(index) + "\" class='markerlabel'></lable></div>"; var shopmarker = new BMapLib.RichMarker(htm1, point); var temptipinfo = getInfohtml(index, togoname, picture, address, reviewcont, BookPercapita, shopid); $("#tipcontainer").append("<div id=\"click_list_infohtml_" + index + "\">" + temptipinfo + "</div>"); shopmarker.addEventListener("click", function(e) { var shopinfohtml = getInfohtml(index, togoname, picture, address, reviewcont, BookPercapita, shopid); var shopinfoWindow = new BMap.InfoWindow(shopinfohtml); map.openInfoWindow(shopinfoWindow,point); }); allmarkers.push(shopmarker); points.push(point); return shopmarker; }生成圖示程式碼
最終形成了,現在的介面
按配送範圍搜尋這部分其實主要就是用一個射線法,判斷點是否在多邊形內,這是我頭頭寫的,不過不怎麼準確,在這裡我也帖下程式碼,也希望哪個朋友有更精確的方法提出來。
using System; using System.Collections; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml.Linq; using System.Reflection; using System.Text; using System.Drawing; using System.Drawing.Drawing2D; public static class hjmap { const double INFINITY = 1e10; const double ESP = 1e-5; const int MAX_N = 1000; public struct XPoint { public double x, y; public XPoint(double _x, double _y) { x = _x; y = _y; } }; public struct LineSegment { public XPoint pt1, pt2; }; /// 判斷點在多邊形內 /// <summary> /// 如果點在多邊形內: 返回0 /// 如果點在多邊形邊上: 返回1 /// 如果點在多邊形外: 返回2 /// </summary> /// <param name="polygon">多邊形頂點</param> /// <param name="point">當前點</param> /// <returns></returns> public static int InPolygon(XPoint[] polygon, XPoint point) { int n = polygon.Length; int count = 0; LineSegment line; line.pt1 = point; line.pt2.y = point.y; line.pt2.x = -INFINITY; for (int i = 0; i < n; i++) { //得到多邊形的一條邊 LineSegment side; side.pt1 = polygon[i]; side.pt2 = polygon[(i + 1) % n]; if (IsOnline(point, side)) { return 1; } // 如果side平行x軸則不作考慮 if (Math.Abs(side.pt1.y - side.pt2.y) < ESP) { continue; } if (IsOnline(side.pt1, line)) { if (side.pt1.y > side.pt2.y) count++; } else if (IsOnline(side.pt2, line)) { if (side.pt2.y > side.pt1.y) count++; } else if (Intersect(line, side)) { count++; } } if (count % 2 == 1) { return 0; } else { return 2; } } // 計算叉乘 |P0P1| × |P0P2| public static double Multiply(XPoint p1, XPoint p2, XPoint p0) { return ((p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y)); } // 判斷線段是否包含點point public static bool IsOnline(XPoint point, LineSegment line) { return ((Math.Abs(Multiply(line.pt1, line.pt2, point)) < ESP) && ((point.x - line.pt1.x) * (point.x - line.pt2.x) <= 0) && ((point.y - line.pt1.y) * (point.y - line.pt2.y) <= 0)); } // 判斷線段相交 public static bool Intersect(LineSegment L1, LineSegment L2) { return ((Math.Max(L1.pt1.x, L1.pt2.x) >= Math.Min(L2.pt1.x, L2.pt2.x)) && (Math.Max(L2.pt1.x, L2.pt2.x) >= Math.Min(L1.pt1.x, L1.pt2.x)) && (Math.Max(L1.pt1.y, L1.pt2.y) >= Math.Min(L2.pt1.y, L2.pt2.y)) && (Math.Max(L2.pt1.y, L2.pt2.y) >= Math.Min(L1.pt1.y, L1.pt2.y)) && (Multiply(L2.pt1, L1.pt2, L1.pt1) * Multiply(L1.pt2, L2.pt2, L1.pt1) >= 0) && (Multiply(L1.pt1, L2.pt2, L2.pt1) * Multiply(L2.pt2, L1.pt2, L2.pt1) >= 0)); } /// 判斷點在多邊形內 /// <summary> /// 如果點在多邊形內: 返回0 /// 如果點在多邊形邊上: 返回1 /// 如果點在多邊形外: 返回2 /// </summary> /// <param name="polygon">定點集合,以30.192353,120.164766|30.192712,120.175851|30.189668,120.170192的方式存在</param> /// <param name="point">當前點</param> /// <returns></returns> public static int InPolygon(string _lat, string _lng, string _polygon) { XPoint mypoint = new XPoint(); mypoint.x = Convert.ToDouble(_lat); mypoint.y = Convert.ToDouble(_lng); string Polygon = _polygon; string[] PolygonArray = Polygon.Split('|'); hjmap.XPoint[] curvePoints = new hjmap.XPoint[PolygonArray.Length - 1]; for (int i = 0; i < PolygonArray.Length - 1; i++) { string[] point_value = PolygonArray[i].Split(','); curvePoints[i] = new hjmap.XPoint((Convert.ToDouble(point_value[0])), (Convert.ToDouble(point_value[1]))); } int rs = InPolygon(curvePoints, mypoint); return rs; } }判斷點是否在多邊形內
以上便是我們系統中地圖訂餐部分的內容了,也有做這塊的朋友,大家可以交流下。
成為一名優秀的程式設計師!