PluginProbe
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings / 7.1.1
MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings v7.1.1
7.2.1 7.2 7.1.2 7.1.1 7.1 7.0.4 7.0.6 7.0.7 6.3.8 6.3.7 6.3.6 6.3.5 6.3.4 6.3.3 6.3.1 trunk 5.7.3 5.7.5 5.8.1 5.8.2 5.8.3 5.8.4 5.8.6 6.0.4 6.0.5 All 36 releases
mlsimport / includes / standalone / class-mlsimport-map-clusterer.php

class-mlsimport-map-clusterer.php in MLSImport: IDX Plugin & MLS Plugin for Real Estate Listings 7.1.1, at includes/standalone/class-mlsimport-map-clusterer.php

88 lines 3.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Pure viewport grid-clustering engine for the standalone/half-map map.
4 *
5 * Given the in-view listing coordinates and the map zoom, buckets the points
6 * into fixed-pixel grid cells and returns one entry per non-empty cell with its
7 * centroid, member count and member post IDs. The map payload then renders a
8 * single-member cell as a price pin and a multi-member cell as a count bubble —
9 * so listings cluster at every zoom and split apart as the cells shrink.
10 *
11 * PURE — no WordPress calls, so it is unit-testable in isolation
12 * (tests/unit/clusterer-test.php).
13 *
14 * @package Mlsimport
15 */
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Grid-clusters {lat,lng,post_id} points into zoom-sized cells.
23 */
24 class Mlsimport_Map_Clusterer {
25
26 /**
27 * Grid-cluster coordinate points into cells sized to the zoom level.
28 *
29 * @param array $coords Objects/arrays with ->lat / ->lng (and optional ->post_id).
30 * @param int $zoom Map zoom; higher zoom => smaller cells => more, tighter cells.
31 * @return array<int,array{lat:float,lng:float,count:int,ids:int[]}>
32 */
33 public static function grid( array $coords, int $zoom ): array {
34 // Clamp the zoom to the Web-Mercator tile range so the cell math is sane.
35 $zoom = max( 0, min( 22, $zoom ) );
36
37 // Web-Mercator world width in pixels at this zoom is 256 * 2^zoom for 360°.
38 // A ~70px cell keeps clusters visually separated at every zoom level.
39 $world_px = 256.0 * pow( 2, $zoom );
40 $cell_deg = ( 70.0 / $world_px ) * 360.0;
41 if ( $cell_deg <= 0 ) {
42 $cell_deg = 0.0001;
43 }
44
45 // Bucket every point into its grid cell, accumulating a running centroid sum.
46 $cells = array();
47 foreach ( $coords as $c ) {
48 // Read lat/lng/post_id from either an object or an array shape.
49 $lat = is_object( $c ) ? (float) $c->lat : (float) $c['lat'];
50 $lng = is_object( $c ) ? (float) $c->lng : (float) $c['lng'];
51 $id = is_object( $c )
52 ? ( isset( $c->post_id ) ? (int) $c->post_id : 0 )
53 : ( isset( $c['post_id'] ) ? (int) $c['post_id'] : 0 );
54
55 // Cell key = the integer grid coordinate "col:row" the point falls in.
56 $key = ( (int) floor( $lng / $cell_deg ) ) . ':' . ( (int) floor( $lat / $cell_deg ) );
57 // First point in a cell seeds its accumulator.
58 if ( ! isset( $cells[ $key ] ) ) {
59 $cells[ $key ] = array(
60 'sum_lat' => 0.0,
61 'sum_lng' => 0.0,
62 'count' => 0,
63 'ids' => array(),
64 );
65 }
66 // Fold this point into the cell: sum coords (for the centroid), bump count,
67 // remember the member id.
68 $cells[ $key ]['sum_lat'] += $lat;
69 $cells[ $key ]['sum_lng'] += $lng;
70 $cells[ $key ]['count']++;
71 $cells[ $key ]['ids'][] = $id;
72 }
73
74 // Emit one entry per non-empty cell: the centroid (sum / count), count and ids.
75 $out = array();
76 foreach ( $cells as $cell ) {
77 $out[] = array(
78 'lat' => $cell['sum_lat'] / $cell['count'],
79 'lng' => $cell['sum_lng'] / $cell['count'],
80 'count' => (int) $cell['count'],
81 'ids' => $cell['ids'],
82 );
83 }
84
85 return $out;
86 }
87 }
88