-
-
Save justim/2c834a657553f0e2531949edae03fb83 to your computer and use it in GitHub Desktop.
Markerclusterer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
colors, | |
} from './../../config'; | |
import { | |
StyleSheet, | |
} from 'react-native'; | |
export default StyleSheet.create({ | |
clusterContainer: { | |
backgroundColor: '#666', | |
height: 30, | |
width: 30, | |
borderRadius: 15, | |
justifyContent: 'center', | |
alignItems: 'center', | |
borderColor: '#fff', | |
borderWidth: StyleSheet.hairlineWidth, | |
}, | |
clusterText: { | |
color: '#fff', | |
fontSize: 12, | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { | |
Text, | |
View, | |
} from 'react-native'; | |
import MapView from 'react-native-maps'; | |
import each from 'lodash/each'; | |
import some from 'lodash/some'; | |
import map from 'lodash/map'; | |
import flatten from 'lodash/flatten'; | |
import { boundsToRegion } from './../../libs/maps'; | |
import styles from './ClustererStyle'; | |
const LATITUDE_PARTS = 13; | |
const LONGITUDE_PARTS = 10; | |
const MINIMAL_DELTA = 0.001; | |
class Point { | |
constructor(latitude, longitude) { | |
this.latitude = latitude; | |
this.longitude = longitude; | |
} | |
} | |
class ClusterBounds { | |
constructor(region, point) { | |
this._region = region; | |
this._point = point; | |
if (this._region) { | |
const latitude = region.latitudeDelta / LATITUDE_PARTS; | |
const longitude = region.longitudeDelta / LONGITUDE_PARTS; | |
this.topLeft = new Point(point.latitude + latitude, point.longitude - longitude); | |
this.bottomRight = new Point(point.latitude - latitude, point.longitude + longitude); | |
} | |
} | |
contains(point) { | |
// max zoom detection to always show single markers | |
if (!this._region | |
|| this._region.latitudeDelta < MINIMAL_DELTA | |
|| this._region.longitudeDelta < MINIMAL_DELTA) { | |
return false; | |
} else { | |
return (point.longitude >= this.topLeft.longitude) && | |
(point.longitude <= this.bottomRight.longitude) && | |
(point.latitude <= this.topLeft.latitude) && | |
(point.latitude >= this.bottomRight.latitude); | |
} | |
} | |
getRegion() { | |
return boundsToRegion(this); | |
} | |
} | |
class Cluster { | |
constructor(region, marker, initialPoint) { | |
this._bounds = new ClusterBounds(region, initialPoint); | |
this._markers = [marker]; | |
this._points = [initialPoint]; | |
} | |
contains(point) { | |
return this._bounds.contains(point); | |
} | |
addPoint(point, marker) { | |
this._points.push(point); | |
this._markers.push(marker); | |
} | |
getMarkers() { | |
return this._markers; | |
} | |
getCenter() { | |
return this._points[0]; | |
} | |
getRegion() { | |
return this._bounds.getRegion(); | |
} | |
getTotalPoints() { | |
return this._points.length; | |
} | |
isClustered() { | |
return this.getTotalPoints() > 3; | |
} | |
} | |
export default getClusters(region, markers, onClusterPress) { | |
const clusters = []; | |
each(markers, marker => { | |
const point = new Point( | |
marker.props.coordinate.latitude, | |
marker.props.coordinate.longitude); | |
const found = some(clusters, cluster => { | |
if (cluster.contains(point)) { | |
cluster.addPoint(point, marker); | |
return true; | |
} | |
}); | |
if (!found) { | |
const cluster = new Cluster(region, marker, point); | |
clusters.push(cluster); | |
} | |
}); | |
return flatten(map(clusters, (cluster, i) => { | |
const center = cluster.getCenter(); | |
if (!cluster.isClustered()) { | |
return cluster.getMarkers(); | |
} | |
return ( | |
<MapView.Marker | |
key={i} | |
coordinate={{ | |
latitude: center.latitude, | |
longitude: center.longitude, | |
}} | |
onPress={ev => onClusterPress(cluster, ev)} | |
> | |
<View style={styles.clusterContainer}> | |
<Text style={styles.clusterText}> | |
{cluster.getTotalPoints()} | |
</Text> | |
</View> | |
</MapView.Marker> | |
); | |
})); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function boundsToRegion(bounds) { | |
const latitudeDelta = Math.abs(bounds.topLeft.latitude - bounds.bottomRight.latitude); | |
const longitudeDelta = Math.abs(bounds.topLeft.longitude - bounds.bottomRight.longitude); | |
return { | |
latitude: bounds.topLeft.latitude - (latitudeDelta / 2), | |
longitude: bounds.topLeft.longitude + (longitudeDelta / 2), | |
latitudeDelta, | |
longitudeDelta, | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import getClusters from './get-clusters'; | |
export default class Map extends Component { | |
_onClusterPress(cluster, ev) { | |
this.setState({ region: cluster.getRegion() }); | |
} | |
render() { | |
return ( | |
<MapView | |
rotateEnabled={false} | |
region={this.state.region} | |
onRegionChange={this._onRegionChange.bind(this)} | |
> | |
{getClusters(this.state.region, this.props.mapItems.map(item => { | |
return ( | |
<MapView.Marker | |
key={item.id} | |
coordinate={{ | |
latitude: parseFloat(item.lat), | |
longitude: parseFloat(item.lon), | |
}} | |
/> | |
); | |
}), this._onClusterPress.bind(this))} | |
</MapView.Animated> | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment