Laravel+Swoole+PHP-ml 实现根据经纬度返回对应城市

今天不是很忙,写个小东西分享给大家。

https://github.com/Aquarmini/laravel-phpml...

首先我们新建个项目

composer create laravel/laravel
composer require swooletw/laravel-swoole
composer require php-ai/php-ml

然后我们把配置生成一下,选择 SwooleTW\Http\LaravelServiceProvider

php artisan vendor:publish

接下来生成一下模型

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * @property $oid
 * @property $area_name
 * @property $level
 * @property $parent_oid
 * @property $province_oid
 * @property $city_oid
 * @property $county_oid
 * @property $area_code
 * @property $simple_name
 * @property $lon
 * @property $lat
 * @property $zip_code
 * @property $whole_name
 * @property $whole_oid
 * @property $pre_pin_yin
 * @property $pin_yin
 * @property $simple_py
 * @property $remark
 * @property $version
 */
class PoiDistricts extends Model
{
    /**
     * The primary key for the model.
     *
     * @var string
     */
    public $primaryKey = 'oid';

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'poi_districts';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['oid', 'area_name', 'level', 'parent_oid', 'province_oid', 'city_oid', 'county_oid', 'area_code', 'simple_name', 'lon', 'lat', 'zip_code', 'whole_name', 'whole_oid', 'pre_pin_yin', 'pin_yin', 'simple_py', 'remark', 'version'];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = ['oid' => 'integer', 'level' => 'integer', 'parent_oid' => 'integer', 'province_oid' => 'integer', 'city_oid' => 'integer', 'county_oid' => 'integer', 'lon' => 'float', 'lat' => 'float', 'version' => 'float'];
}

接下来我们写一个生成训练用数据的脚本。

<?php

namespace App\Console\Commands;

use App\PoiDistricts;
use Illuminate\Console\Command;
use Swoole\Runtime;

class PoiDistrictCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'ml:districts';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generate samples and labels.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        Runtime::enableCoroutine(false);

        $arrays = PoiDistricts::query()->where('level', 5)->get(['parent_oid', 'lon', 'lat']);

        $input = [];
        $result = [];
        foreach ($arrays as $item) {
            if (is_numeric($item['lon']) && is_numeric($item['lat'])) {
                $input[] = [
                    $item['lon'],
                    $item['lat']
                ];
                $result[] = $item['parent_oid'];
            }
        }

        file_put_contents(base_path('storage/samples'), serialize($input));
        file_put_contents(base_path('storage/labels'), serialize($result));
    }
}

接下来生成我们的训练数据

php artisan ml:districts

这时,就可以在storage目录下看到我们的训练数据 sampleslabels 了。
继续我们的开发,后面就是编写路由和控制器

<?php
# Route::get('/', 'IndexController@index');

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class IndexController extends Controller
{
    public function index(Request $request)
    {
        $lon = $request->input('lon');
        $lat = $request->input('lat');

        // TODO: 查询对应数据
        return [];
    }
}

接下来完善我们用于训练数据的类

<?php

namespace App;

use Illuminate\Container\Container;
use Phpml\Classification\KNearestNeighbors;

class PoiDistrictService
{
    /**
     * @var KNearestNeighbors
     */
    protected $classifier;

    protected $container;

    public function __construct(Container $container)
    {
        $this->container = $container;

        $samples = unserialize(file_get_contents(base_path('storage/samples')));
        $labels = unserialize(file_get_contents(base_path('storage/labels')));

        $this->classifier = new KNearestNeighbors();
        $this->classifier->train($samples, $labels);
    }

    /**
     * @return KNearestNeighbors
     */
    public function getClassifier(): KNearestNeighbors
    {
        return $this->classifier;
    }
}

然后在AppServiceProvider中注册一下我们的训练库

<?php

namespace App\Providers;

use App\PoiDistrictService;
use Illuminate\Container\Container;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        Container::getInstance()->singleton(PoiDistrictService::class, function ($app) {
            return new PoiDistrictService($app);
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }
}

接下来就简单了,重写我们之前的控制器。

<?php

namespace App\Http\Controllers;

use App\PoiDistricts;
use App\PoiDistrictService;
use Illuminate\Http\Request;

class IndexController extends Controller
{
    public function index(Request $request)
    {
        $lon = $request->input('lon');
        $lat = $request->input('lat');

        // 查询对应数据
        $client = app()->get(PoiDistrictService::class)->getClassifier();
        $id = $client->predict([$lon, $lat]);

        $model = PoiDistricts::query()->where('oid', $id)->first();
        return $model->toArray();
    }
}

最后就是测试一下了

php artisan swoole:http start

curl http://127.0.0.1:1215/?lon=119.2716979980&lat=36.7608912496
{
    "oid": 1782,
    "area_name": "奎文区",
    "level": 4,
    "parent_oid": 177,
    "province_oid": 6,
    "city_oid": 177,
    "county_oid": 1782,
    "area_code": "0536",
    "simple_name": "奎文",
    "lon": 119.12532,
    "lat": 36.70723,
    "zip_code": "261000",
    "whole_name": "中国,山东省,潍坊市,奎文区",
    "whole_oid": "1,6,177,1782",
    "pre_pin_yin": "K",
    "pin_yin": "Kuiwen",
    "simple_py": "KW",
    "remark": "",
    "version": 1
}

响应时间100ms左右,其实也还不错。
并发场景下还是和专门为swoole开发的框架有一定差距。
file
file
不过差距也不太大咯,反正也没人会在PHP里搞这个吧。。

本作品采用《CC 协议》,转载必须注明作者和本文链接
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
本帖由系统于 4年前 自动加精
讨论数量: 5

很有意思,感谢分享

5年前 评论

有点意思~顺手赞一个

5年前 评论

这个有意思了.
想到我们之前是通过前端 js 调取腾讯地图实现的.

5年前 评论
李铭昕

@Ali 是的,不过PHP计算性能不大好。也只能玩玩了。。

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!