leaflet集成google map搜索、支持顯示菜單功能(支持回車選中第一項,自定義菜單)

我之前參與過一個與地圖展示相關的項目,是在react項目中使用leaflet實現的,當時項目中集成了地形、衛星、交通等圖層,加入了一些停車場、交通信號燈等數據信息。當時項目中使用的是react-leaflet,但是我們知道leaflet有很多plugins可以使用,但是這些插件很多沒有對應的react版本,有些插件也不滿足我們的需求,所以在項目中修改了一些插件。

Leaflet-gplaces-autocomplete是將google地點搜索功能添加到leaflet地圖中的一個插件,對於實現我們的需求有兩個問題:1、不支持回車搜索位置列表第一項;2、不支持自定義菜單功能。方便理解上述兩個問題,我們截圖看一下google地圖的實現:

1、回車時搜索列表的第一項

2、點擊搜索框左側的菜單可以展示菜單模塊(包含豐富的功能)

下面是爲實現該功能修改後的Leaflet-gplaces-autocomplete插件介紹。

目錄結構和各文件爲:

Index.js

export {default as AutoSearch} from './AutoSearch';

 AutoSearch.js  該文件是對插件的react封裝。

import {MapControl} from 'react-leaflet';
import './leaflet-google-places-autocomplete';
import './leaflet-gplaces-autocomplete.css';


export default class AutoSearch extends MapControl {
  static propTypes = {};

  componentWillMount() {
    const {handleClickSearchIcon} = this.props;
    this.leafletElement = new window.L.Control.GPlaceAutocomplete({
      clickIcon: handleClickSearchIcon,
      callback(place, map) {
        if (!place.geometry) {
          return;
        }
        map.panTo([
          place.geometry.location.lat(),
          place.geometry.location.lng()
        ]);
        /* map.setZoom(8);*/
      }
    });
  }
}

 leaflet-google-places-autocomplete.js

(function() {
  const L = window.L;
  L.GPlaceAutocomplete = {};

  L.Control.GPlaceAutocomplete = L.Control.extend({
    options: {
      position: 'topleft',
      prepend: true,
      collapsed_mode: false,
      autocomplete_options: {}
    },

    collapsedModeIsExpanded: true,

    autocomplete: null,
    icon: null,
    searchBox: null,


    _ready: null,
    _GAPIPromise: null,

    initialize(options) {
      if (options) {
        L.Util.setOptions(this, options);
      }
      if (!this.options.callback) {
        this.options.callback = this.onLocationComplete;
      }


      this._ready = !!window.google && !!window.google.maps && !!window.google.maps.Map;
      this._GAPIPromise = this._ready ? Promise.resolve(window.google) : new Promise(((resolve, reject) => {
        let checkCounter = 0;
        let intervalId = null;
        intervalId = setInterval(() => {
          if (checkCounter >= 10) {
            clearInterval(intervalId);
            return reject(new Error('window.google not found after 10 attempts'));
          }
          if (!!window.google && !!window.google.maps && !!window.google.maps.Map) {
            clearInterval(intervalId);
            return resolve(window.google);
          }
          checkCounter++;
        }, 500);
      }));


      this._buildContainer();
    },

    _buildContainer() {
      this._GAPIPromise.then(() => {
        this._ready = true;

        // build structure
        this.container = L.DomUtil.create('div', 'leaflet-gac-container leaflet-bar');
        const searchWrapper = L.DomUtil.create('div', 'leaflet-gac-wrapper');
        this.searchBox = L.DomUtil.create('input', 'leaflet-gac-control');
        this.searchIcon = L.DomUtil.create('div', 'leaflet-search-icon');
        this.searchIcon.innerHTML = '<span class="icon-unfold icon-menu_unfold"></span>';
        /*  this.autocomplete = new google.maps.places.Autocomplete(this.searchBox, this.options.autocomplete_options);*/
        this.autocomplete = new window.google.maps.places.SearchBox(this.searchBox);
        // if collapse mode set - create icon and register events
        if (this.options.collapsed_mode) {
          this.collapsedModeIsExpanded = false;

          this.icon = L.DomUtil.create('div', 'leaflet-gac-search-btn');
          L.DomEvent
            .on(this.icon, 'click', this._showSearchBar, this);

          this.icon.appendChild(
            L.DomUtil.create('div', 'leaflet-gac-search-icon')
          );

          searchWrapper.appendChild(
            this.icon
          );
          L.DomUtil.addClass(this.searchBox, 'leaflet-gac-hidden');
        }

        searchWrapper.appendChild(
          this.searchIcon
        );
        searchWrapper.appendChild(
          this.searchBox
        );
        // create and bind autocomplete
        this.container.appendChild(
          searchWrapper
        );
      });
    },

    //* **
    // Collapse mode callbacks
    //* **

    _showSearchBar() {
      this._toggleSearch(true);
    },

    _hideSearchBar() {
      // if element is expanded, we need to change expanded flag and call collapse handler
      if (this.collapsedModeIsExpanded) {
        this._toggleSearch(false);
      }
    },

    _toggleSearch(shouldDisplaySearch) {
      if (shouldDisplaySearch) {
        L.DomUtil.removeClass(this.searchBox, 'leaflet-gac-hidden');
        L.DomUtil.addClass(this.icon, 'leaflet-gac-hidden');
        this.searchBox.focus();
      } else {
        L.DomUtil.addClass(this.searchBox, 'leaflet-gac-hidden');
        L.DomUtil.removeClass(this.icon, 'leaflet-gac-hidden');
      }
      this.collapsedModeIsExpanded = shouldDisplaySearch;
    },

    //* **
    // Default success callback
    //* **

    onLocationComplete(place, map) {
      // default callback
      if (!place.geometry) {
        // eslint-disable-next-line no-alert
        alert('Location not found');
        return;
      }
      map.panTo([
        place.geometry.location.lat(),
        place.geometry.location.lng()
      ]);
    },

    onAdd() {
      // stop propagation of click events
      L.DomEvent.addListener(this.container, 'click', L.DomEvent.stop);
      L.DomEvent.addListener(this.searchIcon, 'click', this.options.clickIcon);
      L.DomEvent.disableClickPropagation(this.container);
      if (this.options.collapsed_mode) {
        // if collapse mode - register handler
        this._map.on('dragstart click', this._hideSearchBar, this);
      }
      return this.container;
    },

    addTo(map) {
      this._GAPIPromise.then(() => {
        this._ready = true;
        this._map = map;

        const container = this._container = this.onAdd(map);


        const pos = this.options.position;


        // eslint-disable-next-line no-underscore-dangle
        const corner = map._controlCorners[pos];

        L.DomUtil.addClass(container, 'leaflet-control');
        if (this.options.prepend) {
          corner.insertBefore(container, corner.firstChild);
        } else {
          corner.appendChild(container);
        }

        const callback = this.options.callback;
        const self = this;
        /*  google.maps.event.addListener(this.autocomplete, "place_changed", function () {
            callback(_this.autocomplete.getPlace(), map);
          });*/
        this.autocomplete.addListener('places_changed', () => {
          const places = self.autocomplete.getPlaces();
          if (places && places.length) {
            callback(places[0], map);
          }
        });
        return this;
      });
    }


  });
}());

 leaflet-gplaces-autocomplete.css

.leaflet-gac-wrapper {
    width: 260px;
    height: 48px;
    border-radius: 2px;
    background-color: #fff;
    -webkit-box-shadow: 0 2px 5px 0 rgba(0,0,0,.5);
    box-shadow: 0 2px 5px 0 rgba(0,0,0,.5);
}

.leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar{
    border: none!important;
}

.leaflet-control-container .leaflet-gac-control {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    border: 1px solid transparent;
    width: calc(~'100% - 50px');
    height: 100%;
    padding: 12px;
    font-size: 14px;
    outline: none;
    -o-text-overflow: ellipsis;
    text-overflow: ellipsis;
}

.leaflet-control-container .leaflet-right .leaflet-gac-control {
    position: absolute;
    right: 0;
    transition: width .3s ease .15s;
}

.leaflet-control-container .leaflet-gac-control:focus {
    outline: none;
}

.leaflet-control-container .leaflet-gac-search-btn {
    background: #fff;
    width: 30px;
    height: 30px;
    border-radius: 4px;
}

.leaflet-control-container .leaflet-gac-search-btn .leaflet-gac-search-icon {
    cursor: pointer;
    width: 100%;
    height: 100%;
    background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABOUlEQVQ4T6XTLUgmQRgH8N+Ligd+FOu1ww+wKPhxlwxXxHYnqCAGQZtgMBgU4eWaXLhyCgYxiAYVk6igJotgEaNiNYgGL6kHJwOzsO+yGzw3zszzm5nnP1vyzq+UU9+JUbTiCWdYw13eXmmgCr8wlbPwERPYys6lgVA8jSvM4RQfMIQF1KIfR2kkAdpxiRv04CGzUx9OcI02/EvmE+AH5jGG9YK+bmMQ3TjPApsYQXPcJc+Ywc/Y4I0ssIpxdOCi4ATl2Ivv2M0Ck1jBImZzgOrYoxZ8xG0WqI9Hb4pX2UkhNViKMe5jIC+FMPYVezGu4xhjHb7hUyx6wXDeFRK0C79jlMnYX4SmhZfZiwok7ymHwpBGyPs5RnaPRhzicxopAop+sYAc4Av+BPStQIAbsByffPl/gIrTvQLbJDoR8K3H6QAAAABJRU5ErkJggg==") no-repeat center center;
}

.leaflet-control-container .leaflet-gac-hidden {
    opacity: 0;
    width: 0;
    height: 0;
    overflow: hidden;
    transition: width .3s ease .15s;
}
.leaflet-search-icon{
    width: 50px;
    height: 48px;
    font-size: 16px;
    padding-left: 16px;
    line-height: 48px;
    vertical-align: top;
    display: inline-block;
    cursor: pointer;
}
.icon-unfold{
    display: inline-block;
    font-size: 24px;
    vertical-align: middle;
    padding-bottom: 4px;
}

主要修改爲在_buildContainer方法中添加了searchBox和searchIcon及autocomplete的實現

this.searchBox = L.DomUtil.create('input', 'leaflet-gac-control');
this.searchIcon = L.DomUtil.create('div', 'leaflet-search-icon');
this.searchIcon.innerHTML = '<span class="icon-unfold icon-menu_unfold"></span>';
/*  this.autocomplete = new google.maps.places.Autocomplete(this.searchBox, this.options.autocomplete_options);*/
this.autocomplete = new window.google.maps.places.SearchBox(this.searchBox);

在onAdd方法中添加如下事件的綁定:

L.DomEvent.addListener(this.searchIcon, 'click', this.options.clickIcon);

其中clickIcon是在AutoSearch中傳遞的自定義事件handleClickSearchIcon。

最終實現效果如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章