How to make a moving marker like Uber cars? - reactjs

I create a marker like this:
import React from "react";
import config from 'config';
import { compose, withProps, withState, lifecycle } from "recompose";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
DirectionsRenderer,
Polyline,
} from "react-google-maps";
const googleApiKey = config.googleApiKey;
const HistoryView = compose(
withProps({
googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${googleApiKey}&v=3.exp&libraries=geometry,drawing,places`,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `345px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withState('zoom', 'onZoomChange', 11),
withScriptjs,
withGoogleMap,
lifecycle({
componentDidMount() {
const { historyCords } = this.props;
},
componentWillMount() {
this.setState({
zoomToMarkers: map => {
//console.log("Zoom to markers");
const bounds = new google.maps.LatLngBounds();
map.props.children.forEach((child) => {
if (child.type === Marker) {
bounds.extend(new google.maps.LatLng(child.props.position.lat, child.props.position.lng));
}
})
map.fitBounds(bounds);
}
})
},
})
)(props =>
<GoogleMap
//ref={props.zoomToMarkers}
defaultZoom={8}
defaultCenter={new google.maps.LatLng(props.historyCords[0].latitude, props.historyCords[0].longitude)}
center={new google.maps.LatLng(props.latitude, props.longitude)}
zoom={17}
>
{<Polyline path={props.polylineCords}
geodesic={true}
options={{
strokeColor: "#1e9494",
strokeOpacity: 0.75,
strokeWeight: 2,
icons: [
{
icon: "lineSymbol",
offset: "0",
repeat: "20px"
}
]
}}
/>}
{<Marker
options={{icon: {url: "../../public/images/red-mark.svg", scaledSize: new window.google.maps.Size(30, 62)}}}
position={{ lat: props.latitude, lng: props.longitude }}
onClick={props.onMarkerClick} />
}
</GoogleMap>
);
export { HistoryView };
Now how do I move this marker like a car on location updates?
I use states to update the position of the marker but it doesn't animate. How do I do this?
My issue is when a latlng is updated the marker jumps from one place to another but I want it to move like a car. Have you ever tracked an Uber ride on the web? something like that.
Gif for car animation

Related

could not update state after onplacechanged event occure in react js?

__ I have used withprops in react-google-maps. i have also used searchbox from react-google-maps my problem is that when onplacechanged event occure i want i am trying to update center in state but this.setState is not accesible in onplacechabged function. i want to access this.setState from RMSelectLocation class but it could'nt accesible. is there any other to imlement google map with autocomplete searchbox in reactjs __
import React, { Component, useEffect } from "react";
import { compose, withProps, lifecycle } from "recompose";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
} from "react-google-maps";
import { get } from "lodash";
import { SearchBox } from "react-google-maps/lib/components/places/SearchBox";
var cntr = { lat: 19.993982, lng: 73.790416 };
class RMSelectLocation extends Component {
state = {
locations: "nashik",
center: { lat: 19.993982, lng: 73.790416 },
zoom: 12,
type: "",
};
componentDidMount() {}
render() {
const MapWithASearchBox = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key=AIzaSyAIVf-f5cukgHjy_ZEv32yc9Liehb4vTGQ&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `200%` }} />,
}),
lifecycle({
componentWillMount() {
const refs = {};
this.setState({
bounds: null,
center: {
lat: 19.9975,
lng: 73.7898,
},
markers: [],
onMapMounted: (ref) => {
refs.map = ref;
},
onBoundsChanged: () => {
this.setState({
bounds: refs.map.getBounds(),
center: refs.map.getCenter(),
});
},
onSearchBoxMounted: (ref) => {
refs.searchBox = ref;
},
onPlacesChanged: () => {
console.log(this.state);
console.log(this.props);
const places = refs.searchBox.getPlaces();
const bounds = new window.google.maps.LatLngBounds();
places.forEach((place) => {
if (place.geometry.viewport) {
bounds.union(place.geometry.viewport);
} else {
bounds.extend(place.geometry.location);
}
});
const nextMarkers = places.map((place) => ({
position: place.geometry.location,
}));
const nextCenter = get(
nextMarkers,
"0.position",
this.state.center
);
this.setState({
center: nextCenter,
markers: nextMarkers,
});
},
});
},
}),
withScriptjs,
withGoogleMap
)((props) => (
<GoogleMap
ref={props.onMapMounted}
defaultZoom={15}
center={props.center}
onBoundsChanged={props.onBoundsChanged}
defaultMapTypeId={"hybrid"}
>
<SearchBox
ref={props.onSearchBoxMounted}
bounds={props.bounds}
controlPosition={window.google.maps.ControlPosition.TOP_RIGHT}
onPlacesChanged={props.onPlacesChanged}
>
<input
type="text"
placeholder="Customized your placeholder"
style={{
boxSizing: `border-box`,
border: `1px solid transparent`,
width: `240px`,
height: `32px`,
marginTop: `27px`,
padding: `0 12px`,
borderRadius: `3px`,
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.3)`,
fontSize: `14px`,
outline: `none`,
textOverflow: `ellipses`,
}}
/>
</SearchBox>
{props.markers.map((marker, index) => (
<Marker key={index} position={marker.position} />
))}
</GoogleMap>
));
return (
<MapWithASearchBox />
);
}
}
export default RMSelectLocation;
Ciao, if this.setState is not reachable could be a this context problem as explained here. Take a look and let us know :)

Can not get radius value from Editable Circle onRadiusChanged event

I am new to React Google Maps. I'm unable to get radius value from Circle.
https://github.com/JustFly1984/react-google-maps-api
I want to get circle redius onRadiusChanged event.
index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import Map from './Map';
import './style.css';
const App = () => {
return (
<div>
<Map
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyDurZQBXjtSzKeieXwtFeGe-jhZu-HEGQU&libraries=drawing"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
center={{ lat: -33.8665433, lng: 151.1956316 }}
zoom={15}
/>
</div>
);
};
render(<App />, document.getElementById('root'));
map.js
import React, { Component } from "react";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
Circle
} from "react-google-maps";
class MyMapComponent extends Component {
constructor(props) {
super(props);
this.state = {
radius: 50
}
}
updateRadius(e) {
console.log(e);
}
render() {
return (
<GoogleMap
defaultZoom={this.props.zoom}
defaultCenter={this.props.center}
>
<Marker google={this.props.google}
name={'Dolores park'}
draggable={true}
onDragEnd={this.onMarkerDragEnd}
position={this.props.center}
/>
<Circle
center={this.props.center}
options={{
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 1,
fillColor: "#FF0000",
fillOpacity: 0.35,
clickable: false,
draggable: false,
editable: true,
visible: true,
radius: this.state.radius, //Calculation in Meter
zIndex: 1
}}
onRadiusChanged={this.updateRadius}
/>
</GoogleMap>
);
}
}
export default withScriptjs(withGoogleMap(MyMapComponent));
Result -
undefined
onRadiusChanged (as the underlying center_changed event for a Circle class ) event does not accept any arguments. Since onRadiusChanged event handler method is not bound with its component instance in your example, the updated radius could be determined like this:
updateRadius() {
const updatedRadius = this.getRadius();
}
where this refers to native Circle object.

Why react-google-maps rendering one Circle component twice?

When I added react-google-maps to project, render worked twice. So I have 2 circles one under another. Also, I display the center coordinates by onDragEnd() method. This event works for only one of this circles.
Any others google maps dosen`t exist on project.
Here is some ways I was trying to fix it:
1) Use only withGoogleMap,
2) Use GoogleMapsWrapper component inside render() method of parent component,
3) Use componentDidMount();
trying everything from satckoverflow :)
and nothing helps.
import React, { Component } from 'react';
import MapForm from './mapForm';
import { GoogleMap, withGoogleMap, withScriptjs, Circle } from 'react-google-maps';
const GoogleMapsWrapper = withScriptjs(withGoogleMap(props => {
const {onMapMounted, ...otherProps} = props;
return <GoogleMap {...otherProps} ref={c => {
onMapMounted && onMapMounted(c)
}}>{props.children}</GoogleMap>
}));
class GoogleMapsContainer extends Component {
state = {
coords: {lat:0, lng: 0}
};
dragCircle = () => {
this.setState({
coords: {
lat: this._circle.getCenter().lat(),
lng: this._circle.getCenter().lng()
}
})
}
render() {
return(
<div style={{display: 'flex',flexDirection: 'row', width: '100%', marginLeft: '37px'}}>
<MapForm
filters={this.props.filters}
coords={this.state.coords}
/>
<GoogleMapsWrapper
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${KEY}&v=3.exp&libraries=geometry,drawing,places`}
loadingElement={<div style={{height: `100%`}}/>}
containerElement={<div style={{position: 'relative',width: '100%', }} />}
mapElement={<div style={{height: `100%`}}/>}
defaultZoom={13}
defaultCenter={KYIV}
>
<Circle
ref={(circle) => {this._circle = circle}}
defaultCenter = {KYIV}
defaultDraggable={true}
defaultEditable={true}
defaultRadius={2000}
onDragEnd = {this.dragCircle}
options={{
strokeColor: `${colors.vividblue}`,
fillColor: `${colors.vividblue}`,
fillOpacity: 0.1
}}
/>
</GoogleMapsWrapper>
</div>
)
}
}
export default GoogleMapsContainer;
I need only one circle with my methods.mycircles
Ok, the problem was in React StrictMode component in project.

google maps in redux form

i'd like to use google maps for getting lat and long using redux form. I create this:
import React from 'react';
import { compose, withProps, lifecycle } from 'recompose';
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
const MyMapComponent = compose(
withProps({
googleMapURL:
'https://maps.googleapis.com/maps/api/js?key=AIzaSyCYSleVFeEf3RR8NlBy2_PzHECzPFFdEP0&libraries=geometry,drawing,places',
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
lifecycle({
componentWillMount() {
const refs = {};
this.setState({
position: null,
onMarkerMounted: ref => {
refs.marker = ref;
},
onPositionChanged: () => {
const position = refs.marker.getPosition();
console.log(position.toString());
},
});
},
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap defaultZoom={8} defaultCenter={{ lat: -34.397, lng: 150.644 }}>
{props.isMarkerShown && (
<Marker
position={{ lat: -34.397, lng: 150.644 }}
draggable={true}
ref={props.onMarkerMounted}
onPositionChanged={props.onPositionChanged}
/>
)}
</GoogleMap>
));
class MyParentComponentWrapper extends React.PureComponent {
state = {
isMarkerShown: false,
};
render() {
return (
<div>
<MyMapComponent isMarkerShown={true} />
</div>
);
}
}
export default MyParentComponentWrapper;
But it does not return any values and the does not show the lat and long in the field What should i do? it will console.log the lat and long when user drag the marker
If you want MyMapComponent to return the value to MyParentComponentWrapper just pass a callback function as a prop. Just change the parent component to:
<MyMapComponent isMarkerShown={true} onMakerPositionChanged={this.onMakerPositionChanged}/>
and the map component to:
onPositionChanged: () => {
const position = refs.marker.getPosition();
console.log(position.toString());
this.props.onMakerPositionChanged(position);
},
See working example here

How to use fitBounds in react-google-map once you have bounds

Where exactly do I call fitBounds or map.fitbounds. I am confused where to put it once I have the bounds or how to use it. I have set the bounds to a local state of bounds.
I have looked at this post here react-google-maps: how to use fitBounds, panBy, panTo, panToBounds public APIs? and either I just do it different or something because it does not make much since to me.
I have created a bounds const and then each time I make a marker I have added each of those to the bounds doing bounds.extends(Marker in here)
The code is a little messy so I will point out where I do this in it. Inside the filterItemsByRadius is where I create and set the bounds. At the end of the map function I think set the state of bounds to the bounds.
/* global google */
import { default as React, Component } from 'react';
import raf from 'raf';
import canUseDOM from 'can-use-dom';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import {
withGoogleMap,
GoogleMap,
Circle,
InfoWindow,
Marker,
withScriptjs,
} from 'react-google-maps';
import geolib from 'geolib';
import { geolocated } from 'react-geolocated';
import ItemList from './ItemList';
import { Col } from 'react-bootstrap';
import Paper from 'material-ui/Paper';
import Img from 'react-image';
import RaisedButton from 'material-ui/RaisedButton';
import FontIcon from 'material-ui/FontIcon';
import CreateRadius from './CreateRadius';
import offerActions from '../../redux/actions/offerActions';
const googleMapURL =
'https://maps.googleapis.com/maps/api/js?libraries=places,geometry&key=AIzaSyA7XEFRxE4Lm28tAh44M_568fCLOP_On3k';
const isJson = str => {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
};
const GeolocationGoogleMap = withScriptjs(
withGoogleMap(props => (
<GoogleMap defaultZoom={6} zoom={props.zoom} center={props.center} onClick={props.onMapClick}>
{props.center && (
<Marker
position={props.center}
title={"User's Location"}
options={{ icon: require('./assets/blueDot.png') }}
>
{props.showCenterInfo && (
<InfoWindow>
<div>User's Location</div>
</InfoWindow>
)}
</Marker>
)}
{/* <Circle
center={props.center}
radius={props.zoom}
options={{
fillColor: 'red',
fillOpacity: 0.2,
strokeColor: 'red',
strokeOpacity: 1,
strokeWeight: 1,
}}
/> */}
{props.markers.map((marker, index) => {
const onClick = () => props.onMarkerClick(marker);
const onCloseClick = () => props.onCloseClick(marker);
return (
<Marker
key={index}
position={marker.position}
title={marker.number.toString()}
onClick={onClick}
options={{ icon: 'https://image.ibb.co/evMHxF/shopping_zone_marker_1.png' }}
>
{marker.showInfo && (
<InfoWindow onCloseClick={onCloseClick}>
<div>
<ItemList marker={marker} />
</div>
</InfoWindow>
)}
</Marker>
);
})}
</GoogleMap>
)),
);
class OfferMap extends Component {
constructor(props) {
super(props);
this.state = {
currentPosition: null,
center: null,
content: null,
radius: 15, // ACZ --> put this const in config_env.
showCenterInfo: true,
markers: [],
zoom: 6,
bounds: null,
};
this.handleMarkerClick = this.handleMarkerClick.bind(this);
this.handleCloseClick = this.handleCloseClick.bind(this);
this.handleMapClick = this.handleMapClick.bind(this);
this.filterItemsByRadius = this.filterItemsByRadius.bind(this);
this.radiusChange = this.radiusChange.bind(this);
this.zoomChange = this.zoomChange.bind(this);
}
componentWillReceiveProps(props) {
if (props.coords && !props.coords.positionError) {
this.setState({ center: { lat: props.coords.latitude, lng: props.coords.longitude } });
} else {
fetch('http://ip-api.com/json')
.then(res => res.json())
.then(data => {
this.setState({ center: { lat: data.lat, lng: data.lon } });
})
.catch(error => {
this.setState({ content: `Error: The Geolocation service failed (${error.message}).` });
});
}
this.setState({ markers: props.items });
}
handleMapClick() {
this.setState({ showCenterInfo: false });
}
handleMarkerClick(targetMarker) {
this.setState({
markers: this.state.markers.map(marker => {
if (marker._id === targetMarker._id) {
return {
...marker,
showInfo: true,
};
}
return marker;
}),
});
}
handleCloseClick(targetMarker) {
this.setState({
markers: this.state.markers.map(marker => {
if (marker._id === targetMarker._id) {
return {
...marker,
showInfo: false,
};
}
return marker;
}),
});
}
filterItemsByRadius(userRadius) {
const items = this.state.markers;
const markers = [];
const bounds = new google.maps.LatLngBounds();
items.map((item, i) => {
let itemGeolocation;
let itemDescription = 'NO DESCRIPTION';
let itemThumb;
// Should be ready - tbaustinedit
if (item) {
itemDescription = item.description;
itemThumb = item.media.mediaVault[item.media.defaultIdx] || {
mediaType: 'txt',
};
}
if (item.geolocation) {
itemGeolocation = item.geolocation.coords;
}
if (this.state.center) {
const currentLocation = {
latitude: this.state.center.lat,
longitude: this.state.center.lng,
};
const distanceArr = geolib.orderByDistance(currentLocation, [itemGeolocation]);
const miles = (distanceArr[0].distance / 1609.34).toFixed(2);
if (miles <= userRadius) {
const loc = new google.maps.LatLng(itemGeolocation.lat, itemGeolocation.lng);
bounds.extends(loc);
markers.push({
_id: item._id,
position: itemGeolocation,
number: i,
content: itemDescription,
price: item.price,
quantity: item.quantity,
currency: item.currency,
category: item.category,
title: item.title,
offer: item.offer,
thumbnail: itemThumb,
showInfo: item.showInfo || false,
});
}
}
});
this.setState({
bounds,
});
return markers;
}
radiusChange(event) {
console.log(event.target.value);
this.setState({
radius: event.target.value,
});
const { filter } = this.props.offers;
filter.radius = event.target.value;
this.props.sortOffersNew(filter);
}
zoomChange(e) {
console.log('value', e.target.value);
this.setState({ zoom: Number(e.target.value) });
}
render() {
const markers = this.filterItemsByRadius(this.state.radius);
return (
<Col xs={12} smOffset={0} mdOffset={0}>
<div>
<div style={{ fontFamily: 'Roboto', fontStyle: 'normal' }}>
Offers within radius of: {' '}
<input type="text" defaultValue={this.state.radius} onChange={this.radiusChange} /> {' '}
miles
<br />
</div>
{/* <CreateRadius
radiusChange={this.radiusChange}
numOffers={markers.length}
initRadius={this.state.zoom}
/> */}
</div>
<br />
<div
style={{
width: '100%',
height: '500px',
}}
>
<GeolocationGoogleMap
googleMapURL={googleMapURL}
loadingElement={<div style={{ height: '100%' }} />}
containerElement={<div style={{ height: '100%' }} />}
mapElement={<div style={{ height: '100%' }} />}
center={this.state.center}
showCenterInfo={this.state.showCenterInfo}
content={this.state.content}
radius={this.state.radius}
onMapClick={this.handleMapClick}
onMarkerClick={this.handleMarkerClick}
onCloseClick={this.handleCloseClick}
markers={markers}
zoom={this.state.zoom}
bounds={this.state.bounds}
/>
</div>
</Col>
);
}
}
function mapStateToProps({ browser, offers }) {
return { browser, offers };
}
const dispatchToProps = dispatch => ({
sortOffersNew: filter => dispatch(offerActions.sortOffers(filter)),
});
export default connect(mapStateToProps, dispatchToProps)(
geolocated({
positionOptions: {
enableHighAccuracy: false,
},
userDecisionTimeout: 5000,
})(OfferMap),
);
You can access fitBounds by adding a ref to the DOM element that google-maps-react generates.
first, add a ref:
<GoogleMap ref={(ref) => { this.map = ref; }}>...</GoogleMap>
Once the component is mounted, you will be able to call fitBounds using the ref.
this.map.fitBounds(bounds)
You can assign a ref to the GoogleMap Component, then call the fitBounds() function using a lifecycle hook.
import React, { useRef, useEffect } from 'react';
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from "react-google-maps";
import { GOOGLE_API_KEY } from '../../config/config';
// =======================================================================
// GOOGLE MAPS
// =======================================================================
const RegularMap = withScriptjs(
withGoogleMap(
({ defaultCenter, markers }) => {
const mapRef = useRef(null);
// Fit bounds function
const fitBounds = () => {
const bounds = new window.google.maps.LatLngBounds();
markers.map(item => {
bounds.extend(item.position);
return item.id
});
mapRef.current.fitBounds(bounds);
};
// Fit bounds on mount, and when the markers change
useEffect(() => {
fitBounds();
}, [markers]);
return (
<GoogleMap ref={mapRef} defaultCenter={defaultCenter}>
{markers.map(
({ position }, index) => <Marker key={`marker_${index}`} position={position} />
)}
</GoogleMap>
);
})
);
// =======================================================================
// THE MAIN COMPONENT
// =======================================================================
const MapView = () => {
const markers = [
{ position: { lat: 37.778519, lng: -122.405640 } },
{ position: { lat: 6.4454594, lng: 3.449074 } }
];
return (
<RegularMap
defaultCenter={{ lat: 6.4454594, lng: 3.449074 }}
markers={markers}
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}`}
loadingElement={<div className="loader" />}
containerElement={<div className="mapContainer" style={{ height: "400px" }} />}
mapElement={<div className="map" style={{ height: '100%' }} />}
/>
);
}
export default MapView;
You can use fitbounds in componentDidMount or render function
add ref to GoogleMap and apply fitbounds
<GoogleMap ref={map => map && map.fitBounds(bounds)}> .... </GoogleMap>
The example demonstrates how to center viewport for the given markers:
const MapWithAMarkers = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=AIzaSyC4R6AN7SmujjPUIGKdyao2Kqitzr1kiRg",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
lifecycle({
componentWillMount() {
this.setState({
zoomToMarkers: map => {
//console.log("Zoom to markers");
const bounds = new window.google.maps.LatLngBounds();
map.props.children.forEach((child) => {
if (child.type === Marker) {
bounds.extend(new window.google.maps.LatLng(child.props.position.lat, child.props.position.lng));
}
})
map.fitBounds(bounds);
}
})
},
}),
withScriptjs,
withGoogleMap
)(props =>
<GoogleMap ref={props.zoomToMarkers} defaultZoom={5} defaultCenter={{ lat: 25.0391667, lng: 121.525 }}>
{props.markers.map(marker => (
<Marker
key={marker.id}
position={{ lat: marker.lat, lng: marker.lng }}
/>
))}
</GoogleMap>
);
Demo
I wanted to restrict my map (https://developers.google.com/maps/documentation/javascript/examples/control-bounds-restriction).
I used the options property with GoogleMap component with https://www.npmjs.com/package/#react-google-maps/api library.
The library is complete re-write of the react-google-maps library
CODE:
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
zoom={10}
ref={(ref) => {this.state.map = ref}}
onLoad={this.onGoogleMapLoad}
options={{
restriction: {
latLngBounds: {
north: 19.137384, // Mumbai
south: 18.510866, // Pune
west: 72.874748, // Mumbai
east: 73.879864, // Pune
},
strictBounds: false,
}
}}
>

Resources