Documentation

Google Maps Integration

Google Maps Integration

UltraViolet Pro includes Google Maps integration for creating interactive maps with markers, custom styling, and advanced features.

Overview

Google Maps provides powerful mapping capabilities with:

  • Interactive Maps - Pan, zoom, and explore locations
  • Custom Markers - Add custom markers with info windows
  • Custom Styling - Apply custom themes and colors
  • Directions - Show routes and directions
  • Places API - Search and display places
  • Street View - Integrate Street View imagery
  • Responsive Design - Works on all devices

Setup

API Key Configuration

First, obtain a Google Maps API key from the Google Cloud Console.

Add to your .env file:

GOOGLE_MAPS_API_KEY=your_api_key_here

Include Google Maps Script

<!-- Include Google Maps JavaScript API -->
<script async defer 
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>

Basic Map Implementation

HTML Structure

<div class="card">
    <div class="card-header">
        <h5 class="card-title mb-0">Google Maps</h5>
    </div>
    <div class="card-body">
        <div id="map" style="height: 500px; width: 100%;"></div>
    </div>
</div>

JavaScript Initialization

function initMap() {
    // Map options
    const mapOptions = {
        zoom: 10,
        center: { lat: 40.7128, lng: -74.0060 }, // New York City
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        styles: [
            {
                featureType: 'all',
                elementType: 'geometry.fill',
                stylers: [{ color: '#f5f5f5' }]
            },
            {
                featureType: 'water',
                elementType: 'geometry.fill',
                stylers: [{ color: '#c9c9c9' }]
            }
        ]
    };

    // Create map
    const map = new google.maps.Map(document.getElementById('map'), mapOptions);

    // Add marker
    const marker = new google.maps.Marker({
        position: { lat: 40.7128, lng: -74.0060 },
        map: map,
        title: 'New York City'
    });

    // Add info window
    const infoWindow = new google.maps.InfoWindow({
        content: '<h6>New York City</h6><p>The Big Apple</p>'
    });

    marker.addListener('click', function() {
        infoWindow.open(map, marker);
    });
}

Custom Markers

Basic Markers

// Create custom marker
const marker = new google.maps.Marker({
    position: { lat: 40.7128, lng: -74.0060 },
    map: map,
    title: 'Custom Marker',
    icon: {
        url: '/images/marker-icon.png',
        scaledSize: new google.maps.Size(32, 32)
    }
});

Multiple Markers

const locations = [
    { lat: 40.7128, lng: -74.0060, title: 'New York' },
    { lat: 34.0522, lng: -118.2437, title: 'Los Angeles' },
    { lat: 41.8781, lng: -87.6298, title: 'Chicago' },
    { lat: 29.7604, lng: -95.3698, title: 'Houston' }
];

locations.forEach(location => {
    const marker = new google.maps.Marker({
        position: { lat: location.lat, lng: location.lng },
        map: map,
        title: location.title
    });

    const infoWindow = new google.maps.InfoWindow({
        content: `<h6>${location.title}</h6>`
    });

    marker.addListener('click', function() {
        infoWindow.open(map, marker);
    });
});

Marker Clustering

// Include MarkerClusterer library
// <script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>

const markers = locations.map(location => {
    return new google.maps.Marker({
        position: { lat: location.lat, lng: location.lng },
        title: location.title
    });
});

const markerCluster = new MarkerClusterer(map, markers, {
    imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'
});

Custom Styling

Dark Theme

const darkTheme = [
    { elementType: 'geometry', stylers: [{ color: '#242f3e' }] },
    { elementType: 'labels.text.stroke', stylers: [{ color: '#242f3e' }] },
    { elementType: 'labels.text.fill', stylers: [{ color: '#746855' }] },
    {
        featureType: 'administrative.locality',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#d59563' }]
    },
    {
        featureType: 'poi',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#d59563' }]
    },
    {
        featureType: 'poi.park',
        elementType: 'geometry',
        stylers: [{ color: '#263c3f' }]
    },
    {
        featureType: 'poi.park',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#6b9a76' }]
    },
    {
        featureType: 'road',
        elementType: 'geometry',
        stylers: [{ color: '#38414e' }]
    },
    {
        featureType: 'road',
        elementType: 'geometry.stroke',
        stylers: [{ color: '#212a37' }]
    },
    {
        featureType: 'road',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#9ca5b3' }]
    },
    {
        featureType: 'road.highway',
        elementType: 'geometry',
        stylers: [{ color: '#746855' }]
    },
    {
        featureType: 'road.highway',
        elementType: 'geometry.stroke',
        stylers: [{ color: '#1f2835' }]
    },
    {
        featureType: 'road.highway',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#f3d19c' }]
    },
    {
        featureType: 'transit',
        elementType: 'geometry',
        stylers: [{ color: '#2f3948' }]
    },
    {
        featureType: 'transit.station',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#d59563' }]
    },
    {
        featureType: 'water',
        elementType: 'geometry',
        stylers: [{ color: '#17263c' }]
    },
    {
        featureType: 'water',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#515c6d' }]
    },
    {
        featureType: 'water',
        elementType: 'labels.text.stroke',
        stylers: [{ color: '#17263c' }]
    }
];

const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 10,
    center: { lat: 40.7128, lng: -74.0060 },
    styles: darkTheme
});

Custom Colors

const customStyle = [
    {
        featureType: 'all',
        elementType: 'labels.text.fill',
        stylers: [{ color: '#ffffff' }]
    },
    {
        featureType: 'all',
        elementType: 'labels.text.stroke',
        stylers: [{ color: '#000000' }, { weight: 2 }]
    },
    {
        featureType: 'water',
        elementType: 'geometry.fill',
        stylers: [{ color: '#0066cc' }]
    },
    {
        featureType: 'landscape',
        elementType: 'geometry.fill',
        stylers: [{ color: '#00cc66' }]
    }
];

Directions and Routes

Basic Directions

function initMap() {
    const map = new google.maps.Map(document.getElementById('map'), {
        zoom: 7,
        center: { lat: 41.85, lng: -87.65 }
    });

    const directionsService = new google.maps.DirectionsService();
    const directionsRenderer = new google.maps.DirectionsRenderer();
    directionsRenderer.setMap(map);

    const start = { lat: 40.7128, lng: -74.0060 }; // New York
    const end = { lat: 34.0522, lng: -118.2437 }; // Los Angeles

    directionsService.route({
        origin: start,
        destination: end,
        travelMode: google.maps.TravelMode.DRIVING
    }, (result, status) => {
        if (status === 'OK') {
            directionsRenderer.setDirections(result);
        }
    });
}

Directions with Waypoints

const waypoints = [
    { location: { lat: 39.9526, lng: -75.1652 }, stopover: true }, // Philadelphia
    { location: { lat: 38.9072, lng: -77.0369 }, stopover: true }  // Washington DC
];

directionsService.route({
    origin: start,
    destination: end,
    waypoints: waypoints,
    travelMode: google.maps.TravelMode.DRIVING,
    optimizeWaypoints: true
}, (result, status) => {
    if (status === 'OK') {
        directionsRenderer.setDirections(result);
    }
});

Places Integration

Places Search

function initMap() {
    const map = new google.maps.Map(document.getElementById('map'), {
        center: { lat: 40.7128, lng: -74.0060 },
        zoom: 13
    });

    const service = new google.maps.places.PlacesService(map);

    const request = {
        query: 'restaurants in New York',
        fields: ['name', 'geometry', 'formatted_address'],
        locationBias: map.getCenter()
    };

    service.textSearch(request, (results, status) => {
        if (status === google.maps.places.PlacesServiceStatus.OK) {
            results.forEach(place => {
                const marker = new google.maps.Marker({
                    position: place.geometry.location,
                    map: map,
                    title: place.name
                });

                const infoWindow = new google.maps.InfoWindow({
                    content: `
                        <div>
                            <h6>${place.name}</h6>
                            <p>${place.formatted_address}</p>
                        </div>
                    `
                });

                marker.addListener('click', () => {
                    infoWindow.open(map, marker);
                });
            });
        }
    });
}

Autocomplete Search

<!-- Include Places library -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap"></script>

<input id="pac-input" class="form-control" type="text" placeholder="Search for places...">
<div id="map" style="height: 500px; width: 100%;"></div>
function initMap() {
    const map = new google.maps.Map(document.getElementById('map'), {
        center: { lat: 40.7128, lng: -74.0060 },
        zoom: 13
    });

    const input = document.getElementById('pac-input');
    const searchBox = new google.maps.places.SearchBox(input);

    map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

    searchBox.addListener('places_changed', () => {
        const places = searchBox.getPlaces();

        if (places.length === 0) return;

        const bounds = new google.maps.LatLngBounds();

        places.forEach(place => {
            if (!place.geometry || !place.geometry.location) return;

            const marker = new google.maps.Marker({
                map: map,
                title: place.name,
                position: place.geometry.location
            });

            bounds.extend(place.geometry.location);
        });

        map.fitBounds(bounds);
    });
}

Street View Integration

Basic Street View

function initMap() {
    const map = new google.maps.Map(document.getElementById('map'), {
        center: { lat: 40.7128, lng: -74.0060 },
        zoom: 12
    });

    const streetViewService = new google.maps.StreetViewService();
    const streetViewRenderer = new google.maps.StreetViewPanorama(document.getElementById('street-view'));

    map.addListener('click', (event) => {
        streetViewService.getPanorama({
            location: event.latLng,
            radius: 50
        }, (data, status) => {
            if (status === 'OK') {
                streetViewRenderer.setPano(data.location.pano);
                streetViewRenderer.setPov({
                    heading: 270,
                    pitch: 0
                });
                streetViewRenderer.setVisible(true);
            }
        });
    });
}

Integration with Laravel

Controller Method

class MapController extends Controller
{
    public function index()
    {
        $locations = Location::all();

        return view('admin.maps.google', compact('locations'));
    }

    public function getLocations()
    {
        $locations = Location::select('id', 'name', 'latitude', 'longitude', 'description')
            ->get()
            ->map(function($location) {
                return [
                    'id' => $location->id,
                    'name' => $location->name,
                    'lat' => $location->latitude,
                    'lng' => $location->longitude,
                    'description' => $location->description
                ];
            });

        return response()->json($locations);
    }

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'latitude' => 'required|numeric',
            'longitude' => 'required|numeric',
            'description' => 'nullable|string'
        ]);

        $location = Location::create($request->all());

        return response()->json($location);
    }
}

Frontend Integration

async function loadLocations() {
    try {
        const response = await fetch('/api/locations');
        const locations = await response.json();

        locations.forEach(location => {
            const marker = new google.maps.Marker({
                position: { lat: location.lat, lng: location.lng },
                map: map,
                title: location.name
            });

            const infoWindow = new google.maps.InfoWindow({
                content: `
                    <div>
                        <h6>${location.name}</h6>
                        <p>${location.description || ''}</p>
                        <button onclick="editLocation(${location.id})" class="btn btn-sm btn-primary">Edit</button>
                    </div>
                `
            });

            marker.addListener('click', () => {
                infoWindow.open(map, marker);
            });
        });
    } catch (error) {
        console.error('Error loading locations:', error);
    }
}

Responsive Design

CSS for Responsive Maps

.map-container {
    position: relative;
    width: 100%;
    height: 500px;
    overflow: hidden;
    border-radius: 0.5rem;
}

@media (max-width: 768px) {
    .map-container {
        height: 300px;
    }
}

@media (max-width: 576px) {
    .map-container {
        height: 250px;
    }
}

Dynamic Resizing

function initMap() {
    const map = new google.maps.Map(document.getElementById('map'), {
        center: { lat: 40.7128, lng: -74.0060 },
        zoom: 10
    });

    // Resize map when window size changes
    window.addEventListener('resize', () => {
        google.maps.event.trigger(map, 'resize');
    });
}

Best Practices

  1. API Key Security: Never expose API keys in client-side code
  2. Rate Limiting: Implement proper rate limiting for API calls
  3. Error Handling: Handle API errors gracefully
  4. Performance: Use marker clustering for large datasets
  5. Accessibility: Provide alternative text and keyboard navigation
  6. Mobile Optimization: Ensure maps work well on touch devices
  7. Caching: Cache map data when possible

Troubleshooting

Common Issues

  1. Map Not Loading: Check API key and billing status
  2. Markers Not Showing: Verify coordinates are valid
  3. Styling Issues: Ensure custom styles are properly formatted
  4. Performance: Use appropriate zoom levels and marker limits

Debug Mode

// Enable debug mode for development
const map = new google.maps.Map(document.getElementById('map'), {
    center: { lat: 40.7128, lng: -74.0060 },
    zoom: 10,
    gestureHandling: 'greedy',
    disableDefaultUI: false
});

Examples in UltraViolet

Google Maps is used in several places throughout UltraViolet:

  • Maps Overview: Basic map demonstrations
  • Google Maps: Advanced examples with custom styling
  • Dashboard: Interactive maps with real-time data
  • Location Services: Place search and directions

For more examples, check the resources/views/admin/maps/ directory.