騰訊地圖點聚合開發-實現地圖找房功能
以下內容轉載自前端develop的文章《騰訊地圖實現地圖找房功能》
作者:前端develop
連結:https://juejin.im/post/6844903510614474759#comment
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
鏈家實現的效果
分析
地圖找房功能使用點聚合來實現的。官網示例如下:https://lbs.qq.com/javascript_v2/sample/overlay-markercluster.html
鏈家的地圖找房主要分為三層。第一層為市區層,比如南山、羅湖等;第二層為片區,比如南頭、科技園等;第三層則為小區。
因為第一層,第二層的資料沒有那麼多,這兩個介面都是把所有的資料一次返回給前端。但是第三層的資料量就非常的巨大了,鏈家採取的是返回部分資料,將前端頁面上顯示的最大經緯度以及最小經緯度傳給後臺,後臺再將篩選後的資料返回給前端。(介面地址大家可以使用 Chrome 的開發工具進行抓包,這裡需要注意的是鏈家的介面採用 jsonp 的形式,所以需要抓取 JS)
實現
首先需要新增騰訊地圖的API,這裡推薦使用非同步載入的方式。因為專案使用 Vue 進行開發的單頁應用,有可能使用者並沒有進入地圖找房的頁面,所以這裡建議在開啟地圖找房的頁面時新增騰訊地圖的API。
非同步載入需要避免一個重複載入的問題,即不管使用者是第幾次開啟地圖找房,地圖的 API 都是同一個。 這裡為了降低程式碼複雜度,沒有使用單例模式,具體的程式碼如下:
const TXMap = { map: undefined, // 地圖例項 // 非同步載入獲取api getApi (funName) { let script = document.createElement('script') script.type = 'text/javascript' script.src = `http://map.qq.com/api/js?v=2.exp&callback=${funName}` document.body.appendChild(script) } }
可以看到非同步載入就是動態加入 script 標籤,src 為騰訊地圖 api 的地址,src 包含一個 callback 引數,表示 js 載入完畢後會呼叫 funName 這個函式。添加了地圖 api 之後,window 物件會有一個 qq.maps 物件,我們可以用來判斷是否已經添加了 api,來避免重複新增 api。
接下來就是實現自定義覆蓋物這個方法了。還是參照官方文件:https://lbs.qq.com/javascript_v2/doc/overlay.html
const TXMap = { map: undefined, overlays: [], // 存放所有覆蓋物 sourceData: [], // 原始資料 listener: undefined, // 地圖縮放或平移的事件監聽器 getApi () {}, /* 前面已經宣告,此處省略 */ // 實現自定義覆蓋物 drawOverlay (options) { let _this = this // 下面有多個 window 物件的方法,避免 this 的指向問題 this.sourceData = options.data // 存放原始資料 // 繪製覆蓋物之前,清理之前繪製的覆蓋物 this.clearOverlays() // 如果 initMap 方法已經實現,那麼我們可以直接呼叫,否則需要進行定義 if (window.initMap === undefined) { window.initMap = function () {} // 繪製覆蓋物的具體實現 // 地圖 api 如果沒有引入則呼叫 getApi 方法,否則直接呼叫 initMap () window.qq === undefined ? this.getApi('initMap') : window.initMap() } else { window.initMap() } }, // 清除自定義覆蓋物 clearOverlays () { let overlay while (overlay = this.overlays.pop()) { overlay.onclick = null // 移除點選事件 overlay.parentNode.removeChild(overlay) // 移除 dom 元素 } }, // 在 Vue 元件的 beforeDestroy 呼叫,重置地圖,移除時間為監聽,避免記憶體洩漏 clearMap () { this.map = undefined if (this.listener) { window.qq.maps.event.removeListener(this.listener) } } }
這個地圖找房的架子到此就搭得差不多了,接下來就看看繪製覆蓋物的具體實現了,也就是 initMap 這個方法。
window.initMap = function () {
if (_this.map === undefined) {
// 地圖物件為undefined時, 需要進行地圖的繪製
_this.map = new window.qq.maps.Map(document.getElementById(options.containerId), {
// 初始化地圖中心
center: new window.qq.maps.LatLng(options.lat || 22.702, options.lng || 114.09),
// 初始化縮放級別
zoom: options.zoom || 10,
// 地圖最小縮放級別
minZoom: 10,
// 停用縮放控制元件
zoomControl: false,
// 停用地圖型別控制元件
mapTypeControl: false
})
// idle 事件, 地圖縮放或平移之後觸發該事件
_this.listener = window.qq.maps.event.addListener(_this.map, 'idle', () => {
// 獲取當前地圖可視範圍的最大最小經緯度
let bounds = _this.map.getBounds()
// 獲取當前地圖的縮放級別
let zoom = _this.map.getZoom()
// 呼叫 Vue 元件對 idle 事件的處理函式
options.callback && options.callback(bounds, zoom)
})
}
// 自定義覆蓋物
if (window.CustomOverlay === undefined) {
window.CustomOverlay = function (lat, lng, name, houseCount) {
// 呼叫地圖 api 計算出覆蓋物的位置
this.position = new window.qq.maps.LatLng(lat, lng)
this.name = name // 區域名
this.houseCount = houseCount // 房源數量
}
// 繼承 Overlay
window.CustomOverlay.prototype = new window.qq.maps.Overlay()
// 自定義覆蓋物建構函式,定義覆蓋為的 DOM 結構,DOM 結構,樣式大家可以根據需求自己繪製
window.CustomOverlay.prototype.construct = function () {
let div = this.div = document.createElement('div')
div.className = 'my-overlay' // 覆蓋物類名
// 覆蓋物 html 結構
this.div.innerHTML = `<p class="count" >${this.houseCount}<span>套</span></p><p class="name">${this.name}</p>`
//將dom新增到覆蓋物層,overlayMouseTarget的順序容器 5,此容器包含透明的滑鼠相應元素,用於接收Marker的滑鼠事件
this.getPanes().overlayMouseTarget.appendChild(div)
// 將 div 新增到 overlays,可以用以後續處理
_this.overlays.push(div)
// 定義覆蓋物的點選事件
let center = this.position
this.div.onclick = function () {
// 點選之後對地圖進行縮放以及平移
let zoom = _this.map.getZoom()
if (zoom < 13) {
_this.map.setCenter(center)
_this.map.setZoom(13)
} else if (zoom >= 13 && zoom < 15) {
_this.map.setCenter(center)
_this.map.setZoom(15)
}
}
}
// 實現 draw 介面來繪製 DOM 元素
window.CustomOverlay.prototype.draw = function () {
let overlayProjection = this.getProjection()
// 獲取覆蓋物容器的相對畫素座標
let pixel = overlayProjection.fromLatLngToDivPixel(this.position)
let divStyle = this.div.style
// 根據 DOM 元素調整定位的位置
divStyle.top = pixel.y - 53 + 'px'
divStyle.left = pixel.x - 30 + 'px'
}
}
// 根據介面資料繪製覆蓋物
if (_this.sourceData.length > 0) {
_this.sourceData.map(item => {
let customOverlay = new window.CustomOverlay(item.latitude, item.longitude, item.name, item.house_count)
customOverlay.setMap(_this.map)
})
}
}
至此,地圖找房對繪製覆蓋物方法的封裝就完成了,接下來只需要將 TXMap 暴露出去,然後在 Vue 元件中進行引入,之後再向下面的方法使用即可
TXMap.drawOverlay({
containerId: 'map-box',
data: res.data
})
實現效果
這個例子用了鏈家的資料做了兩層,大家可以根據自己的需要進行修改。
專案地址: GitHub
產品推廣
本文實現地圖找房功能使用的是我們2D版JSAPI,目前我們已經上線3D版地圖API-JavaScript API GL。