import Supercluster from 'supercluster';
import { CLUSTER_CONFIG } from '../config/MapboxConfig';
/**
* Initialize Supercluster with custom configuration
* @param {Array} events - Array of news events with location data
* @returns {Supercluster} Configured supercluster instance
*/
export const initializeCluster = (events) => {
const cluster = new Supercluster({
radius: CLUSTER_CONFIG.radius,
maxZoom: CLUSTER_CONFIG.maxZoom,
minZoom: CLUSTER_CONFIG.minZoom,
minPoints: CLUSTER_CONFIG.minPoints,
map: (props) => ({
sentiment_sum: props.sentiment_score || 0,
sentiment_count: props.sentiment_score !== null ? 1 : 0,
negative_count: (props.sentiment_score || 0) < -0.3 ? 1 : 0
}),
reduce: (accumulated, props) => {
accumulated.sentiment_sum += props.sentiment_sum;
accumulated.sentiment_count += props.sentiment_count;
accumulated.negative_count += props.negative_count;
}
});
// Convert events to GeoJSON features
const features = events.map(event => ({
type: 'Feature',
properties: {
id: event.id,
title: event.title,
summary: event.summary,
link: event.link,
description: event.description,
priority: event.priority,
sentiment_score: event.sentiment_score || 0,
sentiment_label: event.sentiment_label || 'neutral',
source_name: event.source_name,
published_date: event.published_date,
entity_name: event.location?.entity_name || 'Unknown'
},
geometry: {
type: 'Point',
coordinates: [
event.location?.longitude || 0,
event.location?.latitude || 0
]
}
}));
cluster.load(features);
return cluster;
};
/**
* Get clusters and points for current map bounds and zoom
* @param {Supercluster} cluster - Supercluster instance
* @param {Object} bounds - Map bounds {west, south, east, north}
* @param {number} zoom - Current zoom level
* @returns {Array} Array of clusters and individual points
*/
export const getClustersInBounds = (cluster, bounds, zoom) => {
if (!cluster || !bounds) return [];
return cluster.getClusters(
[bounds.west, bounds.south, bounds.east, bounds.north],
Math.floor(zoom)
);
};
/**
* Get individual points from a cluster
* @param {Supercluster} cluster - Supercluster instance
* @param {number} clusterId - Cluster ID
* @returns {Array} Array of individual point features
*/
export const getClusterExpansion = (cluster, clusterId) => {
if (!cluster) return [];
return cluster.getLeaves(clusterId, Infinity);
};
/**
* Calculate average sentiment for a cluster
* @param {Object} clusterProperties - Cluster properties from supercluster
* @returns {number} Average sentiment score
*/
export const getClusterSentiment = (clusterProperties) => {
if (!clusterProperties.sentiment_count) return 0;
return clusterProperties.sentiment_sum / clusterProperties.sentiment_count;
};
/**
* Calculate negative news ratio for a cluster
* @param {Object} clusterProperties - Cluster properties
* @returns {number} Ratio of negative news (0 to 1)
*/
export const getClusterNegativeRatio = (clusterProperties) => {
const total = clusterProperties.point_count || 1;
return (clusterProperties.negative_count || 0) / total;
};
/**
* Get zoom level to expand a cluster
* @param {Supercluster} cluster - Supercluster instance
* @param {number} clusterId - Cluster ID
* @returns {number} Zoom level to expand cluster
*/
export const getClusterExpansionZoom = (cluster, clusterId) => {
if (!cluster) return 10;
return cluster.getClusterExpansionZoom(clusterId);
};