小快/地图包

为 Symfony 5 提供谷歌地图和地理编码支持

安装: 57

依赖: 0

建议者: 0

安全: 0

星星: 0

关注者: 1

分支: 0

开放问题: 0

类型:symfony-bundle

v1.0.23 2024-01-31 09:42 UTC

README

为 Symfony 5 提供谷歌地图和地理编码支持

安装

打开命令行,进入您的项目目录并执行以下命令以下载此包的最新稳定版本

$ composer require kikwik/gmap-bundle

配置

将您的 API 密钥添加到 .env 文件中

  • GMAP_API_KEY 用于服务器地理编码
  • GMAP_API_KEY_JS 用于 JavaScript 地图
###> geocoder ###
# https://console.cloud.google.com/apis/credentials?hl=it&project=my-project
# credential: "My server api key"
# allowed IP address: xxx.xxx.xxx.xxx | yyy.yyy.yyy.yyy
GMAP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# https://console.cloud.google.com/apis/credentials?hl=it&project=my-project
# credential: "My javascript api key"
# allowed domains: https://*.my-domain.ltd/*  |  https://my-domain.ltd/*
GMAP_API_KEY_JS=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
###< geocoder ###

自动装配

该包将配置一个具有签名 Provider $googleMapsGeocoder 的自动装配提供者,您可以使用如下方式使用它

namespace App\Service;

use Geocoder\Provider\Provider;
use Geocoder\Provider\GoogleMaps\Model\GoogleAddress;

class MyService
{
    private $googleMapsGeocoder;

    public function __construct(Provider $googleMapsGeocoder)
    {
        $this->googleMapsGeocoder = $googleMapsGeocoder;
    }

    public function doGeocode(string $address)
    {
        // create the GeocodeQuery with the address to geocode
        $geocodeQuery = GeocodeQuery::create('piazza Duomo 1, Milano')
            ->withLocale('it');
            
        // ask geocode to the $googleMapsGeocoder provider,
        // the result is an array of GoogleAddress objects   
        /** @var GoogleAddress $geocodeResults[] */
        $geocodeResults = $googleMapsGeocoder->geocodeQuery($geocodeQuery);
        
        // first result should be the best match
        return $geocodeResults[0] ?? null;
    }
}

可地理编码实体

使您的实体(和仓库)可地理编码,通过实现 GeocodableEntityInterface(和 GeocodableRepositoryInterface

使用提供的特性来快速实现

namespace App\Entity;

use Kikwik\GmapBundle\Geocodable\GeocodableEntityInterface;
use Kikwik\GmapBundle\Geocodable\GeocodableEntityTrait;

class Place implements GeocodableEntityInterface
{
    use GeocodableEntityTrait;
    
    // ...
}
namespace App\Repository;

use Kikwik\GmapBundle\Geocodable\GeocodableRepositoryInterface;
use Kikwik\GmapBundle\Geocodable\GeocodableRepositoryTrait;

class PlaceRepository extends ServiceEntityRepository implements GeocodableRepositoryInterface
{
    use GeocodableRepositoryTrait;
    
    // ...
}

然后您可以将地址设置为实体,并请求对其进行地理编码(传递提供者)

namespace App\Controller;

use Geocoder\Provider\Provider;

class GmapController extends AbstractController
{
    /**
     * @Route("/gmap/new", name="app_gmap_new")
     */
    public function createNewPlace(Provider $googleMapsGeocoder, EntityManagerInterface $entityManager)
    {
        // create an object that implement GeocodableEntityInterface
        /** @var Kikwik\GmapBundle\Geocodable\GeocodableEntityInterface $place */
        $place = new Place();

        // fill the address fields
        $place->setStreet('Piazza Duomo');
        $place->setStreetNumber('1');
        $place->setZipCode('20100');
        $place->setCity('Milano');
        $place->setProvince('MI');
        $place->setCountry('Italia');

        // Ask geocode by passing the provider
        $place->doGeocode($googleMapsGeocoder);

        $entityManager->persist($place);
        $entityManager->flush();

        return $this->redirect($place->getGmapsUrl());
    }
}

地理编码命令

使用 kikwik:gmap:geocode 命令,您可以批量地理编码所有需要地理编码的实体(从未地理编码或地址在最后一次地理编码后更改的实体)

$ php bin/console kikwik:gmap:geocode --limit=5

使用 --failed 选项尝试重新地理编码失败的实体

$ php bin/console kikwik:gmap:geocode --limit=5 --failed

显示地图

  • 在 JavaScript 块中调用 kw_gmap_script_tags twig 函数以初始化 GMap 库,最终传递可选的 nonce 值
  • 然后创建一个新的 kwMap 对象
  • 并调用其 init 函数,它返回一个解析为地图加载完成的 promise
{% block javascripts %}
    {{ parent() }}
 
    {{ kw_gmap_script_tags(csp_nonce('script')) }}
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            let mapElements = document.querySelectorAll('.kw-map');
            mapElements.forEach(function (mapElement){
                let map = new kwMap();
                map.init(mapElement)
                        .then(function (){

                            map.getGMap().addListener('bounds_changed', function() {
                                const searchText = document.getElementById('map-search-txt');
                                const searchResults = document.getElementById('search-results');
                                const searchResultList = searchResults.querySelector('.js-location-list');
                                const searchResultCount = searchResults.querySelector('.js-location-count');
                                if(searchText.value)
                                {
                                    // get visible markers
                                    let visibleMarkers = map.getVisibleMarkers();
                                    // update counter
                                    searchResultCount.textContent = '('+visibleMarkers.length+')';
                                    // empty result list
                                    searchResultList.innerHTML = '';
                                    // add results to list
                                    for(let visibleMarker of visibleMarkers)
                                    {
                                        let node = document.createElement('li');
                                        node.id = 'result-'+visibleMarker.id;
                                        node.innerHTML = visibleMarker.info;
                                        searchResultList.appendChild(node);
                                    }
                                    // show results
                                    searchResults.classList.remove('d-none');
                                }
                                else
                                {
                                    // hide results
                                    searchResults.classList.add('d-none');
                                }
                            });


                            const mapSourceRadios = document.querySelectorAll('.js-map-source');
                            mapSourceRadios.forEach(function (mapSourceRadio){
                                mapSourceRadio.addEventListener('click', function() {
                                    map.clearMarkers();
                                    let url = this.value;
                                    mapElement.dataset.mapRemoteMarkers = url;
                                    map.loadMarkers();
                                })
                            })
                        })
            });
        });
    </script>
{% endblock %}

然后在页面上为每个地图放置一个 div,并使用 twig 辅助函数

  • {{ kw_map_data_center(-31.56391, 147.154312) }} - 设置地图中心,参数是一对浮点数
  • {{ kw_map_data_center(place) }} - 设置地图中心,参数是一个 GeocodableEntityInterface 对象
  • {{ kw_map_data_zoom(3) }} - 设置地图缩放,参数是一个整数
  • {{ kw_map_data_markers(places) }} - 加载标记,参数是一个 GeocodableEntityInterface 对象数组
  • {{ kw_map_data_cluster({ maxZoom: 10, minPoints: 5 }, 'darkgreen') }} - 启用聚合功能,参数是一个 SuperCluster 选项数组,(见 https://github.com/mapbox/supercluster#options) 以及一个可选的颜色(这会激活 SingleColorRenderer)
  • {{ kw_map_data_remote_markers(asset('path/to/file.json')) }} - 加载远程标记,参数是远程 URL
  • {{ kw_map_data_search_address('#map-address','#map-address-submit', { findNearestMarker: true }) }} - 绑定搜索表单,参数是输入文本的 CSS 选择器,提交按钮的 CSS 选择器以及一个选项数组
  • {{ kw_map_data_street_view('#street-view',place) }} - 启用街景,参数是容器的 CSS 选择器和一个 GeocodableEntityInterface 对象
  • {{ kw_map_data_street_view('#street-view',41.9027835, 12.4963655) }} - 启用街景,参数是容器的 CSS 选择器和一对浮点数

这里列出了所有支持的数据属性

  • data-map-center 一个表示 LatLngLiteral 的 JSON 字符串
  • data-map-zoom 一个整数值
  • data-map-markers 一个表示标记描述符数组的 JSON 字符串,每个标记描述符都必须有以下字段
    • lat 纬度值(浮点数)
    • lng 经度值(浮点数)
    • info Google.maps.InfoWindow 内容(可选)
    • icon 图标文件(可选)
    • identifier 识别标记的字符串(可选)
  • data-map-cluster 表示 SuperCluster 选项的 JSON 字符串(参见 https://github.com/mapbox/supercluster#options
  • data-map-cluster-color 表示集群的 SingleColorRenderer 颜色字符串
  • data-map-remote-markers 从该 URL 加载 JSON 格式的标记
  • data-map-search-address 用于定位地图的中心点的输入文本的 CSS 选择器
  • data-map-search-submit 用于定位地图的提交按钮的 CSS 选择器
  • data-map-search-find-nearest-marker 在成功搜索后设置为 "1",直到地图上出现标记时进行缩放
  • data-map-street-view 将包含街景的元素的 CSS 选择器
  • data-map-street-view-position 表示一个 LatLngLiteral 的 JSON 字符串

一些示例

<form>
    <input type="text" id="map-address" placeholder="Ricerca per città, indirizzo, CAP...">
    <button type="submit" id="map-address-submit">Cerca ›</button>
</form>

Map with clustered external data and search box:
<div class="ratio ratio-1x1">
    <div class="kw-map"
            {{ kw_map_data_remote_markers(asset('agenzie.json')) }}
            {{ kw_map_data_cluster({ maxZoom: 10, minPoints: 10 }) }}
            {{ kw_map_data_search_address('#map-address','#map-address-submit', { findNearest: true }) }}
    ></div>
</div>
            
Empty map centered in australia:
<div class="ratio ratio-1x1">
    <div class="kw-map"
            {{ kw_map_data_center(-31.56391, 147.154312) }}
    ></div>
</div>

Empty map centered in place (an GeocodableEntityInterface object):
<div class="ratio ratio-1x1">
    <div class="kw-map" 
            {{ kw_map_data_center(place) }}
    ></div>
</div>

Map with marker in all places (an array of GeocodableEntityInterface object):
<div class="ratio ratio-1x1">
    <div class="kw-map" {{ kw_map_data_markers(places) }}></div>
</div>

Map with zoom=3, marker in all places (an array of GeocodableEntityInterface object) centered in the first one, street view on the third one
<div class="ratio ratio-1x1">
    <div class="kw-map"
            {{ kw_map_data_center(places[0]) }}
            {{ kw_map_data_zoom(3) }}
            {{ kw_map_data_markers(places) }}
            {{ kw_map_data_search_address('#map-address','#map-address-submit') }}
            {{ kw_map_data_street_view('#street-view',places[2]) }}
    ></div>
</div>

<div class="ratio ratio-21x9">
    <div id="street-view"></div>
</div>

地址自动完成

在您的表单中使用 AddressAutocompleteType 来在单独的组件中地理编码地址

use Kikwik\GmapBundle\Form\Type\AddressAutocompleteType;

class GmapController extends AbstractController
{
    /**
     * @Route("/gmap/autocomplete", name="app_gmap_autocomplete")
     */
    public function addressAutocomplete(Request $request)
    {
        $submittedData = null;
        $form = $this->createFormBuilder()
            ->add('indirizzo1',AddressAutocompleteType::class, [
            ])
            ->add('indirizzo2',AddressAutocompleteType::class, [
                'autocomplete_fields' => ['latitude','longitude']
            ])
            ->getForm();
        $form->handleRequest($request);
        if($form->isSubmitted() && $form->isValid())
        {
            $submittedData = $form->getData();
            dump($submittedData['indirizzo1']['autocomplete']);
            dump($submittedData['indirizzo1']['street']);
            dump($submittedData['indirizzo1']['streetNumber']);
            dump($submittedData['indirizzo1']['zipCode']);
            dump($submittedData['indirizzo1']['locality']);
            dump($submittedData['indirizzo1']['city']);
            dump($submittedData['indirizzo1']['province']);
            dump($submittedData['indirizzo1']['region']);
            dump($submittedData['indirizzo1']['country']);
            dump($submittedData['indirizzo1']['latitude']);
            dump($submittedData['indirizzo1']['longitude']);
        }


        return $this->render('gmap/addressAutocomplete.html.twig',[
            'form'=>$form->createView(),
            'submittedData' => $submittedData,
        ]);
    }
}

请记住在您的模板中加载 gmap 脚本

{% block javascripts %}
    {{ parent() }}
 
    {{ kw_gmap_script_tags() }}
    
{% endblock %}