it-swarm-vi.tech

Phần mềm của tôi

Tôi đã có một dự án mà tôi cần xây dựng một bộ định vị cửa hàng cho một khách hàng.

Tôi đang sử dụng loại bài đăng tùy chỉnh "restaurant-location" và tôi đã viết mã để mã hóa địa chỉ các địa chỉ được lưu trữ trong postmeta bằng cách sử dụng API mã hóa địa lý của Google (đây là liên kết mà mã hóa Nhà Trắng Hoa Kỳ trong JSON và Tôi đã lưu trữ vĩ độ và kinh độ trở lại các trường tùy chỉnh.

Tôi đã viết một hàm get_posts_by_geo_distance() trả về danh sách các bài đăng theo thứ tự địa lý gần nhất bằng cách sử dụng công thức tôi tìm thấy trong trình chiếu tại bài đăng này . Bạn có thể gọi hàm của tôi như vậy (Tôi đang bắt đầu với một "nguồn" lat/long cố định):

include "wp-load.php";

$source_lat = 30.3935337;
$source_long = -86.4957833;

$results = get_posts_by_geo_distance(
    'restaurant-location',
    'geo_latitude',
    'geo_longitude',
    $source_lat,
    $source_long);

echo '<ul>';
foreach($results as $post) {
    $edit_url = get_edit_url($post->ID);
    echo "<li>{$post->distance}: <a href=\"{$edit_url}\" target=\"_blank\">{$post->location}</a></li>";
}
echo '</ul>';
return;

Đây là chức năng get_posts_by_geo_distance():

function get_posts_by_geo_distance($post_type,$lat_key,$lng_key,$source_lat,$source_lng) {
    global $wpdb;
    $sql =<<<SQL
SELECT
    rl.ID,
    rl.post_title AS location,
    ROUND(3956*2*ASIN(SQRT(POWER(SIN(({$source_lat}-abs(lat.lat))*pi()/180/2),2)+
    COS({$source_lat}*pi()/180)*COS(abs(lat.lat)*pi()/180)*
    POWER(SIN(({$source_lng}-lng.lng)*pi()/180/2),2))),3) AS distance
FROM
    wp_posts rl
    INNER JOIN (SELECT post_id,CAST(meta_value AS DECIMAL(11,7)) AS lat FROM wp_postmeta lat WHERE lat.meta_key='{$lat_key}') lat ON lat.post_id = rl.ID
    INNER JOIN (SELECT post_id,CAST(meta_value AS DECIMAL(11,7)) AS lng FROM wp_postmeta lng WHERE lng.meta_key='{$lng_key}') lng ON lng.post_id = rl.ID
WHERE
    rl.post_type='{$post_type}' AND rl.post_name<>'auto-draft'
ORDER BY
    distance
SQL;
    $sql = $wpdb->prepare($sql,$source_lat,$source_lat,$source_lng);
    return $wpdb->get_results($sql);
}

Mối quan tâm của tôi là SQL không được tối ưu hóa như bạn có thể nhận được. MySQL không thể đặt hàng theo bất kỳ chỉ mục khả dụng nào vì nguồn địa lý có thể thay đổi và không có bộ địa lý nguồn hữu hạn nào để lưu vào bộ đệm. Hiện tại tôi đang bối rối để tìm cách tối ưu hóa nó.

Cân nhắc những gì tôi đã làm đã đặt câu hỏi là: Bạn sẽ tối ưu hóa trường hợp sử dụng này như thế nào?

Điều quan trọng là tôi giữ bất cứ điều gì tôi đã làm nếu một giải pháp tốt hơn sẽ khiến tôi vứt bỏ nó. Tôi sẵn sàng xem xét hầu hết mọi giải pháp ngoại trừ một giải pháp yêu cầu làm gì đó như cài đặt máy chủ Sphinx hoặc bất cứ điều gì yêu cầu cấu hình MySQL tùy chỉnh. Về cơ bản, giải pháp cần có khả năng hoạt động trên mọi cài đặt Vanilla WordPress đơn giản. (Điều đó nói rằng, sẽ thật tuyệt nếu bất cứ ai muốn liệt kê các giải pháp thay thế cho những người khác có thể có thể tiến bộ hơn và cho hậu thế.)

Tài nguyên được tìm thấy

FYI, tôi đã thực hiện một chút nghiên cứu về vấn đề này thay vì yêu cầu bạn thực hiện nghiên cứu lại hay thay vì bạn đăng bất kỳ liên kết nào dưới dạng câu trả lời tôi sẽ tiếp tục và đưa chúng vào.

Về Tìm kiếm Nhân sư

11
MikeSchinkel

Bạn cần độ chính xác nào? nếu đó là tìm kiếm trên toàn tiểu bang/quốc gia, có thể bạn có thể thực hiện tìm kiếm lat-lon đến Zip và có khoảng cách được tính toán trước từ khu vực Zip đến khu vực Zip của nhà hàng. Nếu bạn cần khoảng cách chính xác sẽ không phải là một lựa chọn tốt.

Bạn nên xem xét một Geohash giải pháp, trong bài viết Wikipedia có một liên kết đến một thư viện PHP để mã hóa giải mã lat lat cho geohashs.

Ở đây bạn có một bài viết hay giải thích lý do và cách họ sử dụng nó trong Google App Engine (mã Python nhưng dễ theo dõi.) Vì cần sử dụng geohash trong GAE, bạn có thể tìm thấy một số tốt thư viện python và các ví dụ.

Như bài đăng trên blog này giải thích, lợi thế của việc sử dụng geohash là bạn có thể tạo một chỉ mục trên bảng MySQL trên trường đó.

6
user324

Điều này có thể là quá muộn đối với bạn, nhưng dù sao tôi cũng sẽ trả lời, với một câu trả lời tương tự như tôi đã đưa ra cho câu hỏi liên quan này , vì vậy khách truy cập trong tương lai có thể tham khảo cả hai câu hỏi.

Tôi sẽ không lưu trữ các giá trị này trong bảng siêu dữ liệu bài đăng, hoặc ít nhất là không only there. Bạn muốn một bảng có các cột post_id, lat, lon, vì vậy bạn có thể đặt một chỉ mục của lat, lon và truy vấn vào đó. Điều này không quá khó để cập nhật với một cái móc trên lưu bài đăng và cập nhật.

Khi bạn truy vấn cơ sở dữ liệu, bạn xác định hộp giới hạn xung quanh điểm bắt đầu, do đó bạn có thể thực hiện truy vấn hiệu quả cho tất cả các cặp lat, lon giữa biên giới Bắc-Nam và Đông-Tây của hộp.

Sau khi bạn nhận được kết quả giảm này, bạn có thể thực hiện phép tính khoảng cách nâng cao (vòng tròn hoặc hướng lái xe thực tế) nâng cao hơn để lọc ra các vị trí nằm trong các góc của hộp giới hạn và ở xa hơn bạn mong muốn.

Ở đây bạn tìm thấy một ví dụ mã đơn giản hoạt động trong khu vực quản trị. Bạn cần phải tự tạo bảng cơ sở dữ liệu bổ sung. Mã được sắp xếp từ hầu hết đến ít thú vị nhất.

<?php
/*
Plugin Name: Monkeyman geo test
Plugin URI: http://www.monkeyman.be
Description: Geolocation test
Version: 1.0
Author: Jan Fabry
*/

class Monkeyman_Geo
{
    public function __construct()
    {
        add_action('init', array(&$this, 'registerPostType'));
        add_action('save_post', array(&$this, 'saveLatLon'), 10, 2);

        add_action('admin_menu', array(&$this, 'addAdminPages'));
    }

    /**
     * On post save, save the metadata in our special table
     * (post_id INT, lat DECIMAL(10,5), lon DECIMAL (10,5))
     * Index on lat, lon
     */
    public function saveLatLon($post_id, $post)
    {
        if ($post->post_type != 'monkeyman_geo') {
            return;
        }
        $lat = floatval(get_post_meta($post_id, 'lat', true));
        $lon = floatval(get_post_meta($post_id, 'lon', true));

        global $wpdb;
        $result = $wpdb->replace(
            $wpdb->prefix . 'monkeyman_geo',
            array(
                'post_id' => $post_id,
                'lat' => $lat,
                'lon' => $lon,
            ),
            array('%s', '%F', '%F')
        );
    }

    public function addAdminPages()
    {
        add_management_page( 'Quick location generator', 'Quick generator', 'edit_posts', __FILE__  . 'generator', array($this, 'doGeneratorPage'));
        add_management_page( 'Location test', 'Location test', 'edit_posts', __FILE__ . 'test', array($this, 'doTestPage'));

    }

    /**
     * Simple test page with a location and a distance
     */
    public function doTestPage()
    {
        if (!array_key_exists('search', $_REQUEST)) {
            $default_lat = ini_get('date.default_latitude');
            $default_lon = ini_get('date.default_longitude');

            echo <<<EOF
<form action="" method="post">
    <p>Center latitude: <input size="10" name="center_lat" value="{$default_lat}"/>
        <br/>Center longitude: <input size="10" name="center_lon" value="{$default_lon}"/>
        <br/>Max distance (km): <input size="5" name="max_distance" value="100"/></p>
    <p><input type="submit" name="search" value="Search!"/></p>
</form>
EOF;
            return;
        }
        $center_lon = floatval($_REQUEST['center_lon']);
        $center_lat = floatval($_REQUEST['center_lat']);
        $max_distance = floatval($_REQUEST['max_distance']);

        var_dump(self::getPostsUntilDistanceKm($center_lon, $center_lat, $max_distance));
    }

    /**
     * Get all posts that are closer than the given distance to the given location
     */
    public static function getPostsUntilDistanceKm($center_lon, $center_lat, $max_distance)
    {
        list($north_lat, $east_lon, $south_lat, $west_lon) = self::getBoundingBox($center_lat, $center_lon, $max_distance);

        $geo_posts = self::getPostsInBoundingBox($north_lat, $east_lon, $south_lat, $west_lon);

        $close_posts = array();
        foreach ($geo_posts as $geo_post) {
            $post_lat = floatval($geo_post->lat);
            $post_lon = floatval($geo_post->lon);
            $post_distance = self::calculateDistanceKm($center_lat, $center_lon, $post_lat, $post_lon);
            if ($post_distance < $max_distance) {
                $close_posts[$geo_post->post_id] = $post_distance;
            }
        }
        return $close_posts;
    }

    /**
     * Select all posts ids in a given bounding box
     */
    public static function getPostsInBoundingBox($north_lat, $east_lon, $south_lat, $west_lon)
    {
        global $wpdb;
        $sql = $wpdb->prepare('SELECT post_id, lat, lon FROM ' . $wpdb->prefix . 'monkeyman_geo WHERE lat < %F AND lat > %F AND lon < %F AND lon > %F', array($north_lat, $south_lat, $west_lon, $east_lon));
        return $wpdb->get_results($sql, OBJECT_K);
    }

    /* Geographical calculations: distance and bounding box */

    /**
     * Calculate the distance between two coordinates
     * http://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates/1416950#1416950
     */
    public static function calculateDistanceKm($a_lat, $a_lon, $b_lat, $b_lon)
    {
        $d_lon = deg2rad($b_lon - $a_lon);
        $d_lat = deg2rad($b_lat - $a_lat);
        $a = pow(sin($d_lat/2.0), 2) + cos(deg2rad($a_lat)) * cos(deg2rad($b_lat)) * pow(sin($d_lon/2.0), 2);
        $c = 2 * atan2(sqrt($a), sqrt(1-$a));
        $d = 6367 * $c;

        return $d;
    }

    /**
     * Create a box around a given point that extends a certain distance in each direction
     * http://www.colorado.edu/geography/gcraft/warmup/aquifer/html/distance.html
     *
     * @todo: Mind the gap at 180 degrees!
     */
    public static function getBoundingBox($center_lat, $center_lon, $distance_km)
    {
        $one_lat_deg_in_km = 111.321543; // Fixed
        $one_lon_deg_in_km = cos(deg2rad($center_lat)) * 111.321543; // Depends on latitude

        $north_lat = $center_lat + ($distance_km / $one_lat_deg_in_km);
        $south_lat = $center_lat - ($distance_km / $one_lat_deg_in_km);

        $east_lon = $center_lon - ($distance_km / $one_lon_deg_in_km);
        $west_lon = $center_lon + ($distance_km / $one_lon_deg_in_km);

        return array($north_lat, $east_lon, $south_lat, $west_lon);
    }

    /* Below this it's not interesting anymore */

    /**
     * Generate some test data
     */
    public function doGeneratorPage()
    {
        if (!array_key_exists('generate', $_REQUEST)) {
            $default_lat = ini_get('date.default_latitude');
            $default_lon = ini_get('date.default_longitude');

            echo <<<EOF
<form action="" method="post">
    <p>Number of posts: <input size="5" name="post_count" value="10"/></p>
    <p>Center latitude: <input size="10" name="center_lat" value="{$default_lat}"/>
        <br/>Center longitude: <input size="10" name="center_lon" value="{$default_lon}"/>
        <br/>Max distance (km): <input size="5" name="max_distance" value="100"/></p>
    <p><input type="submit" name="generate" value="Generate!"/></p>
</form>
EOF;
            return;
        }
        $post_count = intval($_REQUEST['post_count']);
        $center_lon = floatval($_REQUEST['center_lon']);
        $center_lat = floatval($_REQUEST['center_lat']);
        $max_distance = floatval($_REQUEST['max_distance']);

        list($north_lat, $east_lon, $south_lat, $west_lon) = self::getBoundingBox($center_lat, $center_lon, $max_distance);


        add_action('save_post', array(&$this, 'setPostLatLon'), 5);
        $precision = 100000;
        for ($p = 0; $p < $post_count; $p++) {
            self::$currentRandomLat = mt_Rand($south_lat * $precision, $north_lat * $precision) / $precision;
            self::$currentRandomLon = mt_Rand($west_lon * $precision, $east_lon * $precision) / $precision;

            $location = sprintf('(%F, %F)', self::$currentRandomLat, self::$currentRandomLon);

            $post_data = array(
                'post_status' => 'publish',
                'post_type' => 'monkeyman_geo',
                'post_content' => 'Point at ' . $location,
                'post_title' => 'Point at ' . $location,
            );

            var_dump(wp_insert_post($post_data));
        }
    }

    public static $currentRandomLat = null;
    public static $currentRandomLon = null;

    /**
     * Because I didn't know how to save meta data with wp_insert_post,
     * I do it here
     */
    public function setPostLatLon($post_id)
    {
        add_post_meta($post_id, 'lat', self::$currentRandomLat);
        add_post_meta($post_id, 'lon', self::$currentRandomLon);
    }

    /**
     * Register a simple post type for us
     */
    public function registerPostType()
    {
        register_post_type(
            'monkeyman_geo',
            array(
                'label' => 'Geo Location',
                'labels' => array(
                    'name' => 'Geo Locations',
                    'singular_name' => 'Geo Location',
                    'add_new' => 'Add new',
                    'add_new_item' => 'Add new location',
                    'edit_item' => 'Edit location',
                    'new_item' => 'New location',
                    'view_item' => 'View location',
                    'search_items' => 'Search locations',
                    'not_found' => 'No locations found',
                    'not_found_in_trash' => 'No locations found in trash',
                    'parent_item_colon' => null,
                ),
                'description' => 'Geographical locations',
                'public' => true,
                'exclude_from_search' => false,
                'publicly_queryable' => true,
                'show_ui' => true,
                'menu_position' => null,
                'menu_icon' => null,
                'capability_type' => 'post',
                'capabilities' => array(),
                'hierarchical' => false,
                'supports' => array(
                    'title',
                    'editor',
                    'custom-fields',
                ),
                'register_meta_box_cb' => null,
                'taxonomies' => array(),
                'permalink_epmask' => EP_PERMALINK,
                'rewrite' => array(
                    'slug' => 'locations',
                ),
                'query_var' => true,
                'can_export' => true,
                'show_in_nav_menus' => true,
            )
        );
    }
}

$monkeyman_Geo_instance = new Monkeyman_Geo();
9
Jan Fabry

Tôi đến bữa tiệc muộn, nhưng nhìn lại, get_post_meta thực sự là vấn đề ở đây, thay vì truy vấn SQL bạn đang sử dụng.

Gần đây tôi đã phải thực hiện một tìm kiếm địa lý tương tự trên một trang web mà tôi điều hành, thay vì sử dụng bảng meta để lưu trữ lat và lon (yêu cầu tối đa hai liên kết để tra cứu và, nếu bạn đang sử dụng get_post_meta, hai cơ sở dữ liệu bổ sung truy vấn trên mỗi vị trí), tôi đã tạo một bảng mới với kiểu dữ liệu POINT hình học được lập chỉ mục không gian.

Truy vấn của tôi trông rất giống với truy vấn của bạn, với MySQL thực hiện rất nhiều công việc nặng nhọc (tôi đã bỏ qua các chức năng trig và đơn giản hóa mọi thứ thành không gian hai chiều, vì nó đủ gần với mục đích của tôi):

function nearby_property_listings( $number = 5 ) {
    global $client_location, $wpdb;

    //sanitize public inputs
    $lat = (float)$client_location['lat'];  
    $lon = (float)$client_location['lon']; 

    $sql = $wpdb->prepare( "SELECT *, ROUND( SQRT( ( ( ( Y(geolocation) - $lat) * 
                                                       ( Y(geolocation) - $lat) ) *
                                                         69.1 * 69.1) +
                                                  ( ( X(geolocation) - $lon ) * 
                                                       ( X(geolocation) - $lon ) * 
                                                         53 * 53 ) ) ) as distance
                            FROM {$wpdb->properties}
                            ORDER BY distance LIMIT %d", $number );

    return $wpdb->get_results( $sql );
}

trong đó $ client_location là một giá trị được trả về bởi dịch vụ tra cứu IP địa lý công cộng (tôi đã sử dụng Geoio.com, nhưng có một số giá trị tương tự.)

Nó có vẻ khó sử dụng, nhưng khi thử nghiệm, nó đã liên tục trả về 5 vị trí gần nhất trong số 80.000 hàng trong vòng dưới 4 giây.

Cho đến khi MySQL triển khai chức năng DISTANCE đang được đề xuất, đây có vẻ là cách tốt nhất tôi tìm thấy để thực hiện tra cứu vị trí.

EDIT: Thêm cấu trúc bảng cho bảng cụ thể này. Đây là một bộ danh sách tài sản, vì vậy nó có thể giống hoặc không giống với bất kỳ trường hợp sử dụng nào khác.

CREATE TABLE IF NOT EXISTS `rh_properties` (
  `listingId` int(10) unsigned NOT NULL,
  `listingType` varchar(60) collate utf8_unicode_ci NOT NULL,
  `propertyType` varchar(60) collate utf8_unicode_ci NOT NULL,
  `status` varchar(20) collate utf8_unicode_ci NOT NULL,
  `street` varchar(64) collate utf8_unicode_ci NOT NULL,
  `city` varchar(24) collate utf8_unicode_ci NOT NULL,
  `state` varchar(5) collate utf8_unicode_ci NOT NULL,
  `Zip` decimal(5,0) unsigned zerofill NOT NULL,
  `geolocation` point NOT NULL,
  `county` varchar(64) collate utf8_unicode_ci NOT NULL,
  `bedrooms` decimal(3,2) unsigned NOT NULL,
  `bathrooms` decimal(3,2) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `image_url` varchar(255) collate utf8_unicode_ci NOT NULL,
  `description` mediumtext collate utf8_unicode_ci NOT NULL,
  `link` varchar(255) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`listingId`),
  KEY `geolocation` (`geolocation`(25))
)

Cột geolocation là điều duy nhất có liên quan cho các mục đích ở đây; nó bao gồm các tọa độ x (lon), y (lat) mà tôi chỉ cần tra cứu từ địa chỉ khi nhập giá trị mới vào cơ sở dữ liệu.

1
goldenapples

Chỉ cần tính toán trước khoảng cách giữa tất cả các thực thể. Tôi sẽ lưu trữ nó vào một bảng cơ sở dữ liệu, với khả năng lập chỉ mục các giá trị.

0
hakre