Статьи

Создание вашего стартапа с помощью PHP: геолокация и Google Places

Конечный продукт
Что вы будете создавать

Это третья часть серии « Построение стартапа на PHP» на Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе мы будем публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, которые вы можете изучить.

В этой части мы собираемся построить некоторую базовую инфраструктуру для концепции Мест, где люди могут планировать встречи. Мы расскажем об основах работы с Places, построении на основе схемы нашей базы данных, интеграции геолокации HTML5 и API для Google Maps и Google Places. Идея состоит в том, чтобы использовать эти функции, чтобы сделать выбор места для ваших встреч быстрым и простым. В этом эпизоде ​​мы не рассмотрим все подгонки и отделки, но об этом мы расскажем в следующем уроке.

Весь код для Meeting Planner написан на Yii2 Framework для PHP и использует Bootstrap и JavaScript. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 на Tuts +.

Просто напоминание, я участвую в комментариях ниже. Мне особенно интересно, если у вас есть другие подходы или дополнительные идеи, или вы хотите предложить темы для будущих уроков. Также приветствуются запросы функций для Планировщика собраний.

Прежде чем пользователи смогут планировать встречи, нам нужно, чтобы они могли найти и предложить свои любимые места. Первоначально для простоты мы будем создавать функции поиска и создания объектов Place отдельно от функции планирования.

У пользователей есть три способа добавления мест:

  1. Используя HTML5 Geolocation, они могут посмотреть свое текущее местоположение через Wi-Fi и добавить его как место.
  2. Используя Google Places API, они могут искать место в базе данных Places с помощью автозаполнения. В конце концов, когда мы узнаем их текущее местоположение, мы можем ограничить результаты поиска ближайшими местами.
  3. Ручной ввод. Пользователи могут ввести адрес и описание для своего места, например, офиса или дома.

Вот схема для Мест, которые мы разработали во второй части :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
$tableOptions = null;
     if ($this->db->driverName === ‘mysql’) {
         $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
     }
 
     $this->createTable(‘{{%place}}’, [
         ‘id’ => Schema::TYPE_PK,
         ‘name’ => Schema::TYPE_STRING.’
         ‘place_type’ => Schema::TYPE_SMALLINT.’
         ‘status’ => Schema::TYPE_SMALLINT .
         ‘google_place_id’ => Schema::TYPE_STRING.’
         ‘created_by’ => Schema::TYPE_BIGINT.’
         ‘created_at’ => Schema::TYPE_INTEGER .
         ‘updated_at’ => Schema::TYPE_INTEGER .
     ], $tableOptions);
     $this->addForeignKey(‘fk_place_created_by’, ‘{{%place}}’, ‘created_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);

Обратите внимание, что в этой таблице нет геолокации, связанной с местом. Это потому, что движок MySQL InnoDB не поддерживает пространственные индексы . Поэтому я создал дополнительную таблицу, используя таблицу MyISAM для координат геолокации мест. Это таблица Place_GPS :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class m141025_213611_create_place_gps_table extends Migration
{
  public function up()
  {
      $tableOptions = null;
      if ($this->db->driverName === ‘mysql’) {
          $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=MyISAM’;
      }
  
      $this->createTable(‘{{%place_gps}}’, [
          ‘id’ => Schema::TYPE_PK,
          ‘place_id’ => Schema::TYPE_INTEGER.’
          ‘gps’=>’POINT NOT NULL’,
      ], $tableOptions);
      $this->execute(‘create spatial index place_gps_gps on ‘.'{{%place_gps}}(gps);’);
      $this->addForeignKey(‘fk_place_gps’,'{{%place_gps}}’ , ‘place_id’, ‘{{%place}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
  }

Поскольку я нахожусь в режиме быстрого прототипирования, я собираюсь расширить схему с помощью миграций Yii, и в конечном итоге я также могу внести коррективы в будущем.

Чтобы расширить схему, мы создаем новую миграцию в Yii:

1
./yii migrate/create extend_place_table

И предоставьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
 
use yii\db\Schema;
use yii\db\Migration;
 
class m150114_202542_extend_place_table extends Migration
{
    public function up()
    {
      $tableOptions = null;
      if ($this->db->driverName === ‘mysql’) {
          $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
      }
      $this->addColumn(‘{{%place}}’,’slug’,’string NOT NULL’);
      $this->addColumn(‘{{%place}}’,’website’,’string NOT NULL’);
      $this->addColumn(‘{{%place}}’,’full_address’,’string NOT NULL’);
      $this->addColumn(‘{{%place}}’,’vicinity’,’string NOT NULL’);
      $this->addColumn(‘{{%place}}’,’notes’,’text’);
    }
 
    public function down()
    {
      $this->dropColumn(‘{{%place}}’,’slug’);
      $this->dropColumn(‘{{%place}}’,’website’);
      $this->dropColumn(‘{{%place}}’,’full_address’);
      $this->dropColumn(‘{{%place}}’,’vicinity’);
      $this->dropColumn(‘{{%place}}’,’notes’);
    }
}

Это добавит столбцы для slug, веб-сайта, full_address, окрестностей и заметок. Плагин — это удобный для URL адрес для отображения страницы просмотра места, которую Yii может сгенерировать для нас автоматически. Другие поля будут иногда обновляться пользователями, а другие заполняются из API Google Адресов.

Чтобы запустить миграцию, мы вводим следующее:

1
./yii migrate/up

Вы должны увидеть следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Yii Migration Tool (based on Yii v2.0.0)
 
Total 1 new migration to be applied:
    m150114_202542_extend_place_table
 
Apply the above migration?
*** applying m150114_202542_extend_place_table
    > add column slug string NOT NULL to table {{%place}} … done (time: 0.011s)
    > add column website string NOT NULL to table {{%place}} … done (time: 0.010s)
    > add column full_address string NOT NULL to table {{%place}} … done (time: 0.010s)
    > add column vicinity string NOT NULL to table {{%place}} … done (time: 0.011s)
    > add column notes text to table {{%place}} … done (time: 0.011s)
*** applied m150114_202542_extend_place_table (time: 0.055s)
 
 
Migrated up successfully.

Если вы посетите страницу «Метки», например, http: // localhost: 8888 / mp / index.php / place / create , вы увидите автоматически генерируемую форму Yii2 со всеми полями схемы:

Создать место Стандартная форма Yii2

Для этого урока я повторно запустил генератор кода Yii, Gii, используя шаги второй части для создания кода для новой схемы базы данных. Я поручил Gii переписать код CRUD из ранее.

Примечание. Возможно, вам будет проще заменить источник выборки из второй части источником выборки из этой части. Смотрите ссылку на Github в правом верхнем углу.

Вам также необходимо обновить библиотеки наших поставщиков с помощью composer, чтобы интегрировать поддержку библиотек Google Maps и Places 2amigOS Yii2 . Вот часть нашего файла composer.json:

1
2
3
4
5
6
7
8
«minimum-stability»: «stable»,
   «require»: {
       «php»: «>=5.4.0»,
       «yiisoft/yii2»: «*»,
       «yiisoft/yii2-bootstrap»: «*»,
       «yiisoft/yii2-swiftmailer»: «*»,
       «2amigos/yii2-google-maps-library»: «*»,
       «2amigos/yii2-google-places-library»: «*»

Затем запустите обновление композитора, чтобы загрузить файлы:

1
sudo composer update

На самом деле мы собираемся построить три различных действия и формы контроллера для каждого из этих типов мест. Помните, что мы должны также интегрировать связанную модель PlaceGPS , чтобы хранить координаты GPS для каждого места, независимо от того, как пользователь его добавляет.

Чтобы добавить ссылку « /views/layouts/main.php на панель навигации, отредактируйте /views/layouts/main.php . Это макет страницы по умолчанию, в который Yii оборачивает все наши файлы представлений. Он включает в себя заголовок, панель навигации Bootstrap и нижний колонтитул.

Ниже в $menuItems я добавляю запись массива для меню Place:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
NavBar::begin([
               ‘brandLabel’ => ‘MeetingPlanner.io’, //
               ‘brandUrl’ => Yii::$app->homeUrl,
               ‘options’ => [
                   ‘class’ => ‘navbar-inverse navbar-fixed-top’,
               ],
           ]);
           $menuItems = [
               [‘label’ => ‘Home’, ‘url’ => [‘/site/index’]],
               [‘label’ => ‘Places’, ‘url’ => [‘/place’]],
               [‘label’ => ‘About’, ‘url’ => [‘/site/about’]],
               [‘label’ => ‘Contact’, ‘url’ => [‘/site/contact’]],
           ];
           if (Yii::$app->user->isGuest) {
               $menuItems[] = [‘label’ => ‘Signup’, ‘url’ => [‘/site/signup’]];
               $menuItems[] = [‘label’ => ‘Login’, ‘url’ => [‘/site/login’]];
           } else {
               $menuItems[] = [
                   ‘label’ => ‘Logout (‘ . Yii::$app->user->identity->username . ‘)’,
                   ‘url’ => [‘/site/logout’],
                   ‘linkOptions’ => [‘data-method’ => ‘post’]
               ];
           }
           echo Nav::widget([
               ‘options’ => [‘class’ => ‘navbar-nav navbar-right’],
               ‘items’ => $menuItems,
           ]);
           NavBar::end();
       ?>

Индекс мест будет выглядеть следующим образом после добавления кнопок для трех способов добавления мест:

Страница указателя места встречи

В /views/place/index.php мы можем добавить три кнопки add place :

1
2
3
4
5
<p>
       <?= Html::a(‘Add Place’, [‘create’], [‘class’ => ‘btn btn-success’]) ?>
       <?= Html::a(‘Add Current Location’, [‘create_geo’], [‘class’ => ‘btn btn-success’]) ?>
       <?= Html::a(‘Add a Google Place’, [‘create_place_google’], [‘class’ => ‘btn btn-success’]) ?>
   </p>

И мы можем настроить столбцы, которые отображаются в представлении, включая создание настраиваемого столбца для метода Place, который отображает понятное имя для Place Type:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<?= GridView::widget([
       ‘dataProvider’ => $dataProvider,
       ‘filterModel’ => $searchModel,
       ‘columns’ => [
           [‘class’ => ‘yii\grid\SerialColumn’],
           ‘name’,
           [
               ‘attribute’ => ‘place_type’,
               ‘format’ => ‘raw’,
               ‘value’ => function ($model) {
                           return ‘<div>’.$model->getPlaceType($model->place_type).'</div>’;
                   },
           ],
           [‘class’ => ‘yii\grid\ActionColumn’],
       ],
   ]);

Вот подмножество методов Place Type в /models/Place.php :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const TYPE_OTHER = 0;
   const TYPE_RESTAURANT = 10;
   const TYPE_COFFEESHOP = 20;
   const TYPE_RESIDENCE = 30;
   const TYPE_OFFICE = 40;
   const TYPE_BAR = 50;
    
   …
    
   public function getPlaceType($data) {
     $options = $this->getPlaceTypeOptions();
     return $options[$data];
   }
    
   public function getPlaceTypeOptions()
   {
     return array(
         self::TYPE_RESTAURANT => ‘Restaurant’,
         self::TYPE_COFFEESHOP => ‘Coffeeshop’,
         self::TYPE_RESIDENCE => ‘Residence’,
         self::TYPE_OFFICE => ‘Office’,
         self::TYPE_BAR => ‘Bar’,
           self::TYPE_OTHER => ‘Other’
        );
    }

Обратите внимание, что мы еще не указали состояние входа в систему или владение пользователями мест. Мы вернемся к этому в следующем уроке. Из-за сложности и масштаба этого этапа мы оставим несколько готовых элементов для последующего урока.

Один из сценариев добавления мест — создать место для дома или офиса. Вместо того, чтобы требовать, чтобы пользователи вводили эту информацию вручную, мы часто можем автоматически генерировать ее с помощью геолокации HTML5.

HTML5 Geolocation использует ваш WiFi-адрес для определения точек GPS для вашего текущего местоположения. Он не работает с сотовой / мобильной связью и не является надежным.

Пользователю, вероятно, потребуется предоставить разрешение своему браузеру для определения местоположения, чтобы эта функция работала. Найдите всплывающее окно под адресной строкой, как показано ниже:

Браузер Chrome запрашивает разрешение для геолокации

Я использую скрипт геопозиции от estebanav для поддержки геолокации HTML5 с максимально широкой поддержкой браузеров.

В frontend/controllers/PlaceController.php мы создадим новый метод для действия Create_geo :

01
02
03
04
05
06
07
08
09
10
11
12
13
/**
     * Creates a new Place model via Geolocation
     */
    public function actionCreate_geo()
    {
        $model = new Place();
        if ($model->load(Yii::$app->request->post())) {
           … to be explained below…
       } else {
            return $this->render(‘create_geo’, [
                ‘model’ => $model,
            ]);
        }

Поскольку форма еще не отправлена, Yii отобразит представление create_geo для отображения формы.

В frontend/views/place/create_geo.php мы включим _formGeolocate.php :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<?php
 
use yii\helpers\Html;
 
 
/* @var $this yii\web\View */
/* @var $model frontend\models\Place */
 
$this->title = ‘Create Place By Geolocation’;
$this->params[‘breadcrumbs’][] = [‘label’ => ‘Places’, ‘url’ => [‘index’]];
$this->params[‘breadcrumbs’][] = $this->title;
?>
<div class=»place-create»>
 
    <h1><?= Html::encode($this->title) ?></h1>
 
    <?= $this->render(‘_formGeolocate’, [
        ‘model’ => $model,
    ]) ?>
 
</div>

Давайте посмотрим на первую часть _formGeolocate . Мы должны включить JavaScript для Geoposition.js, а также наш собственный код геолокации, чтобы интегрировать геопозицию с нашей формой. Yii делает это с помощью Asset Bundles . Вы определяете Asset Bundle для разных страниц, и это позволяет вам оптимизировать, какие JS и CSS загружаются в разных областях вашего приложения. LocateAsset мы создадим LocateAsset :

1
2
3
4
5
6
7
8
?php
 
use yii\helpers\Html;
use yii\helpers\BaseHtml;
use yii\widgets\ActiveForm;
 
use frontend\assets\LocateAsset;
LocateAsset::register($this);

В frontend/assets/LocateAsset.php мы определим JavaScript, который нам нужно включить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<?php
 
namespace frontend\assets;
 
use yii\web\AssetBundle;
 
class LocateAsset extends AssetBundle
{
    public $basePath = ‘@webroot’;
    public $baseUrl = ‘@web’;
    public $css = [
    ];
    public $js = [
      ‘js/locate.js’,
      ‘js/geoPosition.js’,
      ‘http://maps.google.com/maps/api/js?sensor=false’,
    ];
    public $depends = [
    ];
}

LocateAsset загружает API Карт Google, библиотеку geoPosition и наш собственный код Locate.js, который показан ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function beginSearch() {
  $(‘#preSearch’).hide();
  $(‘#searchArea’).removeClass(‘hidden’);
  //if (navigator.geolocation) { //navigator.
  if (geoPosition.init()) {
    geoPosition.getCurrentPosition(success, errorHandler, {timeout:5000});
  } else {
    error(‘Sorry, we are not able to use browser geolocation to find you.’);
  }
}
 
function success(position) {
  $(‘#actionBar’).removeClass(‘hidden’);
  $(‘#autolocateAlert’).addClass(‘hidden’);
  var s = document.querySelector(‘#status’);
  //var buttons = document.querySelector(‘#locate_actions’);
  if (s.className == ‘success’) {
    // not sure why we’re hitting this twice in FF, I think it’s to do with a cached result coming back
    return;
  }
   
  s.innerHTML = «You are here:»;
  s.className = ‘success’;
   
  var mapcanvas = document.createElement(‘div’);
  mapcanvas.id = ‘mapcanvas’;
  mapcanvas.style.height = ‘300px’;
  mapcanvas.style.width = ‘300px’;
  mapcanvas.style.border = ‘1px solid black’;
     
  document.querySelector(‘article’).appendChild(mapcanvas);
   
  var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
  var myOptions = {
    zoom: 16,
    center: latlng,
    mapTypeControl: false,
    navigationControlOptions: {style: google.maps.NavigationControlStyle.SMALL},
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById(«mapcanvas»), myOptions);
   
  var marker = new google.maps.Marker({
      position: latlng,
      map: map,
      title:»You are here! (at least within a «+position.coords.accuracy+» meter radius)»
  });
  $(‘#locate_actionbar’).removeClass(‘hidden’);
  $(‘#place-lat’).val(position.coords.latitude);
  $(‘#place-lng’).val(position.coords.longitude);
}
 
function errorHandler(err) {
  var s = document.querySelector(‘#status’);
  s.innerHTML = typeof msg == ‘string’ ?
  s.className = ‘fail’;
  //if (err.code == 1) {} // user said no!
  document.location.href=’/place/index?errorLocate’;
}

По сути, геолокация инициируется, когда пользователь запускает beginSearch . Код Geoposition вызывает функцию успеха, когда возвращается с местоположением пользователя. Мы настраиваем функцию успеха, чтобы отображать карту в этом месте и заполнять поля скрытой формы возвращаемой широтой и долготой. Когда пользователь публикует форму, координаты местоположения будут доступны нашему веб-приложению.

Вот код в Success() который заполняет поля формы координатами местоположения:

1
2
$(‘#place-lat’).val(position.coords.latitude);
$(‘#place-lng’).val(position.coords.longitude);

Остальная часть _formGeolocate.php разделена на две равные половины. С левой стороны мы предоставляем поля формы, в которые пользователь может ввести данные геолокации и скрытые поля, которые нам нужны для поддержки JavaScript. На правой стороне мы оставляем место для кнопки для запуска геолокации и для отображения карты. Функция success() заполняет <article> картой.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<div class=»place-form»>
  <?php $form = ActiveForm::begin();
<div class=»col-md-6″>
 
    <?= $form->field($model, ‘name’)->textInput([‘maxlength’ => 255]) ?>
 
    <?= $form->field($model, ‘website’)->textInput([‘maxlength’ => 255]) ?>
 
    <?= $form->field($model, ‘place_type’)
            ->dropDownList(
                $model->getPlaceTypeOptions(),
                [‘prompt’=>’What type of place is this?’]
            )->label(‘Type of Place’) ?>
             
    <?= $form->field($model, ‘notes’)->textArea() ?>
 
    <?= BaseHtml::activeHiddenInput($model, ‘lat’);
    <?= BaseHtml::activeHiddenInput($model, ‘lng’);
 
    <div class=»form-group»>
        <?= Html::submitButton($model->isNewRecord ? ‘Create’ : ‘Update’, [‘class’ => $model->isNewRecord ? ‘btn btn-success’ : ‘btn btn-primary’]) ?>
    </div>
 
</div> <!— end col 1 —><div class=»col-md-6″>
<div id=»preSearch» class=»center»>
<p><br /></p> <?= Html::a(‘Lookup Location’, [‘lookup’], [‘class’ => ‘btn btn-success’, ‘onclick’ => «javascript:beginSearch();return false;»]) ?>
</div>
 
  <div id=»searchArea» class=»hidden»>
    <div id=»autolocateAlert»>
    </div> <!— end autolocateAlert —>
    <p>Searching for your current location…<span id=»status»>
    <article>
    </article>
    <div class=»form-actions hidden» id=»actionBar»>
      </div> <!— end action Bar—>
  </div> <!— end searchArea —>
  </div> <!— end col 2 —>
      <?php ActiveForm::end();
 
  </div>

Вот как выглядит форма вначале:

Планировщик встреч Создать место по форме геолокации

Нажмите на кнопку « Местоположение поиска» , чтобы начать геолокацию. Снова ищите запрос разрешений в панели навигации браузера.

Как только ваше местоположение будет найдено, мы покажем ваше местоположение на карте:

Планировщик встреч Создать место по геолокации после картирования

Примечание: я установил задержку для геолокации на пять секунд, но иногда вам нужно будет перезагрузить страницу, чтобы получить правильный ответ после предоставления разрешения. Некоторые местоположения WiFi менее определенны, чем другие.

Давайте посмотрим на код отправки формы Meeting Controller:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public function actionCreate_geo()
    {
        $model = new Place();
        if ($model->load(Yii::$app->request->post())) {
            if (Yii::$app->user->getIsGuest()) {
              $model->created_by = 1;
            } else {
              $model->created_by= Yii::$app->user->getId();
            }
            $form = Yii::$app->request->post();
            $model->save();
            // add GPS entry in PlaceGeometry
            $model->addGeometryByPoint($model,$form[‘Place’][‘lat’],$form[‘Place’][‘lng’]);
            return $this->redirect([‘view’, ‘id’ => $model->id]);

На данный момент мы просто помещаем заполнитель для пользователя create_by и оставляем обработку ошибок на потом (извините, пуристы, на данный момент этот фокус не рассматривается).

Когда форма отправляет сообщения и место создается, мы извлекаем точку геолокации из формы (эти скрытые поля, заполненные сценарием Locate.js) и добавляем строку в связанную таблицу мест PlaceGPS .

Как я упоминал во второй части, мы разделяем данные геолокации в другой таблице, потому что движок MySQL InnoDB не поддерживает пространственные индексы . Это также улучшит производительность запросов для поиска ближайших мест встречи между двумя пользователями.

Вот метод addGeometryByPoint в модели Place.php:

1
2
3
4
5
6
public function addGeometryByPoint($model,$lat,$lon) {
        $pg = new PlaceGPS;
        $pg->place_id=$model->id;
        $pg->gps = new \yii\db\Expression(«GeomFromText(‘Point(«.$lat.» «.$lon.»)’)»);
        $pg->save();
    }

Вот как должна выглядеть страница индекса места после сохранения записи:

Индексная страница Планировщика встреч с новыми местами

Если вы хотите увидеть еще одну реализацию HTML5 Geolocation для Yii 1.x, ознакомьтесь с разделом Использование карт окрестностей Zillow и HTML5 Geolocation .

Если вы щелкнете по значку команды представления, связанному с нашим новым местом в представлении индекса выше, вы увидите это

Планировщик встреч Просмотреть место с помощью Google Maps

Мы настроили страницу просмотра, сгенерированную Gii, и добавили код для рисования карты Google с использованием расширения Google Maps Yii2 .

Вот действие View в PlaceController.php:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
    * Displays a single Place model.
    * @param integer $id
    * @return mixed
    */
   public function actionView($id)
   {
       $model = $this->findModel($id);
       $gps = $model->getLocation($id);
       return $this->render(‘view’, [
           ‘model’ => $model,
           ‘gps’=> $gps,
       ]);
   }

Вот метод getLocation в модели Place.php. Он выбирает координаты местоположения из таблицы PlaceGPS :

01
02
03
04
05
06
07
08
09
10
11
public function getLocation($place_id) {
     $sql = ‘Select AsText(gps) as gps from {{%place_gps}} where place_id = ‘.$place_id;
     $model = PlaceGPS::findBySql($sql)->one();
     $gps = new \stdClass;
     if (is_null($model)) {
       return false;
     } else {
       list($gps->lat, $gps->lng) = $this->string_to_lat_lon($model->gps);
     }
     return $gps;
   }

Вот часть файла представления, который отображает страницу. Левая сторона на данный момент состоит из стандартного виджета Yii2 DetailView . Правая сторона генерирует код, который рисует карту:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<div class=»col-md-6″>
<div class=»place-view»>
 
    <?= DetailView::widget([
        ‘model’ => $model,
        ‘attributes’ => [
            ‘name’,
            ‘place_type’,
            ‘website’,
            ‘full_address’,
        ],
    ]) ?>
 
</div>
</div> <!— end first col —>
<div class=»col-md-6″>
  <?
  if ($gps!==false) {
    $coord = new LatLng([‘lat’ => $gps->lat, ‘lng’ => $gps->lng]);
    $map = new Map([
        ‘center’ => $coord,
        ‘zoom’ => 14,
        ‘width’=>300,
        ‘height’=>300,
    ]);
    $marker = new Marker([
        ‘position’ => $coord,
        ‘title’ => $model->name,
    ]);
    // Add marker to the map
    $map->addOverlay($marker);
    echo $map->display();
  } else {
    echo ‘No location coordinates for this place could be found.’;
  }
  ?>
 
</div> <!— end second col —>

Функция автозаполнения Google Адресов — это невероятно быстрый и простой способ для пользователей добавлять места встреч. Я использую расширение Google Places Yii2 от 2amigOS .

Планировщик собраний Служба автозаполнения Google Place

В PlaceController.php мы добавим действие для Create_place_google :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/**
   * Creates a new Place model from Google Place
   * If creation is successful, the browser will be redirected to the ‘view’ page.
   * @return mixed
   */
  public function actionCreate_place_google()
  {
    $model = new Place();
    if ($model->load(Yii::$app->request->post())) {
       … to be explained further below…
    } else {
        return $this->render(‘create_place_google’, [
            ‘model’ => $model,
        ]);
    }
  }

Файл /frontend/views/place/create_place_google.php отобразит форму и инициализирует JavaScript, необходимый для поддержки автозаполнения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div class=»place-create»>
 
    <h1><?= Html::encode($this->title) ?></h1>
    <?= $this->render(‘_formPlaceGoogle’, [
        ‘model’ => $model,
    ]) ?>
 
</div>
 
<?
 
  $gpJsLink= ‘http://maps.googleapis.com/maps/api/js?’
                          ‘libraries’ => ‘places’,
                          ‘sensor’ => ‘false’,
                  ));
  echo $this->registerJsFile($gpJsLink);
 
  $options = ‘{«types»:[«establishment»],»componentRestrictions»:{«country»:»us»}}’;
  echo $this->registerJs(«(function(){
        var input = document.getElementById(‘place-searchbox’);
        var options = $options;
        searchbox = new google.maps.places.Autocomplete(input, options);
        setupListeners();
})();» , \yii\web\View::POS_END );
// ‘setupBounds(‘.$bound_bl.’,’.$bound_tr.’);
?>

Разработчик Петра Барус предоставила расширение Google Places для Yii1.x. Для этого урока я вручную написал базовую поддержку Yii2. Тем не менее, Barus был достаточно любезен, чтобы выпустить расширение Yii2 всего через несколько дней. Я еще не интегрировал его код. Вот его последнее расширение Yii2 Google Places Autocomplete .

Вот пакет MapAsset который я создаю для связанного JavaScript, который понадобится:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<?php
 
namespace frontend\assets;
 
use yii\web\AssetBundle;
 
class MapAsset extends AssetBundle
{
    public $basePath = ‘@webroot’;
    public $baseUrl = ‘@web’;
    public $css = [
    ];
    public $js = [
      ‘js/create_place.js’,
    ];
    public $depends = [
    ];
}

Вот _formPlaceGoogle.php формы _formPlaceGoogle.php :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
 
use yii\helpers\Html;
use yii\helpers\BaseHtml;
use yii\widgets\ActiveForm;
 
use frontend\assets\MapAsset;
MapAsset::register($this);
 
/* @var $this yii\web\View */
/* @var $model frontend\models\Place */
/* @var $form yii\widgets\ActiveForm */
?>
<div class=»col-md-6″>
 
<div class=»placegoogle-form»>
  <p>Type in a place or business known to Google Places:</p>
 
    <?php $form = ActiveForm::begin();
    <?= $form->field($model, ‘searchbox’)->textInput([‘maxlength’ => 255])->label(‘Place’) ?>
     
    <?= BaseHtml::activeHiddenInput($model, ‘name’);
    <?= BaseHtml::activeHiddenInput($model, ‘google_place_id’);
    <?= BaseHtml::activeHiddenInput($model, ‘location’);
    <?= BaseHtml::activeHiddenInput($model, ‘website’);
    <?= BaseHtml::activeHiddenInput($model, ‘vicinity’);
    <?= BaseHtml::activeHiddenInput($model, ‘full_address’);
 
    <?= $form->field($model, ‘place_type’)
            ->dropDownList(
                $model->getPlaceTypeOptions(),
                [‘prompt’=>’What type of place is this?’]
            )->label(‘Type of Place’) ?>
 
    <div class=»form-group»>
        <?= Html::submitButton($model->isNewRecord ? ‘Create’ : ‘Update’, [‘class’ => $model->isNewRecord ? ‘btn btn-success’ : ‘btn btn-primary’]) ?>
    </div>
 
    <?php ActiveForm::end();
 
</div>
</div> <!— end col1 —>
<div class=»col-md-6″>
<div id=»map-canvas»>
  <article></article>
</div>
</div> <!— end col2 —>

Есть поле searchbox которое будет принимать данные автозаполнения пользователя. Есть также множество скрытых полей, которые наш JavaScript будет загружать с результатами сервиса Google Адресов.

Вот create_place.js, который выполняет всю «магию»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function setupListeners() {
// google.maps.event.addDomListener(window, ‘load’, initialize);
    // searchbox is the var for the google places object created on the page
    google.maps.event.addListener(searchbox, ‘place_changed’, function() {
      var place = searchbox.getPlace();
      if (!place.geometry) {
        // Inform the user that a place was not found and return.
        return;
      } else {
        // migrates JSON data from Google to hidden form fields
        populateResult(place);
      }
  });
}
 
function populateResult(place) {
  // moves JSON data retrieve from Google to hidden form fields
  // so Yii2 can post the data
  $(‘#place-location’).val(JSON.stringify(place[‘geometry’][‘location’]));
  $(‘#place-google_place_id’).val(place[‘place_id’]);
  $(‘#place-full_address’).val(place[‘formatted_address’]);
  $(‘#place-website’).val(place[‘website’]);
  $(‘#place-vicinity’).val(place[‘vicinity’]);
  $(‘#place-name’).val(place[‘name’]);
  loadMap(place[‘geometry’][‘location’],place[‘name’]);
}
 
function loadMap(gps,name) {
  var mapcanvas = document.createElement(‘div’);
  mapcanvas.id = ‘mapcanvas’;
  mapcanvas.style.height = ‘300px’;
  mapcanvas.style.width = ‘300px’;
  mapcanvas.style.border = ‘1px solid black’;
     
  document.querySelector(‘article’).appendChild(mapcanvas);
   
  var latlng = new google.maps.LatLng(gps[‘k’], gps[‘D’]);
  var myOptions = {
    zoom: 16,
    center: latlng,
    mapTypeControl: false,
    navigationControlOptions: {style: google.maps.NavigationControlStyle.SMALL},
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var map = new google.maps.Map(document.getElementById(«mapcanvas»), myOptions);
   
  var marker = new google.maps.Marker({
      position: latlng,
      map: map,
      title:name
  });
}

Метод setupListeners() связывает наше поле searchbox со службой автозаполнения Google Адресов. Когда place_changed событие place_changed populateResult() чтобы заполнить скрытые поля в форме данными из Google и загрузить карту, которая отображается в правой половине формы.

Планировщик встреч Добавить из Google Place Автозаполнение после LoadMap

Вы можете использовать отладчик браузера для проверки скрытых полей после того, как они были заполнены данными формы через JavaScript. Эти данные будут опубликованы вместе с формой для отправки, чтобы мы могли добавить их в базу данных Place.

Скрытые поля в Планировщике встреч от Google Places Автозаполнение

Вот оставшийся элемент действия Сохранить Create_place_google :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public function actionCreate_place_google()
    {
      $model = new Place();
      if ($model->load(Yii::$app->request->post())) {
          if (Yii::$app->user->getIsGuest()) {
            $model->created_by = 1;
          } else {
            $model->created_by= Yii::$app->user->getId();
          }
          $form = Yii::$app->request->post();
          $model->save();
          // add GPS entry in PlaceGeometry
          $model->addGeometry($model,$form[‘Place’][‘location’]);
          return $this->redirect([‘view’, ‘id’ => $model->id]);

Это очень похоже на действие Create_geo . У нас есть отдельный метод модели Place.php для упрощения сбора данных о местоположении. Вот addGeometry() :

01
02
03
04
05
06
07
08
09
10
public function addGeometry($model,$location) {
           $x = json_decode($location,true);
           reset($x);
           $lat = current($x);
           $lon = next($x);
       $pg = new PlaceGPS;
       $pg->place_id=$model->id;
       $pg->gps = new \yii\db\Expression(«GeomFromText(‘Point(«.$lat.» «.$lon.»)’)»);
       $pg->save();
   }

Служба автозаполнения мест также позволяет вам установить географический ограничивающий прямоугольник для фильтрации поиска по нему. Когда пользователь начинает печатать, автозаполнение будет использовать только места в пределах десяти миль от них. Поскольку мы не установили текущее местоположение пользователя в качестве переменной сеанса, в данный момент я не реализую ограничивающий прямоугольник. Но мы можем сделать это позже. Вот пример setupBounds() :

1
2
3
4
5
6
function setupBounds(pt1, pt2, pt3, pt4) {
    defaultBounds = new google.maps.LatLngBounds(
    new google.maps.LatLng(pt1, pt2),
    new google.maps.LatLng(pt3, pt4));
    searchbox.setBounds(defaultBounds);
}

Третий способ, которым пользователи могут добавлять места, — это вручную указывать детали и адресную информацию Когда они отправят форму, мы попытаемся найти адрес и получить данные геолокации, но ничего страшного, если мы не сможем это найти. Ручной подход позволит пользователям добавлять такие места, как их дом или офис, которые они могут не захотеть связывать с картографическими данными Google.

Вот как выглядит форма:

Планировщик встреч вручную Добавить место

Вот как выглядит код действия отправки PlaceController.php. Мы используем клиент 2Amigos Maps Geocoding для поиска местоположения по full_address . Очевидно, что мы можем внести множество улучшений, чтобы побудить пользователя ввести полный адрес или, возможно, позднее подключить местоположение Google Мест на карте.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
$model = new Place();
       if ($model->load(Yii::$app->request->post())) {
           $form = Yii::$app->request->post();
           if (Yii::$app->user->getIsGuest()) {
             $model->created_by = 1;
           } else {
             $model->created_by= Yii::$app->user->getId();
           }
           $model->save();
           $gc = new GeocodingClient();
           $result = $gc->lookup(array(‘address’=>$form[‘Place’][‘full_address’],’components’=>1));
           $location = $result[‘results’][0][‘geometry’][‘location’];
           if (!is_null($location)) {
               $lat = $location[‘lat’];
               $lng = $location[‘lng’];
               var_dump($lat);
               var_dump($lng);
            // add GPS entry in PlaceGeometry
            $model->addGeometryByPoint($model,$lat,$lng);
           }
           return $this->redirect([‘view’, ‘id’ => $model->id]);
       }

Область применения этого урока оказалась довольно большой. Я хотел показать вам различные компоненты, связанные с геолокацией и использованием карт, не пропуская слишком много элементов процесса кодирования. Итак, очевидно, что на данный момент есть много горячих клавиш. В следующем уроке мы продолжим улучшать Places в рамках всей системы, уделяя особое внимание правам пользователей, элементам управления доступом, добавлению поддержки любимых мест пользователя и другим улучшениям.

Пожалуйста, не стесняйтесь оставлять свои вопросы и комментарии ниже. Мне особенно интересно, если у вас есть другие подходы или дополнительные идеи, или вы хотите предложить темы для будущих уроков. Вы также можете связаться со мной в Twitter @reifman или написать мне напрямую. Следуйте за моей страницей инструктора Tuts +, чтобы видеть будущие статьи в этой серии.