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.
Related
I have an application in which I am using google maps. In the map component, I am drawing shapes and using coordinates and components passed by props to render the map and shapes on the map.
However, I want such that when the zones and coordinates change, the map should re-render. with the updated area map and shape.
When I add the zones and coordinates as array dependecies for the useEffect hook, I can see that the component re-render by logging to the console, but the google map does not.
Please, how do I go about this?
Has anyone implemented this before?
Do I have to call the "handleApiLoaded" function again?
My code is shown below.
import GoogleMapReact from "google-map-react";
import useHeightPort from "../../Hooks/useHeightport";
import "../../assets/css/Map.css";
import { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import CreateSelect from "./CreateSelect";
import ZoneInfoWindow from "./ZoneInfoWindow";
import {
createInfoWindow,
drawPolygon,
drawCircle,
fitMapBounds,
deepClone,
} from "./helpers"; /* I am using helper functions to tidy up things, not necessarily
integral to the apps functionality */
function Map({
propZones,
propCoords,
propParticipants,
showPLI
}) {
let participants = deepClone(propParticipants); // get all from a single object here
let [zones, setZones] = useState(deepClone(propZones));
let [coords, setCoords] = useState(deepClone(propCoords));
const [createDropdown, setCreateDropdown] = useState(<></>);
const { Height } = useHeightPort();
const calculatedHeight = Number(Height) - 289;
const defaultProps = {
center: { lat: 0, lng: 0 },
zoom: 0,
};
let globalMap, globalMaps;
let shapeObjs = [];
let activeZoneId = false;
const [markers, setMarkers] = useState([]);
useEffect(() => {
setZones(deepClone(propZones));
setCoords(deepClone(propCoords));
console.log("Something changed");
}, [propCoords, propZones /* zoneGroup_id, reshow */]);
const handleApiLoaded = (map, maps) => {
globalMap = map;
globalMaps = maps;
zones.forEach((zone, i) => {
// Draw Shape
let { shape, lat, lng } = drawShape(zone, coords[i]);
// Add InfoWindow to Polygon
let infoWindow = createIW(zone, lat, lng, shape);
shape.addInfoWindow(infoWindow);
shape.zone_id = zone.zone_id;
shapeObjs.push(shape);
});
// Add Participants to Map
if (participants) {
let icon;
let newMarkers = [];
participants.forEach((p, i) => {
icon = {
url: p.image,
//size: new maps.Size(40, 40),
size: new mapds.Size(40, 40),
};
// let marker = new maps.Marker({
let marker = new maps.Marker({
position: { lat: p.lat, lng: p.lng },
//map,
map,
icon,
visible: showPLI,
optimized: false,
});
// Create InfoWindow
let pliInfoWindow = createPLIIW(marker, "Impossible", p);
newMarkers.push({ marker, infoWindow: pliInfoWindow });
});
setMarkers(newMarkers);
}
fitMapBounds(maps, map, zones, coords, shapeObjs);
setCreateDropdown(<CreateSelect drawNewShape={drawNewShape} />); // To create a new shap when the api is fully loaded
};
// Draw Shape
function drawShape(zone, coord) {
const shape =
zone.shapeType === "polygon"
? drawPolygon(
zone.id,
globalMaps,
globalMap,
coord,
zone.style,
updateZoneCoordinates
)
: drawCircle(
zone.id,
globalMaps,
globalMap,
coord,
zone.style,
updateZoneCoordinates
);
let { lat, lng } =
zone.shapeType === "polygon"
? coord[0]
: { lat: coord.lat, lng: coord.lng };
return { shape, lat, lng };
}
// Create InfoWindow
function createIW(zone, lat, lng, shape) {
// Create InfoWindow
let infoDetails = {
loc: zone.location,
lat,
lng,
};
let infoDiv = document.createElement("div");
let infoContent = ReactDOM.render(
ZoneInfoWindow(zone.id, zone.title, infoDetails),
infoDiv
);
return createInfoWindow(infoContent, zone.id, globalMaps, {
parent: shape,
delete: deleteZone,
edit: editZoneWithForm,
});
}
// Draw new shape
const drawNewShape = (type) => {
let newZone = {
id: zones.length + 1,
title: "New Zone",
location: "Atlanta, Georgia",
shapeType: type,
style: {
strokeColor: "#0072ff",
strokeOpacity: 1,
strokeWeight: 2,
fillColor: "#FFFFFF",
fillOpacity: 0.4,
},
};
let newCoord = drawDefaultShape(type);
let { shape, lat, lng } = drawShape(newZone, newCoord);
let infoWindow = createIW(newZone, lat, lng, shape);
shape.addInfoWindow(infoWindow);
shape.obj.setEditable(true);
zones.push(newZone);
coords.push(newCoord);
shapeObjs.push(shape);
if (activeZoneId !== false && activeZoneId !== newZone.id) {
let activeZone = zones.find((z) => z.id === activeZoneId);
shapeObjs[zones.indexOf(activeZone)].obj.setEditable(false);
}
activeZoneId = newZone.id;
editZoneWithForm(newZone.id);
};
return (
<div className="map-container" style={{ height: calculatedHeight + 159 }}>
<div className="map-components" style={{ height: calculatedHeight - 70 }}>
<div className="map-create-zone-container">{createDropdown}</div>
<GoogleMapReact
bootstrapURLKeys={{ key: "GOOGLE_API_KEY" }} // This will be turned to an Environment variable when we push live
defaultCenter={defaultProps.center}
defaultZoom={defaultProps.zoom}
yesIWantToUseGoogleMapApiInternals
/* onGoogleApiLoaded={({ map, maps }) =>
handleApiLoaded.current(map, maps)
} */
onGoogleApiLoaded={({map, maps}) => handleApiLoaded(map, maps)}
></GoogleMapReact>
</div>
</div>
);
}
export default Map;
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';
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 //...;
}
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);
}
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() {}