it-swarm.asia

أمثلية البحث عن موقع مخزن قائم على القرب على مضيف ويب مشترك؟

لدي مشروع حيث أحتاج إلى بناء محدد موقع متجر للعميل.

أنا أستخدم نوعًا مخصصًا للنشر "restaurant-location" وقد قمت بكتابة الكود لترميز العناوين المخزنة في postmeta باستخدام Google Geocoding API (يشير إلى الرابط الذي الترميز الجغرافي للبيت الأبيض الأمريكي في JSON و لقد قمت بتخزين خطوط الطول والعودة إلى الحقول المخصصة.

لقد كتبت دالة get_posts_by_geo_distance() تقوم بإرجاع قائمة المنشورات بترتيب تلك الأقرب من الناحية الجغرافية باستخدام الصيغة التي عثرت عليها في عرض الشرائح في هذا المنشور . يمكنك استدعاء وظيفتي هكذا (أبدأ بـ "مصدر" ثابت/طويل):

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;

إليك الوظيفة 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);
}

ما يقلقني هو أن SQL غير محسنة قدر الإمكان. لا يمكن لـ MySQL طلب أي فهرس متوفر لأن source geo غير قابل للتغيير وليس هناك مجموعة محددة من geos المصدر للتخزين المؤقت. حاليا أنا متعثر بشأن طرق تحسينه.

مع الأخذ في الاعتبار ما قمت به بالفعل ، فإن السؤال هو: كيف ستستفيد من تحسين حالة الاستخدام هذه؟

ليس من المهم أن أحتفظ بأي شيء قمت به إذا كان الحل الأفضل يجعلني أرميه. (أنا مستعد لدراسة أي حل تقريبًا باستثناء أحد الحلول التي تتطلب القيام بشيء مثل تثبيت خادم Sphinx أو أي شيء يتطلب تهيئة MySQL مخصصة. في الأساس ، يجب أن يكون الحل قادراً على العمل على أي تثبيت سهل لـ Vanilla WordPress. (ومع ذلك ، سيكون من الرائع أن يرغب أي شخص في إدراج حلول بديلة للآخرين الذين قد يكونون قادرين على الحصول على مزيد من التقدم والأجيال القادمة).

الموارد وجدت

لمعلوماتك ، لقد أجريت القليل من البحث حول هذا الأمر بدلاً من أن تقوم بإجراء البحث مرة أخرى أو بدلاً من أن تقوم بنشر أي من هذه الروابط كإجابة سأذهب إليها وتضمينها.

فيما يتعلق بأبو الهول البحث

11
MikeSchinkel

ما الدقة التي تحتاجها؟ إذا كان البحث على مستوى الولاية/الولاية القومية ، فيمكنك إجراء بحث تفصيلي إلى Zip ، كما يمكنك إجراء مسافة مسبقة من منطقة Zip إلى منطقة Zip في المطعم. إذا كنت بحاجة إلى مسافات دقيقة فلن يكون ذلك خيارًا جيدًا.

يجب أن تبحث في Geohash الحل ، في مقالة Wikipedia يوجد رابط إلى مكتبة PHP لتشفير فك تشفير الخط الطول إلى geohashs.

هنا لديك مقالة جيدة تشرح لماذا وكيف يستخدمونها في Google App Engine (رمز Python ولكن من السهل متابعتها.) نظرًا للحاجة إلى استخدام geohash في GAE ، يمكنك العثور على بعض الميزات الجيدة الثعبان المكتبات والأمثلة.

كما هذا بلوق وظيفة يفسر ، ميزة استخدام geohashes هو أنه يمكنك إنشاء فهرس على جدول MySQL في هذا المجال.

6
user324

قد يكون هذا متأخراً للغاية بالنسبة لك ، لكنني سأجيب على أي حال ، مع إجابة مماثلة كما أعطيت لهذا السؤال ذي الصلة ، بحيث يمكن للزوار في المستقبل الرجوع إلى كلا السؤالين.

لن أخزن هذه القيم في جدول بيانات تعريف النشر ، أو على الأقل لا فقط هناك. تريد جدولًا يحتوي على أعمدة post_id و lat و lon ، بحيث يمكنك وضع فهرس بـ lat, lon والاستعلام عن ذلك. لا ينبغي أن يكون هذا أمرًا صعبًا للغاية لمواكبة التحديث مع وجود رابط للحفظ والتحديث.

عند الاستعلام عن قاعدة البيانات ، يمكنك تحديد مربع الإحاطة حول نقطة البداية ، بحيث يمكنك القيام باستعلام فعال لجميع أزواج lat, lon بين الحدود الشمالية والجنوبية والشرقية - الغربية من المربع.

بعد حصولك على هذه النتيجة المخفضة ، يمكنك القيام بحساب مسافة أكثر تقدمًا (اتجاهات دائرية أو فعلية) لتصفية المواقع الموجودة في زوايا المربع المحيط وما إلى ذلك بعيدًا عما تريد.

تجد هنا مثالًا بسيطًا للرمز يعمل في منطقة المشرف. تحتاج إلى إنشاء جدول قاعدة البيانات الإضافية بنفسك. يتم طلب الرمز من الأكثر إلى الأقل إثارة للاهتمام.

<?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

لقد تأخرت عن هذا الطرف ، لكن بالنظر إلى الوراء في هذا ، فإن get_post_meta هي في الحقيقة المشكلة هنا ، بدلاً من استعلام SQL الذي تستخدمه.

اضطررت مؤخرًا إلى إجراء بحث جغرافي مماثل على أحد المواقع التي أقوم بتشغيلها ، وبدلاً من استخدام جدول التعريف لتخزين خطوط الطول والعين (والتي تتطلب في أفضل الأحوال وصلين للبحث عنهما ، وإذا كنت تستخدم get_post_meta ، فتوجد قاعدة بيانات إضافية استعلامات لكل موقع) ، قمت بإنشاء جدول جديد مع نوع البيانات POINT هندسة فهرسة مكانيا.

بدا طلب البحث الخاص بي يشبه إلى حد كبير استفسارك ، مع قيام MySQL بالكثير من الأحمال الثقيلة (لقد تركت وظائف علم حساب المثلثات وسهّلت كل شيء إلى الفضاء ثنائي الأبعاد ، لأنه كان قريبًا بما فيه الكفاية لأغراضي):

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 );
}

حيث يمثل $ client_location قيمة يتم إرجاعها بواسطة خدمة بحث IP جغرافية عامة (لقد استخدمت geoio.com ، ولكن هناك عددًا من الأنواع المشابهة).

قد يبدو هذا غير عملي ، ولكن عند اختباره ، قام بإرجاع أقرب 5 مواقع من جدول يحتوي على 80،000 صف في أقل من 0.4 ثانية.

حتى يتم طرح MySQL لوظيفة DISTANCE المقترحة ، يبدو أن هذا هو أفضل طريقة وجدت لتنفيذ عمليات البحث عن الموقع.

تحرير: إضافة بنية الجدول لهذا الجدول معين. لها مجموعة من قوائم الممتلكات ، لذلك قد تكون أو لا تكون مماثلة لأي حالة استخدام أخرى.

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))
)

العمود geolocation هو الشيء الوحيد المناسب للأغراض هنا ؛ يتكون من إحداثيات x (lon) و y (lat) التي أبحث عنها فقط من العنوان عند استيراد قيم جديدة إلى قاعدة البيانات.

1
goldenapples

فقط قبل حساب المسافات بين جميع الكيانات. أود تخزين ذلك في جدول قاعدة بيانات بمفرده ، مع إمكانية فهرسة القيم.

0
hakre