<style>
.example-container {
height: 100vh;
}
</style>
<div class="example-container">
<div data-react="google-map" class="google-map"></div>
</div>
<style>
.example-container {
height: 100vh;
}
</style>
<div class="example-container">
<div data-react="google-map" class="google-map"></div>
</div>
/* No context defined. */
import React, { forwardRef, useRef, useEffect, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import mapOptions from './mapOptions';
import { addZoomControl, addMarkers, createMarker } from './utils';
import { GoogleMapsContext } from '../../context/GoogleMapsProvider';
const GoogleMap = forwardRef((props, ref) => {
const mapRef = useRef();
const [ map, setMap ] = React.useState();
const [ targetMarker, setTargetMarker ] = React.useState(); // eslint-disable-line
const [ markers, setMarkers ] = React.useState();
const { googleMapsApi } = React.useContext(GoogleMapsContext);
const initGoogleMap = () => {
setMap(() => new googleMapsApi.Map(mapRef.current, mapOptions));
};
const getPlaceLatLng = (place) => {
return (new googleMapsApi.LatLng(
place.Latitude,
place.Longitude
));
};
const getVisiblePlaces = () => {
var bounds = map.getBounds();
return (
props.places.reduce((acc, place) => {
const isPlaceVisible = bounds.contains(getPlaceLatLng(place));
return ([
...acc,
...(isPlaceVisible ? [place.Id] : [])
]);
}, [])
);
};
const getTargetArea = (targetLatLng) => {
const distance = window.innerWidth > 767 ? 30000 : 15000; // Change radius between different viewports.
return googleMapsApi.geometry.spherical.computeOffset(targetLatLng, distance, 0);
};
const findClosestMarker = (targetLatLng) => {
var distances = [];
var closest = -1;
for (let i = 0; i < props.places.length; i++) {
var d = googleMapsApi.geometry.spherical.computeDistanceBetween(
targetLatLng,
getPlaceLatLng(props.places[i])
);
distances[i] = d;
if (closest === -1 || d < distances[closest]) {
closest = i;
}
}
return new googleMapsApi.LatLng(
props.places[closest].Latitude,
props.places[closest].Longitude
);
};
const updateTargetMarker = (lonLat) => {
setTargetMarker((prevMarker) => {
// remove previous marker
if (prevMarker) {
prevMarker.setMap(null);
}
return createMarker(
'search',
{
position: lonLat
},
googleMapsApi,
map
);
});
};
const openInfoWindow = (id) => {
markers.forEach(marker => {
marker.infoWindow.close(map, marker);
});
const marker = markers.find((marker) => marker.Id === id);
if (marker) {
marker.infoWindow.open(map, marker);
map.setCenter(marker.getPosition());
}
};
useImperativeHandle(ref, () => ({
openInfoWindow
}));
// Initialize map when ready
useEffect(() => {
const _mapRef = mapRef.current;
if (googleMapsApi && _mapRef) {
initGoogleMap();
}
}, [googleMapsApi, mapRef]);
// Set map focus based on a given tartget place
useEffect(() => {
// break execution if targetPlaceCords are undefined
if (!props.targetPlaceCords) {
return;
}
if (googleMapsApi && map) {
const targetLatLng = props.targetPlaceCords;
const bounds = new googleMapsApi.LatLngBounds();
bounds.extend(targetLatLng);
bounds.extend(getTargetArea(targetLatLng));
map.fitBounds(bounds);
const listener = google.maps.event.addListener(map, 'idle', () => {
map.setCenter(targetLatLng);
const isAnyPlaceVisible = getVisiblePlaces(targetLatLng);
// If map doesn't contains any markers then find closest one.
if (!isAnyPlaceVisible.length) {
bounds.extend(findClosestMarker(targetLatLng));
map.fitBounds(bounds);
}
google.maps.event.removeListener(listener);
});
updateTargetMarker(targetLatLng);
}
}, [props.targetPlaceCords, map]);
useEffect(() => {
if (map) {
addZoomControl(map);
if (props.places) {
setMarkers(() => addMarkers(map, googleMapsApi, props.places, props.onInfoWindowClose, props.onMarkerClick));
googleMapsApi.event.addListener(map, 'idle', function() {
props.onVisibleMarkersUpdate(getVisiblePlaces());
});
}
}
}, [map]);
return (
<div ref={mapRef} className="google-map__map" />
);
});
GoogleMap.displayName = 'GoogleMap';
GoogleMap.defaultProps = {
onVisibleMarkersUpdate: () => {},
onInfoWindowClose: () => {},
onMarkerClick: () => {}
};
GoogleMap.propTypes = {
targetPlaceCords: PropTypes.object,
onVisibleMarkersUpdate: PropTypes.func,
onMarkerClick: PropTypes.func
};
export default GoogleMap;
// map
.google-map {
height: 100%;
width: 100%;
}
.google-map__map {
height: 100%;
}
.google-map__info-window {
background-color: $color-white;
color: $color-black;
}
.google-map__info-window-detail {
display: flex;
line-height: size(2.25);
}
.google-map__info-window-icon {
display: inline-block;
width: size(3);
height: size(2);
}
// zoom
.google-map-zoom-control {
position: absolute;
bottom: size(2) !important;
right: size(2) !important;
width: size(6);
height: size(13);
}
.google-map-zoom-control__inner {
display: flex;
flex-direction: column;
}
.google-map-zoom-control__button {
width: size(6);
height: size(6);
border-radius: 50%;
background: no-repeat center center;
background-size: size(3);
background-color: $color-white;
&--in {
background-image: url('./img/plus.svg');
margin-bottom: size(1);
}
&--out {
background-image: url('./img/minus.svg');
}
}
// info-window
.google-map-info-window {
background-color: $color-white;
color: $color-black;
text-align: left;
font-weight: normal;
max-width: 360px;
}
.google-map-info-window__detail {
display: flex;
line-height: size(2.25);
}
.google-map-info-window__icon {
display: inline-block;
width: size(3);
height: size(2);
}
.google-map-info-window__link {
color: $color-green;
}
export default {
disableDefaultUI: true,
zoomControl: false,
zoom: 7,
mapTypeControl: false,
minZoom: 3,
scrollwheel: false,
streetViewControl: false,
maxZoom: 22,
cityZoomLevel: 17,
countryZoomLevel: 7,
center: {
lat: 57.708416,
lng: 11.981097
},
styles: [
{
elementType: 'geometry',
stylers: [{
color: '#eaeaea'
}]
},
{
elementType: 'labels.icon',
stylers: [{
'visibility': 'off'
}]
},
{
elementType: 'labels.text.fill',
stylers: [{
color: '#616161'
}]
},
{
elementType: 'labels.text.stroke',
stylers: [{
color: '#eaeaea'
}]
},
{
featureType: 'administrative.land_parcel',
elementType: 'labels.text.fill',
stylers: [{
color: '#bdbdbd'
}]
},
{
featureType: 'poi',
elementType: 'geometry',
stylers: [{
color: '#eeeeee'
}]
},
{
featureType: 'poi',
elementType: 'labels.text.fill',
stylers: [{
color: '#757575'
}]
},
{
featureType: 'poi.park',
elementType: 'geometry',
stylers: [{
color: '#e5e5e5'
}]
},
{
featureType: 'poi.park',
elementType: 'labels.text.fill',
stylers: [{
color: '#9e9e9e'
}]
},
{
featureType: 'road',
elementType: 'geometry',
stylers: [{
color: '#ffffff'
}]
},
{
featureType: 'road.arterial',
elementType: 'labels.text.fill',
stylers: [{
color: '#757575'
}]
},
{
featureType: 'road.highway',
elementType: 'geometry',
stylers: [{
color: '#dadada'
}]
},
{
featureType: 'road.highway',
elementType: 'labels.text.fill',
stylers: [{
color: '#616161'
}]
},
{
featureType: 'road.local',
elementType: 'labels.text.fill',
stylers: [{
color: '#9e9e9e'
}]
},
{
featureType: 'transit.line',
elementType: 'geometry',
stylers: [{
color: '#e5e5e5'
}]
},
{
featureType: 'transit.station',
elementType: 'geometry',
stylers: [{
color: '#eeeeee'
}]
},
{
featureType: 'water',
elementType: 'geometry',
stylers: [{
color: '#c9c9c9'
}]
},
{
featureType: 'water',
elementType: 'labels.text.fill',
stylers: [{
color: '#9e9e9e'
}]
}
]
};
import { setInfoWindowContent } from '../index';
import mapPin from './img/map-pin.svg';
import mapPinSelected from './img/map-pin-selected.svg';
const addMultipleMarkers = (dealers, map, infoWindow) => {
let markersArr = [];
const icon = {
url: mapPin,
size: new google.maps.Size(37, 52)
};
for (let dealer of dealers) {
const { title, position } = dealer;
const marker = addSingleMarker(position, title, icon, map);
markersArr.push(marker);
google.maps.event.addListener(marker, 'click', () => {
markersArr.forEach((m) => m.setIcon(mapPin));
marker.setIcon(mapPinSelected);
infoWindow.setContent(setInfoWindowContent(dealer));
infoWindow.open(map, marker);
map.setCenter(marker.getPosition());
});
}
};
const addSingleMarker = (position, title, icon, map) => {
const marker = new google.maps.Marker({
position,
title,
icon,
map
});
return marker;
};
export default {
addSingleMarker,
addMultipleMarkers
};
React component for rendering Google Maps.
Can only be used by in the context of React, where it can be fed with places/markers via props.
For example used as part of the Find Installer component.