Limiting users to one polygon at a time with Google Maps - javascript

I have a React-Google-Maps component which allows users to draw polygons and passes those polygons' associated GeoJSON up to the parent component through a callback.
What I would like to do is to limit users to one polygon at a time; meaning, when a user completes a polygon, all previously rendered polygons are deleted.
I've seen some examples on how to do this with vanilla JS, jQuery and Angular 2, but nothing on React.
My component:
import React, { Component } from 'react';
const { compose, withProps } = require('recompose');
const { withScriptjs, withGoogleMap, GoogleMap } = require('react-google-maps');
const {
DrawingManager
} = require('react-google-maps/lib/components/drawing/DrawingManager');
const editTrack = polygon => {
let GeoJSON = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: []
},
properties: {}
};
for (let point of polygon.getPath().getArray()) {
GeoJSON.geometry.coordinates.push([point.lng(), point.lat()]);
}
return GeoJSON;
};
const PlotMap = compose(
withProps({
googleMapURL:
'https://maps.googleapis.com/maps/api/js?key=mykey&v=3.exp&libraries=geometry,drawing,places',
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap
defaultZoom={8}
defaultCenter={new google.maps.LatLng(32.095, 35.398)}>
<DrawingManager
onPolygonComplete={polygon => {
polygon.setEditable(true);
props.getGeoJSON(editTrack(polygon));
google.maps.event.addListener(polygon.getPath(), 'insert_at', function(
index,
obj
) {
props.getGeoJSON(editTrack(polygon));
});
google.maps.event.addListener(polygon.getPath(), 'set_at', function(
index,
obj
) {
props.getGeoJSON(editTrack(polygon));
});
}}
defaultDrawingMode={google.maps.drawing.OverlayType.POLYGON}
defaultOptions={{
drawingControl: true,
drawingControlOptions: {
style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [google.maps.drawing.OverlayType.POLYGON]
}
}}
/>
</GoogleMap>
));
export default PlotMap;

OK, got it.
import React, { Component } from 'react';
const { compose, withProps } = require('recompose');
const { withScriptjs, withGoogleMap, GoogleMap } = require('react-google-maps');
const {
DrawingManager
} = require('react-google-maps/lib/components/drawing/DrawingManager');
const editTrack = polygon => {
let GeoJSON = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[]]
},
properties: {}
};
for (let point of polygon.getPath().getArray()) {
GeoJSON.geometry.coordinates[0].push([point.lng(), point.lat()]);
}
GeoJSON.geometry.coordinates[0].push(GeoJSON.geometry.coordinates[0][0]);
return GeoJSON;
};
//this is where we will keep our polygon when it is drawn
let latestPolygon;
const PlotMap = compose(
withProps({
googleMapURL:
'https://maps.googleapis.com/maps/api/js?key=mykey&v=3.exp&libraries=geometry,drawing,places',
loadingElement: <div style={{ height: `100%`, width: `100%` }} />,
containerElement: <div style={{ height: `400px`, width: `100%` }} />,
mapElement: <div style={{ height: `100%`, width: `100%` }} />
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap
defaultZoom={8}
defaultCenter={new google.maps.LatLng(32.095, 35.398)}>
<DrawingManager
onPolygonComplete={polygon => {
//if we have a polygon on the map, delete it now
latestPolygon && latestPolygon.setMap(null);
polygon.setEditable(true);
props.getGeoJSON(editTrack(polygon));
google.maps.event.addListener(polygon.getPath(), 'insert_at', function(
index,
obj
) {
props.getGeoJSON(editTrack(polygon));
});
google.maps.event.addListener(polygon.getPath(), 'set_at', function(
index,
obj
) {
props.getGeoJSON(editTrack(polygon));
});
//now we set the storage polygon to be the one we just drew
latestPolygon = polygon;
}}
defaultDrawingMode={google.maps.drawing.OverlayType.POLYGON}
defaultOptions={{
drawingControl: true,
drawingControlOptions: {
style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [google.maps.drawing.OverlayType.POLYGON]
}
}}
/>
</GoogleMap>
));
export default PlotMap;

Related

Don't want to re-render even if the state is changed in react-google-maps

I am using an application that displays a Google map with react Google Maps have multiple pins installed, and the state changes by scrolling, and the active flight changes according to the state.
At that time, the center of the Google map is set to be an activity, but the Google map is re-rendered when the state changes. I don't know how to prevent rendering.
  Google Maps has NPM library. It uses react-google-maps and is implemented using hooks. I tried to return false with useEffect (), but I didn't hear it as it is. Please tell me
MapComponent(HOC)
import React from "react";
import { withGoogleMap, GoogleMap, withScriptjs, Marker, InfoWindow } from "react-google-maps";
import { compose, withProps, withHandlers, withStateHandlers } from "recompose";
const MapWithPlaces = compose(
withProps({
googleMapURL:
`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_PLACE_API_KEY}&libraries=geometry,drawing,places`,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: "400px", width: "100%" }} />,
mapElement: <div style={{ height: "100%" }} />
}),
withStateHandlers(
props => ({
infoWindows: props.places.map(p => {
return { isOpen: false };
}),
defaultCenter: { 'lat': props.lat, 'lng': props.lng }
}),
{
onToggleOpen: ({ infoWindows }) => selectedIndex => ({
infoWindows: infoWindows.map((iw, i) => {
iw.isOpen = selectedIndex === i;
return iw;
})
})
}
),
withHandlers(() => {
const refs = {
map: undefined,
}
console.log(refs);
return {
onMapMounted: () => ref => {
refs.map = ref
},
onZoomChanged: ({ onZoomChange }) => (props) => {
const center = { 'lat': parseFloat(props.lat, 10), 'lng': parseFloat(props.lng, 10) }
refs.map.pantTo(center)
}
}
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap defaultZoom={props.zoom} defaultCenter={props.center} key={props.key} ref={map}>
{props.places &&
props.places.map((place, i) => {
let lat = parseFloat(place.lat, 10);
let lng = parseFloat(place.lng, 10);
return (
<Marker
id={place.id}
key={place.key}
position={{ lat: lat, lng: lng }}
title={place.name}
onClick={props.onToggleOpen.bind(this, i)}
opacity={place.key === props.step ? 1 : 0.5}
label={place.day === props.currentDay ? place.dayIndex.toString() : ''}
>
{props.infoWindows[i].isOpen && (
<InfoWindow onCloseClick={props.onToggleOpen.bind(i)}>
<div>{place.name}</div>
</InfoWindow>
)}
</Marker>
);
})}
</GoogleMap>
));
export default MapWithPlaces;
MapComponent(hooks)
import React, { useState, useEffect, useRef } from "react";
import { withGoogleMap, withScriptjs, GoogleMap, Marker, InfoWindow } from "react-google-maps";
// import mapStyles from "./mapStyles";
const MapCreate = React.memo((props) => {
// const [selectedPark, setSelectedPark] = useState(null);
const mapRef = useRef()
useEffect(() => {
console.log("props updates")
console.log(props);
const mapCenter = {
lat: parseFloat(props.places[props.step].lat, 10),
lng: parseFloat(props.places[props.step].lng, 10)
}
return false
// refMap.current.panTo(mapCenter) //move the map to new location
}, [props]);
return (
<GoogleMap defaultZoom={14} center={{ lat: props.center.lat, lng: props.center.lng }} ref={mapRef}>
{props.places && props.places.map((place, i) => {
let lat = parseFloat(place.lat, 10);
let lng = parseFloat(place.lng, 10);
return (
<Marker
id={place.id}
key={place.key}
position={{ lat: lat, lng: lng }}
title={place.name}
opacity={place.key === props.step ? 1 : 0.5}
label={place.day === props.currentDay ? place.dayIndex.toString() : ''}
>
</Marker>
)
})}
</GoogleMap>
)
})
const MapWrapped = withScriptjs(withGoogleMap(MapCreate));
export default function Map(props) {
const mapRef = useRef(null)
return (
<div style={{ width: "100%", height: "400px" }}>
<MapWrapped
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=${process.env.REACT_APP_GOOGLE_PLACE_API_KEY}`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: "400px", width: "100%" }} />}
mapElement={<div style={{ height: `100%` }} />}
{...props}
/>
</div>
);
}
Try #shouldcomponentupdate
shouldComponentUpdate(nextProps, nextState)
Use shouldComponentUpdate() to let React know if a component’s output
is not affected by the current change in state or props. The default
behavior is to re-render on every state change, and in the vast
majority of cases you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or
state are being received. Defaults to true. This method is not called
for the initial render or when forceUpdate() is used

I can not understand why the button 'nearby place' does not work

in my code there is a button that is responsible for displaying markers on the map of the nearest places from your location, and when I click on it, I safely get the data of places, but the markers do not appear and the rest of the visual part (creating markers when clicking on the map) also stops working
homepage.jsx
import React, { Component } from 'react';
import './homepage.less';
import { connect } from 'react-redux';
import { saveMarkers, setMarker } from '../../actions/index';
import { Link } from 'react-router-dom';
import { GoogleMap, withScriptjs, withGoogleMap } from 'react-google-maps';
import MapComponent from '../mapComponent/mapComponent';
import store from '../../store';
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
zoom: 5,
selectedValue: 'school',
currentLatLng: {
lat: 0,
lng: 0,
},
marks: [],
loaded: this.props.loaded,
};
this.getLoadedMark = this.getLoadedMark.bind(this);
this.selectedChange = this.selectedChange.bind(this);
this.placeMarkerLoad = this.placeMarkerLoad.bind(this);
this.callback = this.callback.bind(this);
this.setMark = this.setMark.bind(this);
}
componentDidMount() {
this.getGeoLocation();
console.log(this.state.marks);
}
selectedChange(e) {
this.setState({
selectedValue: e.target.value,
});
console.log(this.state.selectedValue);
}
setMark = e => {
this.setState({
marks: [
...this.state.marks,
{ lat: e.latLng.lat(), lng: e.latLng.lng(), show: true },
],
});
};
getLoadedMark() {
this.setState({
marks: [...this.state.marks, ...this.state.loaded],
});
}
getGeoLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(position => {
this.setState(prevState => ({
currentLatLng: {
...prevState.currentLatLng,
lat: position.coords.latitude,
lng: position.coords.longitude,
},
}));
});
} else {
console.log('error');
}
};
placeMarkerLoad() {
/*global google*/
let center = new google.maps.LatLng(
this.state.currentLatLng.lat,
this.state.currentLatLng.lng
);
let map = new google.maps.Map(document.getElementById('map'), {
center: center,
zoom: 15,
});
let options = {
location: center,
radius: 1500,
type: this.state.selectedValue,
};
let service = new google.maps.places.PlacesService(map);
service.nearbySearch(options, this.callback);
console.log('GOT IT', options);
}
callback(results = google.maps.places.PlaceResult) {
for (let i = 1; i < results.length; i++) {
this.setState({
marks: [
...this.state.marks,
{
lat: results[i].geometry.location.lat(),
lng: results[i].geometry.location.lng(),
show: true,
},
],
});
}
console.log('RESULTS', results);
}
render() {
const { marks } = this.state;
return (
<div>
<div className="HomePage">
<Link to={`/`}>
<div className="Home">home</div>
</Link>
<Link to={`/about`}>
<div className="aboutAuthor">about</div>
</Link>
<Link to={`/authorization`}>
<div className="authorization">authorization</div>
</Link>
</div>
<button onClick={() => this.props.saveMarkers(this.state.marks)}>
save
</button>
<button onClick={this.getLoadedMark}>load</button>
<button
onClick={() => {
console.log(this.state.marks);
}}
>
state
</button>
<button className="map__button" onClick={this.placeMarkerLoad}>
nearby place
</button>
<select
className="map__button"
name="Object types"
id="1"
value={this.state.selectedValue}
onChange={this.selectedChange}
>
<option value="school">School</option>
<option value="restaurant">Restaurant</option>
<option value="pharmacy">Pharmacy</option>
<option value="gas_station">Gas station</option>
<option value="bank">Bank</option>
<option value="gym">Gym</option>
<option value="hospital">Hospital</option>
<option value="travel_agency">Travel agency</option>
<option value="supermarket">Supermarket</option>
</select>
<div
className="map"
id="map"
style={{ width: '100vw', height: '100vh' }}
>
<MapComponent
currentLocation={this.state.currentLatLng}
onMapClick={this.setMark}
currentZoom={this.state.zoom}
marks={marks}
/>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
loaded: state.markers,
};
};
const mapDispatchToProps = {
saveMarkers,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomePage);
and mapComponent.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { compose, withProps } from 'recompose';
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
Circle,
} from 'react-google-maps';
const MapComponent = compose(
withProps({
googleMapURL:
'https://maps.googleapis.com/maps/api/js?key=AIzaSyCZ7z51hSWmXbLca5zETwnu_dYuW8CbtgM&v=3.exp&libraries=geometry,drawing,places',
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap
defaultZoom={props.currentZoom}
defaultCenter={{
lat: props.currentLocation.lat,
lng: props.currentLocation.lng,
}}
onClick={e => props.onMapClick(e)}
>
{props.marks.map((mark, index) => (
<Circle
key={index}
center={mark}
radius={41000}
options={{
strokeColor: '#66009a',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: `#66009a`,
fillOpacity: 0.35,
zIndex: 1,
}}
/>
))}
</GoogleMap>
));
export default MapComponent;
codesandbox:https://codesandbox.io/s/github/lesha107/map

How to update React Google Map after receive data from server

How to update ReactGoogleMap after receiving data from the server?
This is how I create Google Map using react-google-maps API.
import React from "react"
import { compose, withProps } from "recompose"
import { withScriptjs, withGoogleMap, GoogleMap, Marker , Polyline} from "react-google-maps"
import TextField from "#material-ui/core/TextField";
import Button from "components/CustomButtons/Button.jsx";
import './Maps.css';
import axios from "axios";
const pathCoordinates = [
{ lat: 1.322459, lng: 103.853972 },
{ lat: 1.39227, lng: 103.752 }
];
const MyMapComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=3.exp&libraries=geometry,drawing",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `80vh` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap
)((props) =>
<GoogleMap
defaultZoom={12}
defaultCenter={{ lat: 1.322459, lng: 103.853972 }}
>
<div id='map_controls' class='toolbox'>
<TextField
id="date"
label="Date"
type="date"
defaultValue= ""
InputLabelProps={{
shrink: true,
style: { color: '#fff' }
}}
value={props.date}
onChange={props.handleChange('date')}
/>
<Button color="primary" onClick={props.handleSubmit}>Select Date</Button>
</div>
{console.log("drawing...............")}
{props.drawMap()}
</GoogleMap>
)
const initialState = {
date: "",
data: []
};
class Maps extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.drawMap = this.drawMap.bind(this);
}
componentDidMount() {
console.log(" componentDidMount "+this.state.date);
}
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
handleSubmit(event) {
const input = {};
input["date"] = this.state.date;
axios.post("localhost:4000/readMap", input)
.then(response => {
console.log({response});
this.setState({data: response.data});
})
.catch(error => {console.log(error.response)});
event.preventDefault();
}
drawMap(){
if(!(this.state.date==="")){
var rows = [];
for (var i = 0; i < 2; i++) {
if(i===0){
rows.push(<Marker label= {(i+1).toString()} position={{ lat: 1.39227, lng: 103.752 }} key={i} />);
}else if(i===1){
rows.push(<Marker label= {(i+1).toString()} position={{ lat: 1.322459, lng: 103.853972 }} key={i} />);
}else{
}
}
return (<div>{rows}<Polyline
path={pathCoordinates}
geodesic={true}
options={{
strokeColor: "#ff2527",
strokeOpacity: 0.75,
strokeWeight: 2
}}
/></div>);
}else{
console.log("no date");
}
}
render() {
this.drawMap();
return (
<MyMapComponent
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
drawMap={this.drawMap}
/>
)
}
}
export default Maps;
I want to draw the marker after I receive data from the database. I successfully read the input(which is a date) from the user and send this data to a server to get the data of this date from the database. Data is successfully received and able to print it in the console. However, I have no idea how to update MyMapComponent to display the marker of this date. I run drawMap() function again after I get the data but the MyMapComponent is not updated after I run drawMap().
I found the solution.
render() {
console.log("render::::::::::::::::::::");
return (
<MyMapComponent
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
drawMap={this.drawMap}
>
{this.drawMap()}
</MyMapComponent>
)
}

When displaying multiple markers on a map, how to open just one info window, when clicking on a marker?

I'm using react-google-maps to display a map with markers, and when you click on a marker, all the info windows open up. I would like to display only one marker's info window when I click on it, and for the others to stay closed.
Here is my code:
<GoogleMap
defaultZoom={15}
defaultCenter={{ lat: 51.508530, lng: -0.076132 }}
>
{props.places && props.places.map((place, i) =>
<Marker onClick={props.onToggleOpen} key={i} position={{ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }} >
{props.isOpen && <InfoWindow onCloseClick={props.onToggleOpen}>
<div>{place.name}</div>
</InfoWindow>}
</Marker>
)}
</GoogleMap>
And I'm opening and closing the InfoWindow with this
import { compose, withProps, withStateHandlers, withHandlers, withState } from "recompose";
...
withStateHandlers(() => ({
isOpen: false,
}), {
onToggleOpen: ({ isOpen, id }) => () => ({
isOpen: !isOpen,
})
}),
I'm mapping over all the markers, and displaying them on the map. How could I click open just one marker InfoWindow?
Here is a related question, but it's not made with React, and doesn't use the react-google-maps.
It's more of a React question. You can pass the index of a clicked Marker to onToggleOpen and instead of isOpen you use a selectedPlace state that holds the index of a clicked Marker and use this index to render the right InfoWindow.
Here is an example (not fully tested, but you can get the idea):
/*global google*/
import React from "react"
import { compose, withProps, withHandlers, withState, withStateHandlers } from "recompose"
import { withScriptjs, withGoogleMap, GoogleMap, Marker, InfoWindow } from "react-google-maps"
const MyMapComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap,
withState('places', 'updatePlaces', ''),
withState('selectedPlace', 'updateSelectedPlace', null),
withHandlers(() => {
const refs = {
map: undefined,
}
return {
onMapMounted: () => ref => {
refs.map = ref
},
fetchPlaces: ({ updatePlaces }) => {
let places;
const bounds = refs.map.getBounds();
const service = new google.maps.places.PlacesService(refs.map.context.__SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED);
const request = {
bounds: bounds,
type: ['hotel']
};
service.nearbySearch(request, (results, status) => {
if (status == google.maps.places.PlacesServiceStatus.OK) {
console.log(results);
updatePlaces(results);
}
})
},
onToggleOpen: ({ updateSelectedPlace }) => key => {
updateSelectedPlace(key);
}
}
}),
)((props) => {
console.log(props);
return (
<GoogleMap
onTilesLoaded={props.fetchPlaces}
ref={props.onMapMounted}
onBoundsChanged={props.fetchPlaces}
defaultZoom={15}
defaultCenter={{ lat: 51.508530, lng: -0.076132 }}
>
{props.places && props.places.map((place, i) =>
<Marker onClick={() => props.onToggleOpen(i)} key={i} position={{ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }}>
{props.selectedPlace === i && <InfoWindow onCloseClick={props.onToggleOpen}>
<div>
{props.places[props.selectedPlace].name}
</div>
</InfoWindow>}
</Marker>
)}
</GoogleMap>
)
})
export default class MyFancyComponent extends React.PureComponent {
render() {
return (
<MyMapComponent />
)
}
}

Pass back coordinates to display in react-google-maps

Below is my code:
const SiteGoogleMap = compose(
withProps({
googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAP_API_KEY}&v=3.exp&libraries=geometry,drawing,places`,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withScriptjs,
withGoogleMap
)((props) =>
<GoogleMap
defaultZoom={18}
defaultCenter={{ lat: 3.1314067, lng: 101.6285082 }}
>
{props.isMarkerShown && <Marker position={{ lat: -34.397, lng: 150.644
}} onClick={props.onMarkerClick} />}
<DrawingManager
defaultDrawingMode={google.maps.drawing.OverlayType.POLYGON}
defaultOptions={{
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
google.maps.drawing.OverlayType.POLYGON
],
}
}}
/>
<Polygon path={coordinates} />
</GoogleMap>
)
I would like to get the coordinates (polygon path) from my server. Below is what I have done to get it. It is a component:
export default class Map extends Component {
state = {
isMarkerShown: false,
coordinates: []
}
componentDidMount() {
this.fetchBoundaries()
}
render () {
const { coordinates } = this.state
return (
<SiteGoogleMap
isMarkerShown={this.state.isMarkerShown}
onMarkerClick={this.handleMarkerClick}
/>
);
}
async fetchBoundaries () {
try {
const { coordinates } = this.state
const item = await service.getBoundaries('59d850878328bd177bf50b4d')
coordinates = item.boundaries
this.setState({ coordinates })
} catch (e) {
notification.show('error', 'Unable to load successfully', 'Unable to load client successfully. Please try again later.')
}
}
}
From the fetchBoundaries() function, I am able to get my coordinates. But, it just can't pass it to <Polygon path={coordinates} /> for display.
Anyone know what went wrong?

Categories