过滤谷歌地图自定义覆盖取决于他们是否落在当前的地图边界

Filter google maps custom overlays depending on whether they fall within the current map bounds

本文关键字:边界 地图 是否 自定义 谷歌地图 覆盖 取决于 他们是 他们 过滤      更新时间:2023-09-26

我正在努力寻找在react-redux项目中呈现自定义谷歌地图标记(覆盖)的适当方法。我有的只是一个显示地图和搜索框的页面。当有人搜索一个地方并且找到了这个地方时,这会触发map idle事件,然后我更新地图边界并搜索位置信息并将它们保存在redux存储中。然后取回带有当前地图边界和城市名称的数据。当数据到达时,过滤列表(过滤将在未来的后端,这意味着服务器将发送已经过滤的列表,落在当前视图中),并为地图上的每个列表呈现自定义覆盖。

映射空闲事件:

1)更新地图边界和搜索的地名

从服务器获取一些数据(json格式)

3)过滤器列表,所以我们可以只渲染那些位置落在当前视口(地图边界)

4)为每个列表呈现自定义覆盖

对于每一个map idle事件,这似乎发生在你缩放或平移地图之后,整个过程的更新,提取,过滤和渲染重复。

到目前为止,我所做的是项目正在工作,直到React需要确定哪些覆盖应该被删除,哪些应该重新绘制。

实际上什么是不正确的工作现在是当可见的列表数组更新,React只是删除列表是在数组的末尾(从最后一个索引到0),而不是正确的一个(一个的位置是在视窗之外)。

有时候,如果你已经搜索了一个地方,并在地图上玩了一点,并试图搜索另一个地方,新的地方叠加没有正确定位。相反,它们离地图视窗很远。

我对所有的Ract, Redux和Google Maps Api技术都比较陌生,所以我意识到我可能做了一些非常愚蠢的事情。我希望这里有人能给我指出正确的方向。我搜遍了整个网络,也找不到一个合适的答案。我发现了一些关于如何创建自定义覆盖和如何为谷歌地图创建react组件的有用信息,我也知道有几个好的npm模块可以做我想要的工作(比如这个:react-google-maps和这个:google-map-react),但他们都有自己的问题,他们太复杂了我想要实现的。

我很抱歉在这里粘贴了所有的代码,但我不确定如何在jsbin或类似的代码bin中表示整个项目环境。请让我知道,如果我需要使这样一个代码工作的例子,我会尝试。

这是我现在的代码。当然,这只是问题中重要的部分:

map组件

import React, { PropTypes, Component } from 'react';
import SearchBar from '../SearchBar';
import OverlayViewComponent from '../OverlayViewComponent';
import OverlayViewContent from '../OverlayViewContent';
import mapOptions from './cfg';
import MapStyles from './map.scss';
const propTypes = {
    getListings: PropTypes.func.isRequired,
    updateMapState: PropTypes.func.isRequired,
    visibleListings: PropTypes.array.isRequired,
};
class GoogleMap extends Component {
    constructor() {
        super();
        this._onMapIdle = this._onMapIdle.bind(this);
        this.onPlacesSearch = this.onPlacesSearch.bind(this);
    }
    _initMap(mapContainer) {
        // Create a new map
        this._map = new google.maps.Map(mapContainer, mapOptions);
        this._bindOnMapIdleEvent();
    };
    _bindOnMapIdleEvent() {
        // Attach idle event listener to the map
        this._map.addListener('idle', this._onMapIdle);
    }
    _onMapIdle() {
        const { updateMapState, getListings } = this.props;
        if (this._searchedPlace) {
            console.log('ON MAP IDLE');
            let mapBounds = this._map.getBounds().toJSON();
            updateMapState(mapBounds, this._searchedPlace);
            getListings();
        }
    };
    onPlacesSearch(searchedPlace) {
        if (searchedPlace.name !== '' && searchedPlace.geometry !== null) {
            // Clear out the old marker if present.
            if (this._searchedPlaceMarker) {
                this._searchedPlaceMarker.setMap(null);
                this._searchedPlaceMarker = null;
            }
            let bounds = new google.maps.LatLngBounds();
            // Create a marker for the searched place.
            this._searchedPlaceMarker = new google.maps.Marker({
                map: this._map,
                title: searchedPlace.name,
                position: searchedPlace.geometry.location
            });
            if (searchedPlace.geometry.viewport) {
                // Only geocodes have viewport.
                bounds.union(searchedPlace.geometry.viewport);
            } else {
                bounds.extend(searchedPlace.geometry.location);
            }
            // Save currently searchedPlace into the class local variable
            this._searchedPlace = searchedPlace;
            // Set map so it contains the searchedPlace marker (Ideally it should be only one)
            this._map.fitBounds(bounds);
        } else {
            return;
        }
    }
    componentDidMount() {
        // When component is mounted, initialise the map
        this._initMap(this._mapContainer);
    };
    shouldComponentUpdate(nextProps) {
        if (nextProps.visibleListings.length == this.props.visibleListings.length) {
            return false;
        } else {
            return true;
        }
    };
    componentWillUnmount() {
        google.maps.event.clearInstanceListeners(this._map);
    };
    render() {
        console.log('GOOGLEMAP RENDER');
        return (
            <div id="mapContainer">
                <div id="mapCanvas" ref={(mapContainer) => this._mapContainer = mapContainer}></div>
                <SearchBar onPlacesSearch={this.onPlacesSearch} />
                {
                    this.props.visibleListings.map((listing, index) => {
                        return (
                            <OverlayViewComponent key={index} mapInstance={this._map} position={listing.geo_tag}>
                                <OverlayViewContent listingData={listing} />
                            </OverlayViewComponent>
                        );
                    })
                }
            </div>
        );
    }
};
GoogleMap.propTypes = propTypes;
export default GoogleMap;

OverlayView组件

import React, { PropTypes, Component } from 'react';
import OverlayView from './utils/overlayViewHelpers';
const propTypes = {
    position: PropTypes.array.isRequired,
    mapInstance: PropTypes.object.isRequired,
};
class OverlayViewComponent extends Component {
    componentDidMount() {
        this._overlayInstance = new OverlayView(this.props.children, this.props.position, this.props.mapInstance);
    };
    componentWillUnmount() {
        this._overlayInstance.setMap(null);
    };
    render() {
        return null;
    }
};
OverlayViewComponent.propTypes = propTypes;
export default OverlayViewComponent;

OverlayView类

import ReactDOM from 'react-dom';
const EL_WIDTH = 30;
const EL_HEIGHT = 35;
function OverlayView(element, position, map) {
    this.overlayContent = element;
    this.point = new google.maps.LatLng(position[0], position[1]);
    this.setMap(map);
}
OverlayView.prototype = Object.create(new google.maps.OverlayView());
OverlayView.prototype.constructor = OverlayView;
OverlayView.prototype.onAdd = function() {
    console.log('onAdd');
    this.containerElement = document.createElement('div');
    this.containerElement.style.position = 'absolute';
    this.containerElement.style.width = EL_WIDTH + 'px';
    this.containerElement.style.height = EL_HEIGHT + 'px';
    let panes = this.getPanes();
    panes.overlayMouseTarget.appendChild(this.containerElement);
    ReactDOM.render(this.overlayContent, this.containerElement);
};
OverlayView.prototype.draw = function() {
    console.log('draw');
    if (this.containerElement) {
        let projection = this.getProjection();
        let projectedLatLng = projection.fromLatLngToDivPixel(this.point);
        console.log(projectedLatLng);
        this.containerElement.style.top = projectedLatLng.y - EL_HEIGHT + 'px';
        this.containerElement.style.left = projectedLatLng.x - Math.floor(EL_WIDTH / 2) + 'px';
    }
};
OverlayView.prototype.onRemove = function() {
    console.log('onRemove');
    let parentEl = this.containerElement.parentNode;
    parentEl.removeChild(this.containerElement);
    ReactDOM.unmountComponentAtNode(this.containerElement);
};
export default OverlayView;

OverlayView Content组件

import React, { PropTypes } from 'react';
import markerIcon from '../../../images/icon-marker.png';
const propTypes = {
    listingData: PropTypes.object.isRequired,
};
const OverlayViewContent = (listingData) => {
    console.log('OverlayViewContent render');
    return (
        <div className="customIcon">
            <img src={markerIcon} title={listingData.where} />
        </div>
    );
};
OverlayViewContent.propTypes = propTypes;
export default OverlayViewContent;

这可能会对你有所帮助,尽管这个应用程序使用的是GMaps。

我想我已经找到问题的根源了。

感谢这篇文章,更具体地说,来自Sebastien Lorber的评论。他让我更彻底地看了一下react文档中关于键属性

的内容。

如果我在遍历visblelistings数组时向react组件添加一个更独特的,似乎一切都如预期的那样工作。问题是,我使用数组索引的组件键。这样,当visblelistings数组更新时,需要卸载的覆盖组件的键从数组中删除,react不知道应该卸载哪个组件。因此react总是从数组中删除最后一个覆盖组件。