UNPKG

react-leaflet-marker-layer

Version:

A custom layer for react-leaflet that makes plotting react components simple

178 lines (147 loc) 4.7 kB
import React from 'react'; import ReactDOM from 'react-dom'; import map from 'lodash.map'; import forEach from 'lodash.foreach'; import pluck from 'lodash.pluck'; import min from 'lodash.min'; import max from 'lodash.max'; import isNumber from 'lodash.isnumber'; import filter from 'lodash.filter'; import L from 'leaflet'; import { MapLayer } from 'react-leaflet'; import shouldPureComponentUpdate from 'react-pure-render/function'; export type LngLat = { lng: number; lat: number; } export type Point = { x: number; y: number; } export type Map = { layerPointToLatLng: (lngLat: Point) => LngLat; latLngToLayerPoint: (lngLat: LngLat) => Point; on: (event: string, handler: () => void) => void; getPanes: () => Panes; invalidateSize: () => void; } export type Panes = { overlayPane: Pane; } export type Pane = { appendChild: (element: Object) => void; } function isInvalid(num: number): boolean { return !isNumber(num) && !num; } function isValid(num: number): boolean { return !isInvalid(num); } function shouldIgnoreLocation(loc: LngLat): boolean { return isInvalid(loc.lng) || isInvalid(loc.lat); } export default class MarkerLayer extends MapLayer { static propTypes = { markers: React.PropTypes.array, longitudeExtractor: React.PropTypes.func.isRequired, latitudeExtractor: React.PropTypes.func.isRequired, markerComponent: React.PropTypes.func.isRequired, propsForMarkers: React.PropTypes.object, fitBoundsOnLoad: React.PropTypes.bool, fitBoundsOnUpdate: React.PropTypes.bool, }; shouldComponentUpdate = shouldPureComponentUpdate; componentWillReceiveProps() { // no-op to override MapLayer behavior } componentDidMount(): void { this.leafletElement = ReactDOM.findDOMNode(this.refs.container); this.props.map.getPanes().overlayPane.appendChild(this.leafletElement); if (this.props.fitBoundsOnLoad) { this.fitBounds(); } this.attachEvents(); this.updatePosition(); } componentWillUnmount(): void { this.props.map.getPanes().overlayPane.removeChild(this.leafletElement); } fitBounds(): void { const markers = this.props.markers; const lngs = filter(map(markers, this.props.longitudeExtractor), isValid); const lats = filter(map(markers, this.props.latitudeExtractor), isValid); const ne = { lng: max(lngs), lat: max(lats) }; const sw = { lng: min(lngs), lat: min(lats) }; if (shouldIgnoreLocation(ne) || shouldIgnoreLocation(sw)) { return; } this.props.map.fitBounds(L.latLngBounds(L.latLng(sw), L.latLng(ne))); } markersOrPositionExtractorsChanged(props): boolean { return this.props.markers !== props.markers || this.props.longitudeExtractor !== props.longitudeExtractor || this.props.latitudeExtractor !== props.latitudeExtractor; } componentDidUpdate(prevProps): void { this.props.map.invalidateSize(); if (this.props.fitBoundsOnUpdate && this.markersOrPositionExtractorsChanged(prevProps)) { this.fitBounds(); } this.updatePosition(); } attachEvents(): void { const map: Map = this.props.map; map.on('viewreset', () => this.updatePosition()); } getLocationForMarker(marker): LngLat { return { lat: this.props.latitudeExtractor(marker), lng: this.props.longitudeExtractor(marker) }; } updatePosition(): void { forEach(this.props.markers, (marker, i) => { const markerElement = ReactDOM.findDOMNode( this.refs[this.getMarkerRefName(i)] ); const location = this.getLocationForMarker(marker); if (shouldIgnoreLocation(location)) { return; } const point = this.props.map.latLngToLayerPoint(L.latLng(location)); L.DomUtil.setPosition(markerElement, point); }); } render(): React.Element { return ( <div ref="container" className={`leaflet-objects-pane leaflet-marker-pane leaflet-zoom-hide react-leaflet-marker-layer`} > {this.renderMarkers()} </div> ); } renderMarkers(): Array<React.Element> { const style = { position: 'absolute' }; const MarkerComponent = this.props.markerComponent; return map(this.props.markers, (marker, index: number) => { if (shouldIgnoreLocation(this.getLocationForMarker(marker))) { return; } return ( <MarkerComponent {...this.props.propsForMarkers} key={index} style={style} map={this.props.map} ref={this.getMarkerRefName(index)} marker={marker} /> ); }); } getMarkerRefName(index: number): string { return `marker-${index}`; } }