kikwik / gmap-bundle
Google Map and Geocoder support for Symfony 5
Installs: 61
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
Type:symfony-bundle
pkg:composer/kikwik/gmap-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_KEYfor server geocodingGMAP_API_KEY_JSfor 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_tagstwig function inside the javascripts block to initialize the GMap library, eventually pass the optional nonce value - then create a new
kwMapobject - and call its
initfunction 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-centera json string that represent a LatLngLiteraldata-map-zooman integer valuedata-map-markersa json string that represent an array of marker descriptor, each marker descriptor must have the following fieldslatlatitude value (float)lnglongitude value (float)infothe google.maps.InfoWindow content (optional)iconthe icon file (optional)identifiera sting that identify the marker (optional)
data-map-clustera json string that represent the SuperCluster options (see https://github.com/mapbox/supercluster#options)data-map-cluster-colora color string for the cluster's SingleColorRendererdata-map-remote-markersan url from which load markers in json formatdata-map-search-addressthe css selector of the input text used to center the mapdata-map-search-submitthe css selector of the submit button used to center the mapdata-map-search-find-nearest-markerset to "1" for a zoom out after a successful search, until a marker is visible in the mapdata-map-street-viewthe css selector of the element that will contain the street viewdata-map-street-view-positiona 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 %}