diff --git a/wp-content/plugins/mls-by-hansonxyz/includes/class-mls-cluster.php b/wp-content/plugins/mls-by-hansonxyz/includes/class-mls-cluster.php
index c4b87289..127d00e2 100644
--- a/wp-content/plugins/mls-by-hansonxyz/includes/class-mls-cluster.php
+++ b/wp-content/plugins/mls-by-hansonxyz/includes/class-mls-cluster.php
@@ -23,12 +23,25 @@ class MLS_Cluster {
*/
const CLUSTER_PIXEL_SPACING = 60;
+ /**
+ * Pixel spacing for density dots (smaller, more numerous)
+ */
+ const DENSITY_DOT_SPACING = 40;
+
/**
* Maximum properties to return as individual markers
* Above this threshold, return clusters
*/
const MAX_INDIVIDUAL_MARKERS = 500;
+ /**
+ * Zoom thresholds for visualization modes
+ */
+ const ZOOM_HEATMAP_MAX = 7; // 3-7: heatmap only
+ const ZOOM_DENSITY_MAX = 11; // 8-11: density dots
+ const ZOOM_CLUSTER_MAX = 15; // 12-15: numbered clusters
+ // 16+: individual markers
+
/**
* Database instance
*/
@@ -140,14 +153,16 @@ class MLS_Cluster {
* 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.
+ * corresponds to the target pixel spacing at the given zoom.
*
* @param int $zoom Map zoom level (1-20)
* @param float $lat Center latitude (affects Mercator projection)
+ * @param int $pixel_spacing Target pixel spacing (defaults to CLUSTER_PIXEL_SPACING)
* @return array [lat_step, lng_step] in degrees
*/
- public function get_grid_step_for_zoom($zoom, $lat = 45.0) {
+ public function get_grid_step_for_zoom($zoom, $lat = 45.0, $pixel_spacing = null) {
$zoom = max(3, min(20, (int) $zoom));
+ $pixel_spacing = $pixel_spacing ?: self::CLUSTER_PIXEL_SPACING;
// Degrees per pixel at zoom level (longitude)
// 360 degrees / (256 pixels * 2^zoom tiles)
@@ -159,8 +174,8 @@ class MLS_Cluster {
$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;
+ $lng_step = $pixel_spacing * $degrees_per_pixel_lng;
+ $lat_step = $pixel_spacing * $degrees_per_pixel_lat;
return array($lat_step, $lng_step);
}
@@ -250,19 +265,128 @@ class MLS_Cluster {
$total = (int) $wpdb->get_var($count_sql);
}
- // If low count or high zoom, return individual markers
- if ($total <= self::MAX_INDIVIDUAL_MARKERS || $args['zoom'] >= 16) {
- 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 $this->get_cluster_data($where_sql, $values, $args['zoom'], $center_lat, $total);
+ $zoom = (int) $args['zoom'];
+
+ // Determine visualization mode based on zoom level
+ // Zoom 3-7: Heatmap (just return points for client-side heatmap)
+ if ($zoom <= self::ZOOM_HEATMAP_MAX) {
+ return $this->get_heatmap_data($where_sql, $values, $total);
+ }
+
+ // Zoom 8-11: Density dots (small colored circles without numbers)
+ if ($zoom <= self::ZOOM_DENSITY_MAX) {
+ return $this->get_density_data($where_sql, $values, $zoom, $center_lat, $total);
+ }
+
+ // Zoom 12-15: Numbered clusters (or individual if low count)
+ if ($zoom <= self::ZOOM_CLUSTER_MAX) {
+ if ($total <= self::MAX_INDIVIDUAL_MARKERS) {
+ return $this->get_individual_markers($where_sql, $values, $total);
+ }
+ return $this->get_cluster_data($where_sql, $values, $zoom, $center_lat, $total);
+ }
+
+ // Zoom 16+: Individual markers
+ return $this->get_individual_markers($where_sql, $values, $total);
+ }
+
+ /**
+ * Get heatmap data (just coordinates for client-side rendering)
+ *
+ * @param string $where_sql WHERE clause
+ * @param array $values Prepared values
+ * @param int $total Total count
+ * @return array
+ */
+ private function get_heatmap_data($where_sql, $values, $total) {
+ global $wpdb;
+
+ $table = $this->db->properties_table();
+
+ // Get sampled points for heatmap (limit to prevent overwhelming the client)
+ // Use grid-based sampling to get representative distribution
+ $sql = "SELECT latitude, longitude
+ FROM {$table}
+ WHERE {$where_sql}
+ LIMIT 10000";
+
+ if (!empty($values)) {
+ $results = $wpdb->get_results($wpdb->prepare($sql, $values));
+ } else {
+ $results = $wpdb->get_results($sql);
+ }
+
+ $points = array();
+ foreach ($results as $row) {
+ $points[] = array(
+ (float) $row->latitude,
+ (float) $row->longitude,
+ 1.0 // intensity
+ );
+ }
+
+ return array(
+ 'type' => 'heatmap',
+ 'total' => $total,
+ 'point_count' => count($points),
+ 'points' => $points,
+ );
+ }
+
+ /**
+ * Get density dot data (clustered points with count for coloring)
+ *
+ * @param string $where_sql WHERE clause
+ * @param array $values Prepared values
+ * @param int $zoom Map zoom level
+ * @param float $center_lat Center latitude for Mercator adjustment
+ * @param int $total Total count
+ * @return array
+ */
+ private function get_density_data($where_sql, $values, $zoom, $center_lat, $total) {
+ global $wpdb;
+
+ $table = $this->db->properties_table();
+
+ // Use smaller grid cells for density dots
+ list($lat_step, $lng_step) = $this->get_grid_step_for_zoom($zoom, $center_lat, self::DENSITY_DOT_SPACING);
+
+ $sql = "SELECT
+ FLOOR(latitude / %f) as lat_cell,
+ FLOOR(longitude / %f) as lng_cell,
+ COUNT(*) as count,
+ AVG(latitude) as avg_lat,
+ AVG(longitude) as avg_lng
+ FROM {$table}
+ WHERE {$where_sql}
+ GROUP BY lat_cell, lng_cell
+ HAVING count >= 1";
+
+ $grid_values = array_merge(array($lat_step, $lng_step), $values);
+ $results = $wpdb->get_results($wpdb->prepare($sql, $grid_values));
+
+ $dots = array();
+ foreach ($results as $row) {
+ $dots[] = array(
+ 'lat' => (float) $row->avg_lat,
+ 'lng' => (float) $row->avg_lng,
+ 'count' => (int) $row->count,
+ );
+ }
+
+ return array(
+ 'type' => 'density',
+ 'total' => $total,
+ 'dot_count' => count($dots),
+ 'zoom' => $zoom,
+ 'dots' => $dots,
+ );
}
/**
diff --git a/wp-content/themes/homeproz/archive-property.php b/wp-content/themes/homeproz/archive-property.php
index 19c33ed3..578fe4fd 100755
--- a/wp-content/themes/homeproz/archive-property.php
+++ b/wp-content/themes/homeproz/archive-property.php
@@ -131,6 +131,8 @@ if (function_exists('mls_get_property_count')) {
+
+