Fix clustering density with pixel-based grid calculation
Replace geohash precision mapping with dynamic grid sizing based on target pixel spacing (60px between cluster centers). Uses Leaflet/OSM tile math to calculate degrees-per-pixel at each zoom level, adjusted for Mercator projection at the viewport's center latitude. At zoom 7, this gives ~52km cells and ~150 clusters statewide, properly separating Minneapolis from St. Cloud. Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,29 +18,10 @@ class MLS_Cluster {
|
|||||||
const GEOHASH_CHARS = '0123456789bcdefghjkmnpqrstuvwxyz';
|
const GEOHASH_CHARS = '0123456789bcdefghjkmnpqrstuvwxyz';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom level to geohash precision mapping
|
* Target pixel spacing between cluster centers
|
||||||
* Lower precision = larger geographic area = more clustering
|
* With 30px cluster icons, 45px gap gives 75px total spacing
|
||||||
*/
|
*/
|
||||||
const ZOOM_PRECISION = array(
|
const CLUSTER_PIXEL_SPACING = 60;
|
||||||
3 => 2, // ~630km cells - continent level
|
|
||||||
4 => 2,
|
|
||||||
5 => 2,
|
|
||||||
6 => 3, // ~78km cells - state level
|
|
||||||
7 => 3,
|
|
||||||
8 => 3,
|
|
||||||
9 => 4, // ~20km cells - county level
|
|
||||||
10 => 4,
|
|
||||||
11 => 4,
|
|
||||||
12 => 5, // ~2.4km cells - city level
|
|
||||||
13 => 5,
|
|
||||||
14 => 5,
|
|
||||||
15 => 6, // ~610m cells - neighborhood level
|
|
||||||
16 => 6,
|
|
||||||
17 => 6,
|
|
||||||
18 => 7, // ~76m cells - street level
|
|
||||||
19 => 7,
|
|
||||||
20 => 8, // Individual points
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum properties to return as individual markers
|
* Maximum properties to return as individual markers
|
||||||
@@ -156,20 +137,32 @@ class MLS_Cluster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get precision for zoom level
|
* Calculate grid cell size in degrees for a given zoom level
|
||||||
|
*
|
||||||
|
* Uses Leaflet/OSM tile math to determine what geographic distance
|
||||||
|
* corresponds to CLUSTER_PIXEL_SPACING pixels at the given zoom.
|
||||||
*
|
*
|
||||||
* @param int $zoom Map zoom level (1-20)
|
* @param int $zoom Map zoom level (1-20)
|
||||||
* @return int Geohash precision
|
* @param float $lat Center latitude (affects Mercator projection)
|
||||||
|
* @return array [lat_step, lng_step] in degrees
|
||||||
*/
|
*/
|
||||||
public function get_precision_for_zoom($zoom) {
|
public function get_grid_step_for_zoom($zoom, $lat = 45.0) {
|
||||||
$zoom = max(3, min(20, (int) $zoom));
|
$zoom = max(3, min(20, (int) $zoom));
|
||||||
|
|
||||||
if (isset(self::ZOOM_PRECISION[$zoom])) {
|
// Degrees per pixel at zoom level (longitude)
|
||||||
return self::ZOOM_PRECISION[$zoom];
|
// 360 degrees / (256 pixels * 2^zoom tiles)
|
||||||
}
|
$degrees_per_pixel_lng = 360.0 / (256 * pow(2, $zoom));
|
||||||
|
|
||||||
// Default to precision 5 for unknown zoom levels
|
// Latitude degrees per pixel (adjusted for Mercator at given latitude)
|
||||||
return 5;
|
// At the equator it's the same, at poles it's compressed
|
||||||
|
$lat_rad = deg2rad(abs($lat));
|
||||||
|
$degrees_per_pixel_lat = $degrees_per_pixel_lng * cos($lat_rad);
|
||||||
|
|
||||||
|
// Calculate step size to achieve target pixel spacing
|
||||||
|
$lng_step = self::CLUSTER_PIXEL_SPACING * $degrees_per_pixel_lng;
|
||||||
|
$lat_step = self::CLUSTER_PIXEL_SPACING * $degrees_per_pixel_lat;
|
||||||
|
|
||||||
|
return array($lat_step, $lng_step);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,14 +251,18 @@ class MLS_Cluster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If low count or high zoom, return individual markers
|
// If low count or high zoom, return individual markers
|
||||||
$precision = $this->get_precision_for_zoom($args['zoom']);
|
if ($total <= self::MAX_INDIVIDUAL_MARKERS || $args['zoom'] >= 16) {
|
||||||
|
|
||||||
if ($total <= self::MAX_INDIVIDUAL_MARKERS || $args['zoom'] >= 18) {
|
|
||||||
return $this->get_individual_markers($where_sql, $values, $total);
|
return $this->get_individual_markers($where_sql, $values, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate center latitude for Mercator adjustment
|
||||||
|
$center_lat = 45.0; // Default Minnesota
|
||||||
|
if ($args['bounds'] && count($args['bounds']) === 4) {
|
||||||
|
$center_lat = ($args['bounds'][0] + $args['bounds'][2]) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
// Return clusters
|
// Return clusters
|
||||||
return $this->get_cluster_data($where_sql, $values, $precision, $total);
|
return $this->get_cluster_data($where_sql, $values, $args['zoom'], $center_lat, $total);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -334,27 +331,22 @@ class MLS_Cluster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get clustered data using geohash grouping
|
* Get clustered data using grid-based grouping
|
||||||
*
|
*
|
||||||
* @param string $where_sql WHERE clause
|
* @param string $where_sql WHERE clause
|
||||||
* @param array $values Prepared values
|
* @param array $values Prepared values
|
||||||
* @param int $precision Geohash precision
|
* @param int $zoom Map zoom level
|
||||||
|
* @param float $center_lat Center latitude for Mercator adjustment
|
||||||
* @param int $total Total count
|
* @param int $total Total count
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function get_cluster_data($where_sql, $values, $precision, $total) {
|
private function get_cluster_data($where_sql, $values, $zoom, $center_lat, $total) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
$table = $this->db->properties_table();
|
$table = $this->db->properties_table();
|
||||||
|
|
||||||
// Use MySQL SUBSTR to extract geohash prefix for grouping
|
// Calculate grid cell size based on zoom level and target pixel spacing
|
||||||
// We compute geohash on-the-fly using latitude/longitude
|
list($lat_step, $lng_step) = $this->get_grid_step_for_zoom($zoom, $center_lat);
|
||||||
// For better performance with 30k+ records, we use grid-based clustering
|
|
||||||
|
|
||||||
// Calculate grid cell size based on precision
|
|
||||||
// Approximate degrees per geohash precision level
|
|
||||||
$lat_step = 180 / pow(2, (int)($precision * 2.5));
|
|
||||||
$lng_step = 360 / pow(2, (int)($precision * 2.5));
|
|
||||||
|
|
||||||
// Use FLOOR to group coordinates into cells
|
// Use FLOOR to group coordinates into cells
|
||||||
$sql = "SELECT
|
$sql = "SELECT
|
||||||
@@ -388,7 +380,8 @@ class MLS_Cluster {
|
|||||||
'type' => 'clusters',
|
'type' => 'clusters',
|
||||||
'total' => $total,
|
'total' => $total,
|
||||||
'cluster_count' => count($clusters),
|
'cluster_count' => count($clusters),
|
||||||
'precision' => $precision,
|
'zoom' => $zoom,
|
||||||
|
'grid_size_deg' => array('lat' => $lat_step, 'lng' => $lng_step),
|
||||||
'clusters' => $clusters,
|
'clusters' => $clusters,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user