react-google maps vehicle live tracking - reactjs

I am trying to move the selected marker with deviceId by fetching data from api endpoint. At first render i am fetching the cordinates data for last 1 minute and pushing the cordinates in an empty array declared as Path and after every 30 seconds I am running a setinterval function which fetches the data of last 30 seconds in every 30 seconds and pushes the cordinates in the Path array. this is the code which can give some idea:
import {
withGoogleMap,
withScriptjs,
GoogleMap,
Polyline,
Marker
} from "react-google-maps";
const mapStyles = require("./mapStyles.json");
const Map = ({deviceId}) => {
const [progress, setProgress]= useState()
const path= []
const getInitialPositions = async () => {
let from = new Date()
let milliseconds = Date.parse(from)
milliseconds = milliseconds - (1* 60 * 1000)
from = new Date(milliseconds).toISOString()
console.log(from)
const to = new Date().toISOString()
console.log(to)
const response = await fetch(`/api/positions/?deviceId=${37}&from=${from}&to=${to}`, {
headers: {
'Accept': 'application/json'
}
})
const items = await response.json()
console.log(items)
items.map( item => {
path.push({lat: item.latitude, lng: item.longitude})
return path
})
console.log(path)
};
const getPositions30Seconds = async () => {
let from = new Date()
let milliseconds = Date.parse(from)
milliseconds = milliseconds - (0.5* 60 * 1000)
from = new Date(milliseconds).toISOString()
console.log(from)
const to = new Date().toISOString()
console.log(to)
const response = await fetch(`/api/positions/?deviceId=14&from=${from}&to=${to}`, {
headers: {
'Accept': 'application/json'
}
})
const items = await response.json()
console.log(items)
items.map( item => {
path.push({lat: item.latitude, lng: item.longitude})
return path
})
console.log(path)
};
useEffect (() => {
const interval = window.setInterval(getPositions30Seconds,30000)
return () => {
window.clearInterval(interval)
}
},[]);
useEffect(()=>{
getInitialPositions()
},[])
const icon = {
url: '/images/icon/car.png',
scaledSize: new window.google.maps.Size(30, 30),
anchor: { x: 10, y: 10 }
};
return (
<GoogleMap
defaultZoom={4}
defaultCenter={path[path.length-1]}
defaultOptions={{ styles: mapStyles, fullscreenControl: false, mapTypeControl: false, streetViewControl: false}}
>
{progress && (
<>
<Polyline
path={progress}
options={{ strokeColor: "light" }}
/>
<Marker
icon={icon}
position={progress[progress.length - 1]}
/>
</>
)}
</GoogleMap>
);
};
const MapComponent = withScriptjs(withGoogleMap(Map))
export default () => (
<MapComponent
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLEMAP_KEY}&v=3.exp&libraries=geometry,drawing,places`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%`, width: '100%' }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
)
I want to use this path array just like below mentioned class component which have a static Path array data.
import React from "react";
import {
withGoogleMap,
withScriptjs,
GoogleMap,
Polyline,
Marker
} from "react-google-maps";
const mapStyles = require("./mapStyles.json");
class Map extends React.Component {
state = {
progress: [],
selectedMarker: false
};
path = [
{ lat: 28.539533333333335, lng: 77.05334444444445},
{lat: 28.539581666666667, lng: 77.05323333333334},
{lat: 28.539614999999998, lng: 77.05313333333334},
{lat: 28.539766666666665, lng: 77.05258166666667},
{lat: 28.539884444444443, lng: 77.05252666666667},
{lat: 28.539884444444443, lng: 77.05252666666667},
{lat: 28.542425, lng: 77.05253666666667},
{lat: 28.544408333333333, lng: 77.05254333333333},
{lat: 28.544445, lng: 77.052655},
{lat: 28.544383333333332, lng: 77.05419333333333},
{lat: 28.544383333333332, lng: 77.05419333333333},
{lat: 28.544383333333332, lng: 77.05419333333333},
{lat: 28.544383333333332, lng: 77.05419333333333},
{lat: 28.544383333333332, lng: 77.05419333333333},
{lat: 28.544439999999998, lng: 77.05512},
{lat: 28.544561666666667, lng: 77.055295},
{lat: 28.546363333333336, lng: 77.05680833333334},
{lat: 28.54712166666667, lng: 77.05741277777777},
{lat: 28.547226666666667, lng: 77.05737},
{lat: 28.54752166666667, lng: 77.05704},
{lat: 28.54752166666667, lng: 77.05704},
{lat: 28.54752166666667, lng: 77.05704},
{lat: 28.54752166666667, lng: 77.05704},
{lat: 28.54752166666667, lng: 77.05704},
{lat: 28.547706666666667, lng: 77.05692833333333},
{lat: 28.548081666666665, lng: 77.05644666666666},
{lat: 28.548235000000002, lng: 77.05629},
{lat: 28.548235000000002, lng: 77.05629},
{lat: 28.548571666666668, lng: 77.05574333333333},
{lat: 28.548655, lng: 77.05571166666667},
{lat: 28.548745, lng: 77.05563666666667},
{lat:28.55049, lng: 77.05438},
{lat: 28.550714999999997, lng: 77.05413666666666},
{lat: 28.55175, lng: 77.05356833333333},
{lat: 28.553496666666668, lng: 77.05223166666667},
{lat: 28.553915, lng: 77.05173833333333 }
];
velocity = 50;
initialDate = new Date();
getDistance = () => {
// seconds between when the component loaded and now
const differentInTime = (new Date() - this.initialDate) / 8000; // pass to seconds
return differentInTime * this.velocity;
};
componentDidMount = () => {
this.interval = window.setInterval(this.moveObject, 100);
};
handleClick = (marker, event) => {
// console.log({ marker })
this.setState({ selectedMarker: marker })
}
componentWillUnmount = () => {
window.clearInterval(this.interval);
};
moveObject = () => {
const distance = this.getDistance();
if (!distance) {
return;
}
let progress = this.path.filter(
coordinates => coordinates.distance < distance
);
const nextLine = this.path.find(
coordinates => coordinates.distance > distance
);
if (!nextLine) {
this.setState({ progress });
return; // it's the end!
}
const lastLine = progress[progress.length - 1];
const lastLineLatLng = new window.google.maps.LatLng(
lastLine.lat,
lastLine.lng
);
const nextLineLatLng = new window.google.maps.LatLng(
nextLine.lat,
nextLine.lng
);
// distance of this line
const totalDistance = nextLine.distance - lastLine.distance;
const percentage = (distance - lastLine.distance) / totalDistance;
const position = window.google.maps.geometry.spherical.interpolate(
lastLineLatLng,
nextLineLatLng,
percentage
);
progress = progress.concat(position);
this.setState({ progress });
};
componentWillMount = () => {
this.path = this.path.map((coordinates, i, array) => {
if (i === 0) {
return { ...coordinates, distance: 0 }; // it begins here!
}
const { lat: lat1, lng: lng1 } = coordinates;
const latLong1 = new window.google.maps.LatLng(lat1, lng1);
const { lat: lat2, lng: lng2 } = array[0];
const latLong2 = new window.google.maps.LatLng(lat2, lng2);
// in meters:
const distance = window.google.maps.geometry.spherical.computeDistanceBetween(
latLong1,
latLong2
);
return { ...coordinates, distance };
});
console.log(this.path);
};
componentDidUpdate = () => {
const distance = this.getDistance();
if (!distance) {
return;
}
let progress = this.path.filter(
coordinates => coordinates.distance < distance
);
const nextLine = this.path.find(
coordinates => coordinates.distance > distance
);
let point1, point2;
if (nextLine) {
point1 = progress[progress.length - 1];
point2 = nextLine;
} else {
// it's the end, so use the latest 2
point1 = progress[progress.length - 2];
point2 = progress[progress.length - 1];
}
const point1LatLng = new window.google.maps.LatLng(point1.lat, point1.lng);
const point2LatLng = new window.google.maps.LatLng(point2.lat, point2.lng);
const angle = window.google.maps.geometry.spherical.computeHeading(
point1LatLng,
point2LatLng
);
const actualAngle = angle - 35;
const markerUrl =
'/images/icon/car.png'
const item = document.querySelector(`[src="${markerUrl}"]`);
if (item) {
// when it hasn't loaded, it's null
item.style.transform = `rotate(${actualAngle}deg)`;
}
};
render = () => {
const icon = {
url: '/images/icon/car.png',
scaledSize: new window.google.maps.Size(35, 35),
anchor: { x: 10, y: 10 }
};
return (
<GoogleMap
defaultZoom={18}
defaultCenter={{lat: 28.539766666666665, lng: 77.05258166666667}}
defaultOptions={{
styles: mapStyles,
fullscreenControl: false,
mapTypeControl: false,
streetViewControl: false,
}}
>
{this.state.progress && (
<>
<Polyline
path={this.state.progress}
options={{ strokeColor: "gray" }}
/>
<Marker
icon={icon}
position={this.state.progress[this.state.progress.length - 1]}
/>
</>
)}
</GoogleMap>
);
};
}
const MapComponent = withScriptjs(withGoogleMap(Map));
export default () => (
<MapComponent
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLEMAP_KEY}&v=3.exp&libraries=geometry,drawing,places`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%`, width: '100%' }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
)
It works fine(with static data) and the marker moves smoothly but .I want to implement this dynamically for every devices on click. I am posting this because I am stuck as I am new to react . I am finding hooks a bit easy so tried with functional component but failing to do it. stuck since 5 days and now i am helpless. Somebody help me to implement this like a charm.

Related

How to use MarkerClusterer with #googlemaps/react-wrapper

After following this example provided by google for adding a map and markers to a react application using #googlemaps/react-wrapper, I decided to try adding marker clustering to my app. However, I am unable to get the clusters to appear.
I tried following this question/answer but no clusters have appeared despite adding new MarkerClusterer({ ref, markers }); to my code:
App.js
/*global google*/
import { useState, useEffect, useRef } from "react";
import { Wrapper } from "#googlemaps/react-wrapper";
import { MarkerClusterer } from "#googlemaps/markerclusterer";
import { createCustomEqual } from "fast-equals";
const markerList = [
{ id: "A12345", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 44.459744, lng: -73.214126, assetName: "A" },
{ id: "B09423", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 44.465291, lng: -73.190723, assetName: "A" },
{ id: "C98765", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 44.476949, lng: -73.210578, assetName: "B" },
{ id: "D99999", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 44.444572, lng: -73.208741, assetName: "B" },
{ id: "E12345", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 38.459744, lng: -81.214126, assetName: "A" },
{ id: "F09423", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 38.465291, lng: -81.190723, assetName: "A" },
{ id: "G98765", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 38.476949, lng: -81.210578, assetName: "B" },
{ id: "H99999", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 38.444572, lng: -81.208741, assetName: "B" },
]
const render = (status) => {
return <h1>{status}</h1>;
};
const App = () => {
const [zoom, setZoom] = useState(8); // initial zoom
const [center, setCenter] = useState({ lat: 44.45, lng: -73.21 });
const onIdle = (m) => {
console.log("onIdle");
};
return (
<>
<div style={{ width: "500px", height: "500px" }}>
<Wrapper
apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}
render={render}
>
<Map
center={center}
onIdle={onIdle}
zoom={zoom}
style={{ flexGrow: "1", height: "100%" }}
/>
</Wrapper>
</div>
</>
);
};
const Map = ({ onIdle, children, style, ...options }) => {
const ref = useRef(null);
const [map, setMap] = useState();
useEffect(() => {
if (ref.current && !map) {
setMap(new window.google.maps.Map(ref.current, {}));
}
// Add some markers to the map.
const markers = markerList.map((m) => {
return new window.google.maps.Marker({
position: { lat: parseFloat(m.lat), lng: parseFloat(m.lng) }
//map: map,
});
});
// Add a marker clusterer to manage the markers.
new MarkerClusterer({ ref, markers });
}, [ref, map]);
useDeepCompareEffectForMaps(() => {
if (map) {
map.setOptions(options);
}
}, [map, options]);
useEffect(() => {
if (map) {
["click", "idle"].forEach((eventName) =>
google.maps.event.clearListeners(map, eventName)
);
if (onIdle) {
map.addListener("idle", () => onIdle(map));
}
}
}, [map, onIdle]);
return (
<>
<div ref={ref} style={style} />
</>
);
};
const deepCompareEqualsForMaps = createCustomEqual((deepEqual) => (a, b) => {
if (a instanceof google.maps.LatLng || b instanceof google.maps.LatLng) {
return new google.maps.LatLng(a).equals(new google.maps.LatLng(b));
}
return deepEqual(a, b);
});
function useDeepCompareMemoize(value) {
const ref = useRef();
if (!deepCompareEqualsForMaps(value, ref.current)) {
ref.current = value;
}
return ref.current;
}
function useDeepCompareEffectForMaps(callback, dependencies) {
useEffect(callback, dependencies.map(useDeepCompareMemoize));
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
How can I get MarkerClusterer to function with #googlemaps/react-wrapper?
Replace
new MarkerClusterer({ ref, markers });
with
new MarkerClusterer({ map, markers });
PS: The answer to the mentioned question was updated

Geocode in react app not updating the map React

I'm trying to get the map geocode but when the map is loading it never uses the new lat and lng. It's always the original one from the useState.
const CustomMap = () => {
const [customLat, setCustomLat] = useState(0);
const [customLng, setCustomLng] = useState(0);
useEffect(() => {
Geocode.fromAddress("Eiffel Tower").then(
(response) => {
const { lat, lng } = response.results[0].geometry.location;
setCustomLat(lat);
setCustomLng(lng);
},
(error) => {
console.log(error.data);
}
);
}, [])
return (
<GoogleMapReact
bootstrapURLKeys={{ key: 'API_KEY' }}
defaultCenter={{ lat: customLat, lng: customLng }}
defaultZoom={11}>
</GoogleMapReact>
)
}
Even though in the console I can see it's getting the map with url of the Eifell towel.
Fetch finished loading: GET "https://maps.googleapis.com/maps/api/geocode/json?address=Eiffel%20Tower&key=KEY&language=en".
That is the last console log but the center of the map is still the original ones.
You are using the default, this only works on the first render, to update you need use the center, like this
<GoogleMapReact
bootstrapURLKeys={{ key: 'API_KEY' }}
defaultCenter={{ lat: customLat, lng: customLng }}
center = {{lat: customLat, lng: customLng}}
defaultZoom={11}>
</GoogleMapReact>

Limit the number of clicks on map using React

I am using the Google Maps API and I want to limit the number of markers on the map (limit: 10). I couldn't find anything related to it in the API docs
neither I can find any similar source to solve my problem.
Here is my code:
import React from "react";
import {
GoogleMap,
useLoadScript,
Marker,
} from "#react-google-maps/api";
const mapContainerStyle = {
height: "50vh",
width: "100vw",
};
const options = {
zoomControl: false,
scrollwheel: false,
draggable: false
};
const center = {
lat: 34.155834,
lng: -119.202789,
};
export default function App() {
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: "AIzaSyCpaQDSgGTCetTR0uz42RyV80cByaGaYLs",
});
const [markers, setMarkers] = React.useState([]);
const onMapClick =
React.useCallback((e) => {
setMarkers((current) =>
[
...current,
{
lat: e.latLng.lat(),
lng: e.latLng.lng(),
},
]);
}, []);
const mapRef = React.useRef();
const onMapLoad = React.useCallback((map) => {
mapRef.current = map;
}, []);
if (loadError) return "Error";
if (!isLoaded) return "Loading...";
return (
<div>
<GoogleMap
id="map"
mapContainerStyle={mapContainerStyle}
zoom={14}
center={center}
options={options}
onClick={onMapClick}
onLoad={onMapLoad}
>
{markers.map((marker) => (
<Marker
key={`${marker.lat}-${marker.lng}`}
position={{ lat: marker.lat, lng: marker.lng }}
onClick={() => {
console.log("clicked")
}}
/>
))}
</GoogleMap>
</div>
);
}
How do I set the number of clicks up to 10?
You could do something like this. You may also want to call another function before returning current to perform some other update to alert the user they are maxed out on markers.
const onMapClick = React.useCallback((e) => {
setMarkers((current) => {
if (current.length < 10) {
return [
...current,
{
lat: e.latLng.lat(),
lng: e.latLng.lng()
}
];
} else {
return current;
};
});
}, []);

Can I export values from an arrow function in ReactJS?

I'm trying to use pass lng lat variables to the a google maps component from a google's geocode API, however since the geocode function is an arrow function I'm unable to use the values outside of this function. Is there any way to fix this? I need to be able to use the constants lat and lng from the geocode function and pass them to the lat and lang properties in the MapWithMarker constant
Geocode.fromAddress("5th Avenue New York").then(
response => {
const{ lat, lng } = response.results[0].geometry.location;
},
error => {
console.error(error);
}
);
const MapWithAMarker = withGoogleMap(props =>
<GoogleMap
defaultZoom={0}
defaultCenter={{ lat: lat, lng: lng }}
>
<Marker
position={{ lat: lat, lng: lng }}
/>
</GoogleMap>
);
./src/components/dashboard/ProductDetails.js
Line 107: 'lat' is not defined no-undef
Line 107: 'lng' is not defined no-undef
Line 110: 'lat' is not defined no-undef
Line 110: 'lng' is not defined no-undef
One way of doing this is
var MapWithAMarker;
Geocode.fromAddress("5th Avenue New York").then(
response => {
const{ lat, lng } = response.results[0].geometry.location;
MapWithAMarker = withGoogleMap(props =>
<GoogleMap
defaultZoom={0}
defaultCenter={{ lat: lat, lng: lng }}
>
<Marker
position={{ lat: lat, lng: lng }}
/>
</GoogleMap>
);
}
)
.catch(err => console.log(err));
And then you do use it like
if( MapWithAMarker ){
// do whatever you want;
}
You can cache them such as:
const MyMapComponent = function(props) {
const [position, setPosition] = React.useState();
React.useEffect(() => {
Geocode.fromAddress("5th Avenue New York").then(
response => {
setPosition(response.results[0].geometry.location);
},
error => {
console.error(error);
}
);
}, [])
if (!position) {
return null;
}
return <MapWithAMarker {...props} lat={position.lat} lng={position.lng} />
}
const MapWithAMarker = withGoogleMap(props =>
const
<GoogleMap
defaultZoom={0}
defaultCenter={{ lat: props.lat, lng: props.lng }}
>
<Marker
position={{ lat: props.lat, lng: props.lng }}
/>
</GoogleMap>
);
And now you can use MyMapComponent as a decorator of MapWithAMarker.

How to add coordinates to google-maps-react on a user click event?

I'm trying to add a marker on based on a user clicking on the map for the google-maps-react npm. Currently the code below will generate markers and add them to the this.state = { markers:[ ] } and I would like to map them out on the map component. However, the position:event.latLng, will not register the lat and lng and the marker will only be created and inserted into the state with the key: Date.now() and defaultAnimation: 2. Below the code:
import React, { Component } from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
export class MapContainer2 extends Component {
constructor(props){
super(props);
this.state={
lat:null,
lng:null,
markers:[]
}
}
componentDidMount(){
navigator.geolocation.getCurrentPosition(position=>
this.setState({
lat:position.coords.latitude,
lng:position.coords.longitude,
}));
}
mapClicked = (event) =>{
const { markers } = this.state;
this.setState({
markers:[
{
position:event.latLng,
key: Date.now(),
defaultAnimation: 2,
},
...markers
]
})
}
render() {
if (!this.props.loaded) {
return <div>Loading...</div>
}
const style = {
width: '100%',
height: '100vh'
}
return (
<Map
google={this.props.google}
zoom={11}
style={style}
initialCenter={{
lat: this.state.lat,
lng: this.state.lng
}}
center={{
lat: this.state.lat,
lng: this.state.lng
}}
onClick={this.mapClicked}
>
<Marker
title={'Geolocation'}
position={{
lat:this.state.lat,
lng:this.state.lng,
}}
/>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: ('AIzaSyCZ7rgMN34kWkGvr8Pzkf_8nkT7W6gowBA')
})(MapContainer2)
I resolved it by updating the function mapClicked with the following:
mapClicked = (mapProps, map, event) => {
const { markers } = this.state;
const lat = event.latLng.lat();
const lng = event.latLng.lng();
let url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=myapikey`
axios.get(url).then(response => {
this.setState({
googleReverseGeolocation:response.data.results[0].formatted_address,
markers:[{position:{lat:event.latLng.lat(),lng:event.latLng.lng()}}, ...markers],
latClick:lat,
lngClick:lng
});
this.props.onMapClickChange(lat, lng, response.data.results[0].formatted_address);
});
}

Resources