Add US geo data tables, filter bounds API, and URL hash state management
- Add mls_geo_cities and mls_geo_zipcodes tables with 29,880 cities and 33,144 zip codes - Add get_filter_bounds() method to reposition map when filters don't intersect current view - Move all URL state (filters, page, scroll, map position) to hash to avoid WordPress 404s - Add filter bounds AJAX endpoint for map repositioning on filter change Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,20 @@ class MLS_DB {
|
||||
return $this->get_table_name(MLS_TABLE_MEDIA_LOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get geo cities table name
|
||||
*/
|
||||
public function geo_cities_table() {
|
||||
return $this->get_table_name(MLS_TABLE_GEO_CITIES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get geo zipcodes table name
|
||||
*/
|
||||
public function geo_zipcodes_table() {
|
||||
return $this->get_table_name(MLS_TABLE_GEO_ZIPCODES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all database tables
|
||||
*/
|
||||
@@ -283,6 +297,41 @@ class MLS_DB {
|
||||
|
||||
dbDelta($sql_media_log);
|
||||
|
||||
// Geo cities table
|
||||
$table_geo_cities = $wpdb->prefix . MLS_TABLE_GEO_CITIES;
|
||||
$sql_geo_cities = "CREATE TABLE {$table_geo_cities} (
|
||||
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
state_code VARCHAR(2) NOT NULL,
|
||||
state_name VARCHAR(50) NOT NULL,
|
||||
city VARCHAR(100) NOT NULL,
|
||||
county VARCHAR(100) DEFAULT NULL,
|
||||
latitude DECIMAL(10,6) NOT NULL,
|
||||
longitude DECIMAL(10,6) NOT NULL,
|
||||
|
||||
PRIMARY KEY (id),
|
||||
KEY state_code (state_code),
|
||||
KEY city (city),
|
||||
KEY state_city (state_code, city),
|
||||
KEY lat_lng (latitude, longitude)
|
||||
) {$charset_collate};";
|
||||
|
||||
dbDelta($sql_geo_cities);
|
||||
|
||||
// Geo zipcodes table
|
||||
$table_geo_zipcodes = $wpdb->prefix . MLS_TABLE_GEO_ZIPCODES;
|
||||
$sql_geo_zipcodes = "CREATE TABLE {$table_geo_zipcodes} (
|
||||
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
zipcode VARCHAR(10) NOT NULL,
|
||||
latitude DECIMAL(10,6) NOT NULL,
|
||||
longitude DECIMAL(10,6) NOT NULL,
|
||||
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY zipcode (zipcode),
|
||||
KEY lat_lng (latitude, longitude)
|
||||
) {$charset_collate};";
|
||||
|
||||
dbDelta($sql_geo_zipcodes);
|
||||
|
||||
// Run index migrations
|
||||
self::run_index_migrations();
|
||||
}
|
||||
@@ -402,6 +451,8 @@ class MLS_DB {
|
||||
MLS_TABLE_RATE_LIMITS,
|
||||
MLS_TABLE_SYNC_LOG,
|
||||
MLS_TABLE_MEDIA_LOG,
|
||||
MLS_TABLE_GEO_CITIES,
|
||||
MLS_TABLE_GEO_ZIPCODES,
|
||||
);
|
||||
|
||||
foreach ($tables as $table) {
|
||||
@@ -472,4 +523,159 @@ class MLS_DB {
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import geo data from CSV files
|
||||
* Only imports if tables are empty
|
||||
*
|
||||
* @return array Import results with counts
|
||||
*/
|
||||
public static function import_geo_data() {
|
||||
global $wpdb;
|
||||
|
||||
$results = array(
|
||||
'cities_imported' => 0,
|
||||
'zipcodes_imported' => 0,
|
||||
'cities_skipped' => false,
|
||||
'zipcodes_skipped' => false,
|
||||
);
|
||||
|
||||
$cities_table = $wpdb->prefix . MLS_TABLE_GEO_CITIES;
|
||||
$zipcodes_table = $wpdb->prefix . MLS_TABLE_GEO_ZIPCODES;
|
||||
|
||||
// Check if tables already have data
|
||||
$cities_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$cities_table}");
|
||||
$zipcodes_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$zipcodes_table}");
|
||||
|
||||
// Import cities if table is empty
|
||||
if ($cities_count === 0) {
|
||||
$cities_file = MLS_PLUGIN_DIR . 'data/us_cities.csv';
|
||||
if (file_exists($cities_file)) {
|
||||
$results['cities_imported'] = self::import_cities_csv($cities_file, $cities_table);
|
||||
}
|
||||
} else {
|
||||
$results['cities_skipped'] = true;
|
||||
}
|
||||
|
||||
// Import zipcodes if table is empty
|
||||
if ($zipcodes_count === 0) {
|
||||
$zipcodes_file = MLS_PLUGIN_DIR . 'data/us_zipcodes.csv';
|
||||
if (file_exists($zipcodes_file)) {
|
||||
$results['zipcodes_imported'] = self::import_zipcodes_csv($zipcodes_file, $zipcodes_table);
|
||||
}
|
||||
} else {
|
||||
$results['zipcodes_skipped'] = true;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import cities CSV file
|
||||
* CSV format: ID,STATE_CODE,STATE_NAME,CITY,COUNTY,LATITUDE,LONGITUDE
|
||||
*
|
||||
* @param string $file Path to CSV file
|
||||
* @param string $table Table name
|
||||
* @return int Number of rows imported
|
||||
*/
|
||||
private static function import_cities_csv($file, $table) {
|
||||
global $wpdb;
|
||||
|
||||
$handle = fopen($file, 'r');
|
||||
if (!$handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip header row
|
||||
fgetcsv($handle);
|
||||
|
||||
$count = 0;
|
||||
$batch = array();
|
||||
$batch_size = 500;
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
if (count($row) < 7) continue;
|
||||
|
||||
// ID,STATE_CODE,STATE_NAME,CITY,COUNTY,LATITUDE,LONGITUDE
|
||||
$batch[] = $wpdb->prepare(
|
||||
"(%s, %s, %s, %s, %f, %f)",
|
||||
$row[1], // state_code
|
||||
$row[2], // state_name
|
||||
$row[3], // city
|
||||
$row[4], // county
|
||||
(float) $row[5], // latitude
|
||||
(float) $row[6] // longitude
|
||||
);
|
||||
|
||||
if (count($batch) >= $batch_size) {
|
||||
$values = implode(',', $batch);
|
||||
$wpdb->query("INSERT INTO {$table} (state_code, state_name, city, county, latitude, longitude) VALUES {$values}");
|
||||
$count += count($batch);
|
||||
$batch = array();
|
||||
}
|
||||
}
|
||||
|
||||
// Insert remaining rows
|
||||
if (!empty($batch)) {
|
||||
$values = implode(',', $batch);
|
||||
$wpdb->query("INSERT INTO {$table} (state_code, state_name, city, county, latitude, longitude) VALUES {$values}");
|
||||
$count += count($batch);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import zipcodes CSV file
|
||||
* CSV format: ZIP,LAT,LNG
|
||||
*
|
||||
* @param string $file Path to CSV file
|
||||
* @param string $table Table name
|
||||
* @return int Number of rows imported
|
||||
*/
|
||||
private static function import_zipcodes_csv($file, $table) {
|
||||
global $wpdb;
|
||||
|
||||
$handle = fopen($file, 'r');
|
||||
if (!$handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip header row
|
||||
fgetcsv($handle);
|
||||
|
||||
$count = 0;
|
||||
$batch = array();
|
||||
$batch_size = 500;
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
if (count($row) < 3) continue;
|
||||
|
||||
// ZIP,LAT,LNG
|
||||
$batch[] = $wpdb->prepare(
|
||||
"(%s, %f, %f)",
|
||||
trim($row[0]), // zipcode
|
||||
(float) trim($row[1]), // latitude
|
||||
(float) trim($row[2]) // longitude
|
||||
);
|
||||
|
||||
if (count($batch) >= $batch_size) {
|
||||
$values = implode(',', $batch);
|
||||
$wpdb->query("INSERT INTO {$table} (zipcode, latitude, longitude) VALUES {$values}");
|
||||
$count += count($batch);
|
||||
$batch = array();
|
||||
}
|
||||
}
|
||||
|
||||
// Insert remaining rows
|
||||
if (!empty($batch)) {
|
||||
$values = implode(',', $batch);
|
||||
$wpdb->query("INSERT INTO {$table} (zipcode, latitude, longitude) VALUES {$values}");
|
||||
$count += count($batch);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user