Local Context Map Google React JS - javascript

I'm currently trying to implement the beta version of Local Context Map from Google in my React JS app. It was working then but at some point, it just didn't. I'm not sure if it's because of the new Chrome update, since I've never checked it with other browsers before. Now, it's not displaying the map. I think it's got something to do with the lifecycle of calling the script and rendering the view. I'm really clueless now I don't know where to look anymore.
The error I'm getting is LocalContextMapView is not defined
This is the link for the documentation here
This is how I implemented it
import PropTypes from 'prop-types'
import { defaultCenter, places, mapStyles } from '#utils/config'
import { GOOGLE_MAPS_API_KEY } from '#helpers/config'
const LocalContext = ({ label, location }) => {
let map
const onScriptload = () => {
let s = document.createElement('script')
s.src = `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=localContext&v=beta`
document.body.appendChild(s)
s.addEventListener('load', function () {
initMap()
})
}
function initMap() {
const localContextMapView = new google.maps.localContext.LocalContextMapView({
element: document.getElementById('map'),
placeTypePreferences: places,
maxPlaceCount: 24,
})
map = localContextMapView.map
new google.maps.Marker({ position: location, map: map })
map.setOptions({
center: location,
zoom: 15,
clickableIcons: true,
styles: mapStyles,
})
window.map = map
}
useEffect(() => {
onScriptload()
}, [])
return (
<div className="overflow-hidden">
{label && <p className="py-5 text-3xl font-bold">{label}</p>}
<div id="map" className="relative w-full px-10 md:px-0 h-50vh" style={{ boxSizing: 'initial' }} />
</div>
)
}
LocalContext.defaultProps = {
className: 'h-80',
}
LocalContext.propTypes = {
label: PropTypes.string,
location: PropTypes.object,
}
export default LocalContext

It seems that there's something that makes the LocalContextMapView to be called before the script was fully loaded. I can implement the Local Context in react by tweaking your onScriptLoad. I put a condition that will check if the script is loaded by adding a script-loaded attribute in the script.
const onScriptload = () => {
var gScript = document.querySelector("script-loaded");
var isLoaded = gScript && gScript.getAttribute("script-loaded");
if (!isLoaded) {
var index = document.getElementsByTagName("script")[0];
var script = document.createElement("script");
script.src =
"https://maps.google.com/maps/api/js?key=YOUR_API_KEY&libraries=localContext&v=beta&callback=initialize";
script.async = true;
script.defer = true;
script.setAttribute("script-loaded", true);
index.parentNode.insertBefore(script, index);
window.initialize = initMap;
} else {
initMap();
}
};
Here is the full code snippet and the working code:
import React, { useEffect } from "react";
import "./style.css";
const LocalContext = () => {
let map;
const onScriptload = () => {
var gScript = document.querySelector("script-loaded");
var isLoaded = gScript && gScript.getAttribute("script-loaded");
if (!isLoaded) {
var index = document.getElementsByTagName("script")[0];
var script = document.createElement("script");
script.src =
"https://maps.google.com/maps/api/js?key=YOUR_API_KEY&libraries=localContext&v=beta&callback=initialize";
script.async = true;
script.defer = true;
script.setAttribute("script-loaded", true);
index.parentNode.insertBefore(script, index);
window.initialize = initMap;
} else {
initMap();
}
};
function initMap() {
const localContextMapView = new google.maps.localContext.LocalContextMapView(
{
element: document.getElementById("map"),
placeTypePreferences: ["restaurant", "tourist_attraction"],
maxPlaceCount: 12
}
);
map = localContextMapView.map;
map.setOptions({
center: { lat: 51.507307, lng: -0.08114 },
zoom: 14
});
}
useEffect(() => {
onScriptload();
}, []);
return (
<div className="overflow-hidden">
<div id="map" />
</div>
);
};
export default LocalContext;

I would recommend usage of one of the two packages #googlemaps/js-api-loader and a useEffect hook or #googlemaps/react-wrapper(uses #googlemaps/js-api-loader internally).
import { Wrapper } from "#googlemaps/react-wrapper";
const MyApp = () => (
<Wrapper apiKey={"YOUR_API_KEY"} libraries={['localcontext']}>
<LocalContext />
</Wrapper>
);
The internals of the #googlemaps/react-wrapper are quite trivial if you want more control with the useEffect hook.

You need to specify the callback param in the query string pointing to a function in the window so that when the scripts finish loading, the callback function will be invoked. script.addEventListener('load') will not work because the initial script will load few other scripts so the initMap function should be called after everything is finished loading.
Also make sure to set the #map height explicitly to define the size of the div element that contains the map.
// initMap should be defined as a global function in the window
window.initMap = function initMap() {}

Related

Google Maps React Wrapper - Marker Cluster create #googlemaps/react-wrapper

I am using the Google ReactJS library to add Maps to my React web app and the #googlemaps/react-wrapper library to cluster markers. But I am not able to make marker clustering on the wrapper. Please, if anyone has any idea, help to solve the problem.
The component code is present below:
import React, { useEffect, useRef } from "react";
import { Wrapper, Status } from "#googlemaps/react-wrapper";
export default function MapContainer({ center, zoomLevel, markerList, icon }) {
const googleMapRef = useRef(null);
let googleMap = null;
useEffect(() => {
try {
let bounds = new window.google.maps.LatLngBounds();
const initGoogleMap = () => {
return new window.google.maps.Map(googleMapRef.current, {
center: center,
zoom: zoomLevel,
styles: [{ stylers: [{ saturation: -100 }] }]
});
}
const createMarker = (markerObj) => new window.google.maps.Marker({
position: { lat: parseFloat(markerObj.lat), lng: parseFloat(markerObj.lng) },
map: googleMap,
icon: {
url: icon,
scaledSize: new window.google.maps.Size(80, 80)
},
});
googleMap = initGoogleMap();
markerList.map(x => {
const marker = createMarker(x);
bounds.extend(marker.position);
});
if (markerList.length === 0) {
initGoogleMap();
bounds = new window.google.maps.LatLngBounds();
}
googleMap.fitBounds(bounds);
}
catch (e) {
console.log("maps", e)
}
})
const render = (status) => {
if (status === Status.LOADING) return "Loading...";
if (status === Status.FAILURE) return "Error";
return null;
};
return (
<div>
<div>
<Wrapper apiKey={TOKEN} render={render}>
{/* <GISTrackingMap zoomLevel={props.zoomLevel} mapFilterByVal={props.mapFilterByVal} center={props.center} gisTrackingData={props.gisTrackingData} /> */}
<div ref={googleMapRef} style={{ width: "100%", height: "78vh" }} />
</Wrapper>
</div>
</div>
)
}
To create a clusterer you have to have an array of markers. So I would suggest moving bounds.extend(marker.position); out of the markerList mapping and save the result:
const markers = markerList.map(x => {
createMarker(x);
});
And then just create a clusterer:
new MarkerClusterer({ googleMapRef, markers });
UPD
Looks like the MarkerClusterer doesn't work with ref, so the code above should be changed as follows:
new MarkerClusterer({ googleMap, markers });
Don't forget to install the library and add the import:
yarn add #googlemaps/markerclusterer
import { MarkerClusterer } from '#googlemaps/markerclusterer';

Markers in react-google-map doesnt render after change in props

I cant deal with that problem.
I got object locations. there are objects with info about position, function and icon color.
I send it to Map. At start it shows ok but then when i filter the array and send new ones to show it doesnt render new ones.
I add state to class and setState it- after check i see in console that everything is ok with changes- but markers are still the same.
Please help
locations is also a state in parent stateless component.
Map Component
import React, {Component} from 'react'
import GoogleMapReact from 'google-map-react'
import MarkerClusterer from '#google/markerclusterer'
export default class GoogleMapContainer extends Component {
state = {
locationsToShow: null
}
componentDidMount () {
const script = document.createElement('script')
script.src = 'https://developers.google.com/maps/documentation/javascript/examples /markerclusterer/markerclusterer.js'
script.async = true
document.body.appendChild(script)
}
componentDidUpdate(prevProps) {
if (this.props.locations !== prevProps.locations) {
this.setState({locationsToShow: this.props.locations})
}
}
static defaultProps = {
center: {
lat: 59.95,
lng: 30.33
},
zoom: 11
}
render () {
const setGoogleMapRef = (map, maps) => {
this.googleMapRef = map
this.googleRef = maps
let locations = this.props.locations
console.log('locations w propsie')
let markers = this.state.locationsToShow && this.state.locationsToShow.map((location) => {
let item = new this.googleRef.Marker({position: location.position, icon: location.icon })
google.maps.event.addListener(item, 'click', location.fn)
return item
})
console.log('markerow jest')
let markerCluster = new MarkerClusterer(map, markers, {
imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m',
gridSize: 60,
minimumClusterSize: 2
})
}
return (
<GoogleMapReact
bootstrapURLKeys={{key: `apiKey`}}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({map, maps}) => setGoogleMapRef(map, maps)}
defaultCenter={{lat: 52.193275, lng: 20.930372}}
defaultZoom={7}
options={{streetViewControl: true}}
/>
)
}
}
parent comp element
let locationsList = filteredData.map((el) => {
const lati = Number(el.location.latitude).toFixed(6)
const longi = Number(el.location.longitude).toFixed(6)
return {position: { lat: Number(lati), lng: Number(longi) }, icon: typeOfIcon(el.status), fn: ()=>{
setIsOpen(true)
setData(el)
} }
})
setLocations(locationsList)
setGoogleMapRef is only called when the map component is instantiated I am guessing (you want this). so you need to access the map and update your locations in your render function outside of the setGoogleMapRef callback.
something like the following:
render () {
const setGoogleMapRef = (map, maps) => {
this.googleMapRef = map
this.googleRef = maps
this.markerCluster = //...
};
if (this.googleMapRef) {
let locations = this.props.locations
console.log('locations w propsie')
let markers = this.state.locationsToShow &&
this.state.locationsToShow.map((location) => {
let item = new this.googleRef.Marker({position: location.position, icon: location.icon })
google.maps.event.addListener(item, 'click', location.fn)
return item
})
console.log('markerow jest')
// update marker cluster
}
return //...;
}

Fit google maps bounds using vue3-google-map

I am trying to build a Vue component that takes in input an array of gps (lat/lng) data and which have to be drawn on a map using a google maps polyline. It is working until this point. The problem rises when I try to use map.fitbounds in order to manage the zoom and center out the map to that polyline. As per vue3-google-map documentation I tried to create a ref to the map object with ref="mapRef". Unfortunately in the mounted() hook it seems that I can'f find that object, inside Vue DevTools it will later show up.
<template>
<GoogleMap ref="mapRef" api-key="<myapikey>" style="width: 100%; height: 500px" :center="center" :zoom="15">
<Polyline :options="path" />
</GoogleMap>
</template>
<script>
import {
defineComponent,
ref
} from 'vue'
import {
GoogleMap,
Polyline
} from 'vue3-google-map'
export default defineComponent({
components: {
GoogleMap,
Polyline
},
setup() {
const mapRef = ref(null)
return {
mapRef
}
},
methods: {
managebounds(path) {
this.intervalid = setInterval(function () {
if (!this.bounds && !this.loaded) {
if (window.google && window.google.maps && path) {
this.bounds = new window.google.maps.LatLngBounds()
this.loaded = true
clearInterval(this.intervalid)
console.log("AFTER CLEAR")
this.bounds.extend(path.path[0]) //i take the first and last point of the polyline
this.bounds.extend(path.path[path.path.length - 1])
var extendBy = 0.001;
console.log("EXTEND1")
var point1 = new window.google.maps.LatLng(
this.bounds.getNorthEast().lat() + extendBy,
this.bounds.getNorthEast().lng() + extendBy
)
var point2 = new window.google.maps.LatLng(
this.bounds.getSouthWest().lat() - extendBy,
this.bounds.getSouthWest().lng() - extendBy
)
this.bounds.extend(point1);
console.log("EXTEND2")
this.bounds.extend(point2);
console.log("FITTING BOUNDS")
this.intervalid = setInterval(function () {
if (this.$refs.mapRef.value?.ready) {
console.log("LOADED")
this.$refs.mapRef.value.map.fitBounds(this.bounds); //here mapRef is undefined
clearInterval(this.intervalid)
} else {
console.log("NOT LOADED")
}
}, 1000)
} else {
console.log("OUT")
}
}
}, 500)
}
},
mounted() {
this.$nextTick(function () {
this.managebounds(this.path)
})
},
data() {
return {
path: {
path: this.gpspath.path,
strokeColor: this.gpspath.color
},
bounds: null,
loaded: false,
intervalid: -1
}
},
props: {
"gpspath": {
type: [],
default: [{}]
}
}
})
</script>
Any hint about fixing this issue?
I had to read again the Vue documentation about computed properties and figured out is was a bad idea trying to build a Method.
I made a function inside the setup() hook and called that from my computed data.
The working result is in the following code:
setup() {
const mapRef = ref(null)
function centermap(start, end){
if (mapRef.value?.ready) {
const gmap = mapRef.value.map;
const api = mapRef.value.api;
this.bounds = new api.LatLngBounds();
this.bounds.extend(start);
this.bounds.extend(end);
gmap.fitBounds(this.bounds);
}else{
console.log("NOT READY")
}
}
return {
mapRef, centermap
}
},
computed: {
path() {
if(this.gpspath){
let filteredpath = this.gpspath.path.filter(x=>Math.abs(x.lat)>1)
if(filteredpath && filteredpath.length>1)
this.centermap(filteredpath[0], filteredpath[filteredpath.length-1])
return {
path: filteredpath,
strokeColor: this.gpspath.color
}
}else{
return {
path: [],
strokeColor: "#ffffff"
}
}
}
}
I also faced this problem; here is my solution:
Page with map:
import { mapFitBounds } from "#/composables/mapFitBounds";
const mapRef = ref(null);
watch(
() => mapRef.value?.ready,
(ready) => {
if (!ready) return;
mapFitBounds(mapRef, markersArray);
}
);
composables/mapFitBounds.js:
export function mapFitBounds(mapRef, markers) {
let bounds;
const api = mapRef.value.api;
const map = mapRef.value.map;
bounds = new api.LatLngBounds();
for (let i = 0; i < markers.length; i++) {
bounds.extend(markers[i]);
}
map.fitBounds(bounds);
}

How to use Google Map MarkerClusterer on OverlayView which is rendered via ReactDOM createPortal

I've been banging my head against the wall with google maps. See my initial issue here
I fixed the issue of using a react component as a map marker, by creating a portal with the OverlayView into a div. But now I'm faced with a bump in a rug now where I am now unsure how to create clustered markers?
Previously I used #googlemaps/markerclustererplus to create a cluster from an array of of markers created from a class CustomMarker that generates an OverlayView with the code snippet below.
const markers = mapMarkers.map((m) => {
const { position, icon, draggable } = m;
const marker = new CustomMarker(
position,
draggable,
icon,
map,
onChildMouseUp
);
return marker;
});
// Add a marker clusterer to manage the markers.
new MarkerClusterer(map, markers, {
calculator: (gMarkers, numStyles) => {
let index = 0;
const count = gMarkers.length;
let dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index += 1;
}
index = Math.min(index, numStyles);
return {
text: `${count}`,
index
};
},
gridSize: 50,
styles: [
{
className: "clusterMarker",
width: 50,
height: 18
}
]
});
Now I'm confused how to populate the 2nd argument in MarkerClusterer with the markers now I add markers in the components return statement
<Map id="map" mapHeight={height} mapWidth={width}>
{gmap && mapMarkers?.length > 0 ? (
<>
{mapMarkers.map((m, i) => {
return (
<CustomMarkerNew key={i} position={m.position} googleMap={gmap}>
{m.icon}
</CustomMarkerNew>
);
})}
</>
) : null}
</Map>
This is how the CustomMarkerNew component looks
CustomMarkerNew.js
import React, { useState, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
const OverlayView = (props) => {
const { google } = window;
const { googleMap, position, children } = props;
const [latlng, setLatlng] = useState(position);
const [container, setContainer] = useState(document.createElement("div"));
const [overlay, setOverlay] = useState(undefined);
const initOverlay = useCallback(() => {
const overlayView = new google.maps.OverlayView();
overlayView.setMap(googleMap);
overlayView.getDraggable = () => true;
setOverlay(overlayView);
setLatlng(position);
google.maps.OverlayView.preventMapHitsFrom(container);
}, [googleMap, container]);
const onAdd = useCallback(() => {
// only draw marker if it has not been assigned the customMarker class
if (!container.classList.contains("customMarker")) {
container.classList.add("customMarker");
container.style.position = "absolute";
const panes = overlay.getPanes();
panes.overlayImage.appendChild(container);
}
overlay.setPosition();
}, [overlay]);
const draw = useCallback(() => {
container.style.position = "absolute";
container.draggable = true;
overlay.getPanes().floatPane.appendChild(container);
overlay.setPosition();
}, [overlay, container, latlng]);
const onRemove = useCallback(() => {
ReactDOM.unmountComponentAtNode(container);
setContainer(undefined);
overlay.setMap(null);
}, [container, overlay]);
const setPosition = (pos) => {
// update global latlng value with current position of marker
if (pos) setLatlng(pos);
const projection = overlay.getProjection();
if (!projection) return;
// convert latlng value to pixel equivalent
const point = projection.fromLatLngToDivPixel(latlng);
// set left and top values from values from point variable to place marker correctly on map
if (point && point.x && point.y) {
container.style.left = `${point.x}px`;
container.style.top = `${point.y}px`;
}
};
const bindFunctions = useCallback(() => {
overlay.onAdd = onAdd.bind(this);
overlay.draw = draw.bind(this);
overlay.onRemove = onRemove.bind(this);
overlay.setPosition = setPosition.bind(this);
overlay.getDraggable = () => true;
}, [overlay]);
useEffect(() => {
if (overlay) {
bindFunctions();
}
}, [overlay]);
useEffect(() => {
initOverlay();
}, []);
return (
(overlay &&
children &&
container &&
ReactDOM.createPortal(children, container)) ||
null
);
};
export default React.memo(OverlayView);
Here is a CodeSandbox as an example
Any help would be much appreciated.

Google Map Integration with React Js projects

Need to use Google Maps API to get this as desired output: {'hotels','atms','banks','hospitals'}.
Already tried google-map-react but not worked properly.
Requirement it can be any of the below:
free api
script
library.
Reference Image:
You can use Google Maps Javascript's Places Nearby Search to search for the nearby places using different keyword as you mentioned. Here is a sample code from Google Maps Doc.
You can also implement it in reactjs using the code snippet below with the similar functionality as your use case.
import React from "react";
import ReactDOM from "react-dom";
import "./style.css";
let map;
const API_KEY = "YOUR_API_KEY";
const coords = { lat: 41.375885, lng: 2.177813 };
let markers = [];
class NearbySearchApp extends React.Component {
constructor(props) {
super(props);
this.renderMap = this.renderMap.bind(this);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
if (!window.google) {
const script = document.createElement("script");
script.type = "text/javascript";
script.src =
`https://maps.googleapis.com/maps/api/js?key=` +
API_KEY +
`&libraries=geometry,places`;
script.id = "googleMaps";
script.async = true;
script.defer = true;
document.body.appendChild(script);
script.addEventListener("load", (e) => {
this.renderMap();
});
} else {
this.renderMap();
}
}
renderMap() {
const el = document.getElementById("map");
if (el) {
map = new google.maps.Map(el, {
zoom: 14,
center: {
lat: coords.lat,
lng: coords.lng,
},
});
return map;
} else {
return null;
}
}
handleClick(data) {
//clearing markers and marker array everytime a keyword is clicked
for (let i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
markers = [];
document.getElementById("chosen").innerHTML =
"You clicked: " + data.keyword;
//NearbySearch function
let service = new google.maps.places.PlacesService(map);
service.nearbySearch(
{ location: coords, radius: 500, type: [data.keyword] },
function (results, status, pagination) {
if (status !== "OK") return;
const bounds = new google.maps.LatLngBounds();
for (let i = 0, place; (place = results[i]); i++) {
//creating markers icon per type of place
let image = {
url: place.icon,
size: new google.maps.Size(71, 71),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(17, 34),
scaledSize: new google.maps.Size(25, 25),
};
//creates marker for every place result
const marker = new google.maps.Marker({
map: map,
icon: image,
title: place.name,
position: place.geometry.location,
});
//putting markers in the array
markers.push(marker);
//showing markers from the array in the map
for (let i = 0; i < markers.length; i++) {
markers[i].setMap(map);
}
bounds.extend(place.geometry.location);
}
}
);
}
render() {
return (
<section>
<h1>ADD YOUR API KEY TO MAKE IT WORK</h1>
<div id="map" />
<div id="panel">
<h3 id="chosen">Please choose a Place:</h3>
<input
type="button"
onClick={this.handleClick.bind(null, { keyword: "atm" })}
value="ATM"
/><br/>
<input
type="button"
onClick={this.handleClick.bind(null, { keyword: "hospital" })}
value="Hospital"
/><br/>
<input
type="button"
onClick={this.handleClick.bind(null, { keyword: "store" })}
value="store"
/>
</div>
</section>
);
}
}
export default NearbySearchApp;
You can use the export default to export NearbySearchApp and import it to another script file like this:
import React from 'react';
import ReactDOM from 'react-dom';
import NearbySearchApp from './NearbySearchApp';
ReactDOM.render(<NearbySearchApp />, document.getElementById('app'));
Here's a sample code that implements this. Make sure to use your API key for the sample code to work properly.

Categories