kikwik / gmap-bundle
Google Map and Geocoder support for Symfony 5
Installs: 59
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: >=7.2.5
- ext-json: *
- doctrine/orm: ^2.7
- geocoder-php/google-maps-provider: ^4.5
- nyholm/psr7: ^1.5
- stof/doctrine-extensions-bundle: ^1.3
- symfony/form: ^5.3|^6.0
- symfony/framework-bundle: ^5.3|^6.0
- symfony/http-client: ^5.3|^6.0
- symfony/yaml: ^5.3|^6.0
- twig/extra-bundle: ^2.12|^3.0
- willdurand/geocoder-bundle: ^5.13
README
Google Map and Geocoder support for Symfony 5
Installation
Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:
$ composer require kikwik/gmap-bundle
Configuration
Add your Api keys to .env file:
GMAP_API_KEY
for server geocodingGMAP_API_KEY_JS
for javascript maps:
###> 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 ###
Autowiring
The bundle will configure an autowired provider with signature Provider $googleMapsGeocoder
that you can use as follow:
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; } }
Geocodable Entity
Make your entities (and repository) geocodable by implementing GeocodableEntityInterface
(and GeocodableRepositoryInterface
)
Use the provided traits to be quick:
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; // ... }
Then you can set address to the entity and ask to geocode her self (passing the provider):
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()); } }
Geocode Command
With the kikwik:gmap:geocode
command you can batch geocode all the entities that need to be geocoded (never geocoded or with address changed after the last geocode)
$ php bin/console kikwik:gmap:geocode --limit=5
Use the --failed
option to try to geocode again the failed ones
$ php bin/console kikwik:gmap:geocode --limit=5 --failed
Display Maps
- Call the
kw_gmap_script_tags
twig function inside the javascripts block to initialize the GMap library, eventually pass the optional nonce value - then create a new
kwMap
object - and call its
init
function that return a promise that is resolved when the map is loaded
{% 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 %}
Then place a div on the page for each map, and use the twig helpers:
{{ kw_map_data_center(-31.56391, 147.154312) }}
- set map center, parameters are a couple of float{{ kw_map_data_center(place) }}
- set map center, parameter is a GeocodableEntityInterface object{{ kw_map_data_zoom(3) }}
- set map zoom, parameter is an integer{{ kw_map_data_markers(places) }}
- load markers, parameter is an array of GeocodableEntityInterface objects{{ kw_map_data_cluster({ maxZoom: 10, minPoints: 5 }, 'darkgreen') }}
- activate cluster feature, parameters are an array of SuperCluster options, (see https://github.com/mapbox/supercluster#options) and an optional color (this activate the SingleColorRenderer){{ kw_map_data_remote_markers(asset('path/to/file.json')) }}
- load remote markers, parameter is the remote url{{ kw_map_data_search_address('#map-address','#map-address-submit', { findNearestMarker: true }) }}
- bound a search form, parameters are the css selector of the input text, the css selector of the submit button and an array of options{{ kw_map_data_street_view('#street-view',place) }}
- enable street view, parameters are the css selector of the container and a GeocodableEntityInterface object{{ kw_map_data_street_view('#street-view',41.9027835, 12.4963655) }}
- enable street view, parameters are the css selector of the container and a couple of float
Here all the data-attribute supported:
data-map-center
a json string that represent a LatLngLiteraldata-map-zoom
an integer valuedata-map-markers
a json string that represent an array of marker descriptor, each marker descriptor must have the following fieldslat
latitude value (float)lng
longitude value (float)info
the google.maps.InfoWindow content (optional)icon
the icon file (optional)identifier
a sting that identify the marker (optional)
data-map-cluster
a json string that represent the SuperCluster options (see https://github.com/mapbox/supercluster#options)data-map-cluster-color
a color string for the cluster's SingleColorRendererdata-map-remote-markers
an url from which load markers in json formatdata-map-search-address
the css selector of the input text used to center the mapdata-map-search-submit
the css selector of the submit button used to center the mapdata-map-search-find-nearest-marker
set to "1" for a zoom out after a successful search, until a marker is visible in the mapdata-map-street-view
the css selector of the element that will contain the street viewdata-map-street-view-position
a json string that represent a LatLngLiteral
some examples:
<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>
Address Autocomplete
Use the AddressAutocompleteType
in your forms to geocode adresses in separate components
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, ]); } }
remember to load gmap scripts in your template:
{% block javascripts %} {{ parent() }} {{ kw_gmap_script_tags() }} {% endblock %}