Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
whatever
  • Loading branch information
bkapplegate committed Apr 7, 2020
commit 2658b62d521d262a9b5032d513cf86103fc4dac1
3 changes: 1 addition & 2 deletions ClusterMarker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
148 changes: 84 additions & 64 deletions ClusteredMapView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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) {
Expand All @@ -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)
})
}
Expand All @@ -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 (
<MapView
Expand All @@ -145,7 +166,7 @@ export default class ClusteredMapView extends PureComponent {
ref={this.mapRef}
onRegionChangeComplete={this.onRegionChangeComplete}>
{
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)

Expand All @@ -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} />
)
})
}
Expand Down Expand Up @@ -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])
}