diff --git a/ClusterMarker.js b/ClusterMarker.js index 8b1765a..c57ae6c 100644 --- a/ClusterMarker.js +++ b/ClusterMarker.js @@ -24,8 +24,7 @@ export default class ClusterMarker extends Component { const cluster = { pointCount, coordinate: { latitude, longitude }, - clusterId: this.props.properties.cluster_id, - groupValue: this.props.properties.groupValue + clusterId: this.props.properties.cluster_id } return this.props.renderCluster(cluster, this.onPress) } diff --git a/ClusteredMapView.js b/ClusteredMapView.js index 723d6fb..6ad6ef6 100644 --- a/ClusteredMapView.js +++ b/ClusteredMapView.js @@ -23,12 +23,12 @@ import { export default class ClusteredMapView extends PureComponent { - clusters = {} - constructor(props) { super(props) this.state = { + data: [], + clusterData: [], // helds renderable clusters and markers region: props.region || props.initialRegion, // helds current map region } @@ -38,15 +38,26 @@ export default class ClusteredMapView extends PureComponent { this.mapRef = this.mapRef.bind(this) this.onClusterPress = this.onClusterPress.bind(this) this.onRegionChangeComplete = this.onRegionChangeComplete.bind(this) - } - componentDidMount() { - this.clusterize(this.props.data, this.props.groupKey) + this.clusterIdMap = new Map(); + this.currentDateString = new Date().toISOString(); } - componentDidUpdate(prevProps, prevState) { - if (!this.isAndroid && this.props.animateClusters) - LayoutAnimation.configureNext(this.props.layoutAnimationConf) + static getDerivedStateFromProps(nextProps, prevState){ + if(nextProps.data !== prevState.data){ + return { data: nextProps.data }; + } + else return null; + } + + componentDidUpdate(prevProps, prevState) { + if(prevProps.data !== this.props.data){ + this.clusterize(this.props.data) + } + } + + componentDidMount() { + this.clusterize(this.props.data) } mapRef(ref) { @@ -57,43 +68,48 @@ export default class ClusteredMapView extends PureComponent { return this.mapview } - getClusteringEngine = (groupValue = 0) => this.clusters[groupValue] - - getAllFromClusterEngine = (getFn = (x) => []) => Object.values(this.clusters).map(getFn).reduce((a, b) => a.concat(b), []) - - groupBy = (xs, key) => xs.reduce((rv, x) => { (rv[x[key]] = rv[x[key]] || []).push(x); return rv; }, {}); - - clusterize = (dataset, groupKey = 0) => { - this.clusters = {}; - dataset = dataset || [] - let dataGroups = groupKey ? this.groupBy(dataset, groupKey) : { 0: dataset }; + getClusteringEngine() { + return this.superCluster + } - Object.keys(dataGroups).forEach(groupValue => { - const dataGroup = dataGroups[groupValue]; - let cluster = new SuperCluster({ // eslint-disable-line new-cap - extent: this.props.extent, - minZoom: this.props.minZoom, - maxZoom: this.props.maxZoom, - radius: this.props.radius || (this.dimensions[0] * .045), // 4.5% of screen width - }) + getClusterKey(clusterId, dateString) { + return `cluster-${clusterId}-${dateString}`; + } - // get formatted GeoPoints for cluster - const rawGroupData = dataGroup.map(item => itemToGeoJSONFeature(item, this.props.accessor)) + forceRedrawCluster(clusterId) { + const dateString = new Date().toISOString(); + this.clusterIdMap.set(clusterId, dateString); + const cluster = this.state.clusterData.find(c => c.id === clusterId); + if (cluster) cluster.key = this.getClusterKey(cluster.id, dateString); + this.forceUpdate(); + } - // load geopoints into SuperCluster - cluster.load(rawGroupData) + clusterize(dataset) { + this.superCluster = new SuperCluster({ // eslint-disable-line new-cap + extent: this.props.extent, + minZoom: this.props.minZoom, + maxZoom: this.props.maxZoom, + radius: this.props.radius || (this.dimensions[0] * .045), // 4.5% of screen width + }) - this.clusters[groupValue] = cluster; - }); + // get formatted GeoPoints for cluster + const rawData = dataset.map(item => itemToGeoJSONFeature(item, this.props.accessor)) - const data = this.getClusters(this.state.region); - return data; + // load geopoints into SuperCluster + this.superCluster.load(rawData) + this.currentDateString = new Date().toISOString(); + this.clusterIdMap.clear(); + const clusterData = this.getClusters(this.state.region) + this.setState({ clusterData, data: this.props.data }) } + clustersChanged(nextState) { + return this.state.data.length !== nextState.data.length + } onRegionChangeComplete(region) { - let data = this.getClusters(region) - this.setState({ region, data }, () => { + let clusterData = this.getClusters(region) + this.setState({ region, clusterData }, () => { this.props.onRegionChangeComplete && this.props.onRegionChangeComplete(region, data) }) } @@ -102,41 +118,46 @@ export default class ClusteredMapView extends PureComponent { const bbox = regionToBoundingBox(region), viewport = (region.longitudeDelta) >= 40 ? { zoom: this.props.minZoom } : GeoViewport.viewport(bbox, this.dimensions) - // Get all clusters from each group and set the grouped by value as a property - return Object.keys(this.clusters) - .map(groupValue => ({ groupValue, clusters: this.clusters[groupValue].getClusters(bbox, viewport.zoom)})) - .reduce((a, b) => { - b.clusters.forEach(x => x.properties['groupValue'] = b.groupValue) - return a.concat(b.clusters); - }, []) + const clusters = this.superCluster.getClusters(bbox, viewport.zoom); + clusters.forEach(c => { + if (c.id) { + let dateString = this.clusterIdMap.get(c.id); + if (!dateString) { + this.clusterIdMap.set(c.id, this.currentDateString); + dateString = this.currentDateString; + } + c.key = this.getClusterKey(c.id, dateString); + } + }); + return clusters; } onClusterPress(cluster) { // cluster press behavior might be extremely custom. if (!this.props.preserveClusterPressBehavior) { - this.props.onClusterPress && this.props.onClusterPress(cluster.properties.cluster_id, cluster.properties.groupValue) - return + this.props.onClusterPress && this.props.onClusterPress(cluster.properties.cluster_id) + } else { + // ////////////////////////////////////////////////////////////////////////////////// + // NEW IMPLEMENTATION (with fitToCoordinates) + // ////////////////////////////////////////////////////////////////////////////////// + // get cluster children + const children = this.superCluster.getLeaves(cluster.properties.cluster_id, this.props.clusterPressMaxChildren) + const markers = children.map(c => c.properties.item) + + const coordinates = markers.map(item => getCoordinatesFromItem(item, this.props.accessor, false)) + + // fit right around them, considering edge padding + this.mapview.fitToCoordinates(coordinates, { edgePadding: this.props.edgePadding }) + + this.props.onClusterPress && this.props.onClusterPress(cluster.properties.cluster_id, markers) } - - // ////////////////////////////////////////////////////////////////////////////////// - // NEW IMPLEMENTATION (with fitToCoordinates) - // ////////////////////////////////////////////////////////////////////////////////// - // get cluster children - const children = this.getClusteringEngine(cluster.properties.groupValue).getLeaves(cluster.properties.cluster_id, this.props.clusterPressMaxChildren); - const markers = children.map(c => c.properties.item) - - const coordinates = markers.map(item => getCoordinatesFromItem(item, this.props.accessor, false)) - - // fit right around them, considering edge padding - this.mapview.fitToCoordinates(coordinates, { edgePadding: this.props.edgePadding }) - - this.props.onClusterPress && this.props.onClusterPress(cluster.properties.cluster_id, markers) + // This is to keep react native maps from changing the z-index on the cluster marker if the "tap" event had overlapping hitboxes + this.forceRedrawCluster(cluster.id); } render() { - const { style, data, groupKey, ...props } = this.props - const mapViewData = this.clusterize(data, groupKey); + const { style, ...props } = this.props return ( { - this.props.clusteringEnabled && mapViewData.map((d) => { + this.props.clusteringEnabled && this.state.clusterData.map((d) => { if (d.properties.point_count === 0) return this.props.renderMarker(d.properties.item) @@ -154,7 +175,7 @@ export default class ClusteredMapView extends PureComponent { {...d} onPress={this.onClusterPress} renderCluster={this.props.renderCluster} - key={`cluster-${d.properties.groupValue}-${d.properties.cluster_id}`} /> + key={d.key} /> ) }) } @@ -208,7 +229,6 @@ ClusteredMapView.propTypes = { layoutAnimationConf: PropTypes.object, edgePadding: PropTypes.object.isRequired, // string - groupKey: PropTypes.string, // mutiple accessor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]) }