radutopala/google-map
Laravel package for integrating Google Maps into your application, with configuration and Blade helpers for rendering maps and markers. Lightweight and easy to wire into existing views. Note: it has no stars and a low health score, so check maintenance status before using in production.
radutopala/google-map is a framework-agnostic PHP library (a fork of egeloen/ivory-google-map) that builds the HTML + JavaScript needed to render a Google Maps JS API v3 map. There is no Laravel service provider, no facade, and no config file — you get PHP objects that serialize to JS, and you wire them into Blade yourself.
Install and confirm the tag supports your PHP version (the package description still says PHP 5.3+, which is the original library's floor; the fork's composer.json is the source of truth):
composer require radutopala/google-map
Where to look first, in this order:
Ivory\GoogleMap\Map — the root object (container id, center, zoom, overlays, events, controls).Ivory\GoogleMap\Helper\Builder\MapHelperBuilder / ApiHelperBuilder — the renderers.Ivory\GoogleMap\Overlay\* — Marker, InfoWindow, Polyline, Polygon, Circle, EncodedPolyline.Ivory\GoogleMap\Service\* — server-side REST wrappers (Geocoder, Directions, DistanceMatrix, Elevation, Place, TimeZone).First use case — a map with one marker, rendered in a controller:
use Ivory\GoogleMap\Map;
use Ivory\GoogleMap\Base\Coordinate;
use Ivory\GoogleMap\Overlay\Marker;
use Ivory\GoogleMap\Overlay\InfoWindow;
use Ivory\GoogleMap\Helper\Builder\MapHelperBuilder;
use Ivory\GoogleMap\Helper\Builder\ApiHelperBuilder;
$map = new Map('office-map'); // first arg = DOM element id
$map->setCenter(new Coordinate(48.8566, 2.3522));
$map->setZoom(12);
$marker = new Marker(new Coordinate(48.8566, 2.3522));
$marker->setInfoWindow(
(new InfoWindow())->setContent('<strong>HQ</strong>')
);
$map->getOverlayManager()->addMarker($marker);
$mapHelper = MapHelperBuilder::create()->build();
$apiHelper = ApiHelperBuilder::create()
->setKey(config('services.google_maps.key'))
->build();
return view('map', [
'api' => $apiHelper->render([$mapHelper]), // <script src=...> + init callback
'map' => $mapHelper->render($map), // <div> + inline <script>
]);
{{-- resources/views/map.blade.php --}}
{!! $api !!}
{!! $map !!}
That is the whole loop: build a Map, hand it to a helper, echo the strings. If you internalise nothing else, internalise renderHtml() / renderJs() (separate pieces, useful when you want the <div> in Blade and the JS at the bottom of the body).
Wrap construction in a service, not a controller. Maps get built from Eloquent collections constantly; give yourself one place that knows how to translate models into overlays.
// app/Services/MapFactory.php
class MapFactory
{
public function forLocations(Collection $locations, string $id = 'map'): Map
{
$map = new Map($id);
$map->setCenter(new Coordinate(
$locations->avg('lat') ?: 48.8566,
$locations->avg('lng') ?: 2.3522,
));
$map->setAutoZoom(true); // fits bounds to overlays, overrides zoom
foreach ($locations as $loc) {
$marker = new Marker(new Coordinate($loc->lat, $loc->lng));
$marker->setInfoWindow(
(new InfoWindow())->setContent(view('partials.map-bubble', compact('loc'))->render())
);
$map->getOverlayManager()->addMarker($marker);
}
return $map;
}
}
Bind the helper once. MapHelperBuilder::create()->build() is cheap but not free, and you'll want consistent options (formatter, JSON builder) everywhere — register it as a singleton in AppServiceProvider.
Blade component for the two-part output. A <x-google-map :map="$map" :id="'office-map'" /> component that emits {!! $apiHelper->render([$mapHelper]) !!}{!! $mapHelper->render($map) !!} keeps views clean and gives you one place to handle the "JS API already loaded" problem (see Gotchas).
Server-side geocoding, cached. The Service namespace saves you from writing Guzzle calls, but hit it through Cache::remember keyed on the normalised address — Google bills per request and rate-limits aggressively:
use Ivory\GoogleMap\Service\Geocoder\GeocoderService;
$service = new GeocoderService($httpAdapter, config('services.google_maps.key'));
$coordinate = Cache::remember("geo:{$hash}", now()->addMonth(), function () use ($service, $address) {
$result = $service->geocode($address)->getResults()[0] ?? null;
return $result ? $result->getGeometry()->getLocation() : null; // Coordinate
});
Directions / DistanceMatrix are the same shape: build a request object, call one method, walk a response object. Great for delivery-radius or "distance to nearest branch" features.
Dynamic overlays via event listeners. Events are raw JS strings injected verbatim — that's your extension point for anything the library doesn't model (drawing tools, click-to-add-marker, custom clustering):
$map->getEventManager()->addEvent('click', sprintf(
'%s.addMarker(new google.maps.Marker({position: e.latLng, map: %s}));',
$map->getVariable(),
$map->getVariable()
));
Use $map->getVariable() (never a hardcoded map) so multiple maps on one page don't collide.
AJAX / Livewire partial reloads. render() produces a <div> + an IIFE that runs immediately. On SPA-style updates, either render only renderJs() into a dispatched browser event, or make the component re-mount the container element so the inline script re-executes.
Testing. Output is a deterministic string — assert on it rather than on a browser:
$js = MapHelperBuilder::create()->build()->renderJs($map);
$this->assertStringContainsString('new google.maps.Marker', $js);
$this->assertStringContainsString('"lat":48.8566', $js);
services.google_maps.key from GOOGLE_MAPS_KEY and never commit it.$apiHelper->render([$mapHelper1, $mapHelper2, ...]) accepts an array for a reason — pass every helper and it emits one <script> tag plus a callback that initialises them all. Two separate render() calls = two <script src> tags = "You have included the Google Maps JavaScript API multiple times" and possibly a blank map.Map constructor's first argument is the DOM id and seeds the JS variable name. Building the same map object twice, or reusing 'map' in two components, produces duplicate id attributes and only one map works.new Coordinate($lat, $lng) everywhere. Easy to get backwards when your DB columns are longitude/latitude; write one accessor and use it.setAutoZoom(true) silently beats setZoom(). If your zoom is being ignored, that's why. Same for setCenter() — if you provide overlays, the center becomes a fallback only.InfoWindow::setContent() are injected raw into a JS string. Never pass user input directly. Run it through json_encode($html) or render a Blade partial and json_encode the result — otherwise a quote in a product name breaks the whole map script.composer why-not php 8.3, and expect to patch return types or array-offset warnings yourself. For a new project, weigh googlemaps/google-maps-services-php (server-side only) or just calling the JS API directly from Alpine/Vue.script-src https://maps.googleapis.com 'unsafe-inline' (the library emits inline scripts) and img-src https://maps.gstatic.com data:.MarkerClusterer, heatmaps with weight, or advanced markers you'll drop to raw JS via getEventManager() or emit your own <script> alongside the helper output.Ivory\HttpAdapter\HttpAdapterInterface or a PSR-18 client — check composer.json before assuming Guzzle. Inject it; don't new it inside a controller.$mapHelper->renderJs($map) into a <pre> and paste it into the browser console. Errors like InvalidValueError: setCenter: not a LatLng are almost always a missing setCenter(), a null coordinate from a geocode that returned zero results, or a coordinate built from strings.How can I help you explore Laravel packages today?