我之前參與過一個與地圖展示相關的項目,是在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。
最終實現效果如下: