I am creating an app that uses wildfire data from APIs and flips it in order to display polygons on a Google Map using the google-maps-react package. I have figured everything out up until returning and displaying the polygon using a function built into the map component. Does anyone want to chime in on what the issue might be? I'd really appreciate some help. Thanks.
import React, { Component } from 'react';
import { Map, GoogleApiWrapper, Polygon } from 'google-maps-react';
const mapStyles = {
margin: 30,
width: '93.75%',
height: '90%',
border: '1px solid #3E1C18',
display: 'inline-block'
};
class FireMap extends Component {
constructor(props) {
super(props)
this.state = {
fires: [],
polygons: []
}
}
componentDidMount() {
fetch('https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Public_Wildfire_Perimeters_View/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json')
.then(res => res.json())
.then(data => {
this.setState({ fires: data.features })
this.state.fires.map((fire) =>{
if (fire.geometry !== null){
let fireCoords = fire.geometry.rings
let trueCoords = []
fireCoords.map((coords) => {
coords.map((pair) => {
let newPair = {lat: pair[1], lng: pair[0]}
return trueCoords.push(newPair)
})
})
this.state.polygons.push(trueCoords);
console.log(this.state.polygons)
}
})
})
}
showPolygons = () => {
this.state.polygons.map((polygon) => {
let firePoly= <Polygon paths={polygon} options={{
fillColor: "#BF5E4B",
fillOpacity: 0.45,
strokeColor: "#6B352A",
strokeOpacity: 0.9,
strokeWeight: 1
}}/>
return firePoly
})
}
render(){
return(
<div className="mapBox">
<Map
google={this.props.google}
zoom={8}
style={mapStyles}
initialCenter={{ lat: 37.7749, lng: -122.4149 }}
>
{this.showPolygons()}
</Map>
</div>
);
}
}
I haven't caught from your comment if you've tried things I've suggested... But you've edited your post with some random (and incorrect) changes. OK, I'll try to post an answer. It's an answer related to the original code because it looks simpler and contains less errors.
This is how I think it should look like:
const coord_pair_to_latlng = ([lat,lng]) => ({ lat, lng })
const convert_ring_coords = ring => ring.map(coord_pair_to_latlng)
class FireMap extends Component {
constructor(props) {
super(props)
this.state = { fires: [] }
}
componentDidMount() {
fetch('https://services3.arcgis.com/T4QMspbfLg3qTGWY/arcgis/rest/services/Public_Wildfire_Perimeters_View/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json')
.then(res => res.json())
.then(data => this.setState({ fires: data.features }))
}
displayFires = () => this.state.fires
.filter(fire => fire.geometry !== null)
.map(fire => fire.geometry.rings)
.map(rings => <Polygon
paths = { rings.reduce((acc, ring) => acc.concat(convert_ring_coords(ring)), []) }
fillColor = "#BF5E4B"
fillOpacity = {0.45}
strokeColor = "#6B352A"
strokeOpacity = {0.9}
strokeWeight = {1}
/>)
render() {
return (
<div className="mapBox">
<Map
google = {this.props.google}
zoom = {8}
style = {mapStyles}
initialCenter = {{ lat: 37.7749, lng: -122.4149 }}
>
{this.displayFires()}
</Map>
</div>
)
}
}
Except for a more functional code style (which you can ignore), basically all I've changed was:
new Polygon() and <Polygon> is not the same thing. You should return the latter. JSX translates to something like React.createElement(Polygon,...) not to new Polygon(...). OK, you've fixed that.
As per docs and per source code, Polygon should be created as
<Polygon
paths = {coords}
fillColor = "#BF5E4B"
fillOpacity = {0.45}
strokeColor = "#6B352A"
strokeOpacity = {0.9}
strokeWeight = {1}
/>
and not as
<Polygon
paths = {coords}
options = {{...}}
/>
your options are ignored
this.displayFires() in componentDidMount does nothing, so it should be removed.
As a side note: also at the time of this.displayFires() call this.state is not changed yet. But it shouldn't change the outcome because, as I said, this.displayFires() have no effect in componentDidMount... but this.state.polygons.push in your new version of code can have an effect... or I'd better say will introduce bugs. You should never do that.
Related
I'm trying to add a similar feature (an interactive map with popups) to a react application I'm working on. I'm trying to change this into a function component with hooks but have been having some problems (specifically with handleResults and addMarker). I'm relatively new to coding, sorry if this seems really trivial.
import React, { Component } from 'react';
import ReactMapGl, {Marker, Popup} from 'react-map-gl'
import Geocoder from 'react-map-gl-geocoder'
import 'mapbox-gl/dist/mapbox-gl.css';
import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css'
const mapboxToken = 'Your API Key'
class Map3 extends Component {
constructor() {
super()
this.state = {
viewport: {
width: '100vw',
height: '100vh',
latitude: 49.28,
longitude: -123.12,
zoom: 14
},
currMarker: null,
markers: [],
selectedMarker: null
}
this.handleViewportChange = this.handleViewportChange.bind(this)
this.handleGeocoderViewportChange = this.handleGeocoderViewportChange.bind(this)
this.handleResult = this.handleResult.bind(this)
this.addMarker = this.addMarker.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleMarkerClick = this.handleMarkerClick.bind(this)
}
mapRef = React.createRef()
handleViewportChange(viewport) {
this.setState(prevState => ({
viewport: {...prevState.viewport, ...viewport}
}))
}
handleGeocoderViewportChange(viewport) {
const geocoderDefaultOverrides = {transitionDuration: 1000}
return this.handleViewportChange({
...viewport,
...geocoderDefaultOverrides
})
}
handleResult (result) {
this.setState({
currMarker: {
name: result.result.place_name,
latitude: result.result.center[1],
longitude: result.result.center[0]
}
})
}
addMarker() {
const {currMarker} = this.state
this.setState(prevState => ({
markers: [...prevState.markers, currMarker],
currMarker: null
}))
}
handleMarkerClick(marker) {
this.setState({
selectedMarker: marker
})
}
handleClose = () => {
this.setState({
selectedMarker: null
})
}
render() {
const {viewport, markers, selectedMarker} = this.state
return (
<ReactMapGl
ref={this.mapRef}
{...viewport}
onViewportChange={viewport => this.setState({viewport})}
mapboxApiAccessToken={mapboxToken}
mapStyle="mapbox://styles/mapbox/streets-v10"
>
<button className="add-btn" onClick={this.addMarker}>Add</button>
{markers.map((marker, idx) => {
return (
<Marker
key={idx}
latitude={marker.latitude}
longitude={marker.longitude}
onClick={() => this.handleMarkerClick(marker)}
>
<img src="pin.png" alt="marker"/>
</Marker>
)
})
}
<Geocoder
onSelected={this.handleResult}
mapRef={this.mapRef}
placeholder="Search here!"
onViewportChange={this.handleGeocoderViewportChange}
onResult={this.handleResult}
mapboxApiAccessToken={mapboxToken}
position="top-right"
/>
{this.state.selectedMarker &&
<Popup
mapRef={this.mapRef}
latitude={selectedMarker.latitude}
longitude={selectedMarker.longitude}
closeButton={true}
closeOnClick={false}
onClose={this.handleClose}
>
<h3>{selectedMarker.name}</h3>
</Popup>
}
</ReactMapGl>
)
}
}
export default Map3;
I'm trying to adapt this example from https://github.com/mapbox/mapbox-react-examples/tree/master/basic,
import React from 'react'
import ReactDOM from 'react-dom'
import mapboxgl from 'mapbox-gl'
mapboxgl.accessToken = 'pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4M29iazA2Z2gycXA4N2pmbDZmangifQ.-g_vE53SD2WrJ6tFX7QHmA';
class Application extends React.Component {
constructor(props: Props) {
super(props);
this.state = {
lng: 5,
lat: 34,
zoom: 1.5
};
}
componentDidMount() {
const { lng, lat, zoom } = this.state;
const map = new mapboxgl.Map({
container: this.mapContainer,
style: 'mapbox://styles/mapbox/streets-v9',
center: [lng, lat],
zoom
});
map.on('move', () => {
const { lng, lat } = map.getCenter();
this.setState({
lng: lng.toFixed(4),
lat: lat.toFixed(4),
zoom: map.getZoom().toFixed(2)
});
});
}
render() {
const { lng, lat, zoom } = this.state;
return (
<div>
<div className="inline-block absolute top left mt12 ml12 bg-darken75 color-white z1 py6 px12 round-full txt-s txt-bold">
<div>{`Longitude: ${lng} Latitude: ${lat} Zoom: ${zoom}`}</div>
</div>
<div ref={el => this.mapContainer = el} className="absolute top right left bottom" />
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
to a case in which, rather than displaying the map's center, I would like to display the latitude and longitude of the mouse position.
So far, I've managed to simply log it to the console:
import React from 'react';
import mapboxgl from 'mapbox-gl';
mapboxgl.accessToken = 'pk.eyJ1Ijoia3VydHBlZWsiLCJhIjoiY2p6cnVneWdvMHlzeDNqcWo0dm83ZzZ2eiJ9.yUCSreTRcKs12uT5PTCztg';
export default class Map extends React.Component {
componentDidMount() {
this.map = new mapboxgl.Map({
container: this.mapContainer,
style: 'mapbox://styles/mapbox/outdoors-v11',
center: [-119.5591, 37.715],
zoom: 9
});
this.map.on('load', function(e) {
e.target.on('mousemove', function(e) {
console.log(JSON.stringify(e.point));
console.log(JSON.stringify(e.lngLat.wrap()));
});
});
}
componentWillUnmount() {
this.map.remove();
}
render() {
const style = {
position: 'absolute',
top: 0,
bottom: 0,
width: '100%'
};
return <div style={style} ref={el => this.mapContainer = el} />;
}
}
This writes lines like the following to the console:
{"x":972,"y":272}
{"lng":-118.90266689452113,"lat":37.86205552587528}
However, rather than logging the coordinates to the console, I would like to invoke this.setState() like in the example so that I can render the coordinates in a child component.
The problem is, within the on('mousemove', ...) callback function, this is not the component. I've read about using arrow functions (which are lexically scoped) to work around this, but it seems to me that in this case, I need a 'normal' function(e) in order to capture the event.
How can I setState() with the mouse coordinates in this example?
It's possible to use arrow functions just like any other function
this.map.on('load', e => {
e.target.on('mousemove', e => {
this.setState({}) //correct this
})
})
I'm trying to change the CSS of Google Maps Markers in React when one hovers over the marker, but I am really struggling on how to do it.
I have tried messing with the 'event' object extensively to no avail. I have also tried adding an id to the Marker, but this doesn't actually do anything.
position={{
lat: entry.lat,
lng: entry.lng,
}}
animation={window.google.maps.Animation.DROP}
onClick={(event) => window.open(
'https://blah.com/post/' + entry.id, '_blank'
)
}
onMouseOver={(event) => console.log(event)}
/>
I just want the Marker to change in size when the user hovers over it. No page reloading, none of that.
Please take a look at the code below as an example of a marker's mouse over event in React. It's based on this code from this tutorial I've just come across.
Hopefully it helps point you in the right direction and give you ideas on how this feature can be implemented (just one of many different ways). In this case I've achieved the effect you've described by changing the marker's icon's scaledSize.
I also recommend you go through Google's documentation on markers.
map.js
class Map extends Component {
constructor(props) {
super(props);
this.onScriptLoad = this.onScriptLoad.bind(this)
}
onScriptLoad() {
const map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options);
this.props.onMapLoad(map)
}
componentDidMount() {
if (!window.google) {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = `https://maps.google.com/maps/api/js?key=`;
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
s.addEventListener('load', e => {
this.onScriptLoad()
})
} else {
this.onScriptLoad()
}
}
render() {
return (
<div style={{ width: 500, height: 500 }} id={this.props.id} />
);
}
}
export default Map
index.js
class App extends Component {
constructor() {
super();
}
render() {
return (
<Map
id="myMap"
options={{
center: { lat: 41.0082, lng: 28.9784 },
zoom: 8
}}
onMapLoad={map => {
const marker = new window.google.maps.Marker({
position: { lat: 41.0082, lng: 28.9784 },
map: map,
icon: {
url: "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png", size: new google.maps.Size(20, 32), scaledSize: new google.maps.Size(20, 32), origin: new google.maps.Point(0, 0)
}
});
marker.addListener('mouseover', e => {
marker.setIcon({ url: "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png", scaledSize: new google.maps.Size(30, 42) })
})
marker.addListener('mouseout', e => {
marker.setIcon({ url: "https://developers.google.com/maps/documentation/javascript/examples/full/images/beachflag.png", scaledSize: new google.maps.Size(20, 32) })
})
}}
/>
);
}
}
render(<App />, document.getElementById('root'));
Hope this helps!
I have an SVG map with paths, and those paths change colors when I hover over them.
It changes state of specific section, for example my state looks like that:
POL3139: {
color: '#fbb9c5'
},
I am trying to switch back to the base color after I leave the path.
Here I am changing
onHover = (event) => {
event.stopPropagation();
const e = event.target.id
this.setState(prevState => ({
[e]: {
...prevState,
color: '#650df9'
},
}));
}
It totally works and changes my color to the picked one.
But then I am trying to revert back to the original one.
I tried that by making a base color in the state:
POL3139: {
color: '#fbb9c5',
base: '#fbb9c5'
},
and then onMouseLeave:
onLeave = (event) => {
event.stopPropagation();
const e = event.target.id;
this.setState(prevState => ({
[e]: {
...prevState,
// color: prevState.base - doesn't work
// color: prevState.[e].base - doesn't work
// color: [prevState.e.base] - doesn't work
color: 'pink'
}
}));
}
I was trying many possible solutions but I can't get it to work.
I am still learning react and it might be an easy one but I can't figure it out.
I don't think you're deconstructing your prevState properly.
Here's an example to illustrate how to deconstruct:
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = {
style: {
color: "black",
base: "black",
cursor: "pointer"
}
};
handleMouseEnter = e => {
const { style } = this.state;
this.setState({ style: { ...style, color: "red" } });
};
handleMouseLeave = e => {
const { style } = this.state;
this.setState({ style: { ...style, color: style.base } });
};
render() {
return (
<div
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={this.state.style}
>
<h1>hello</h1>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In this case style is equivalent to your [e].
In particular, look at the deconstructing here:
this.setState({ style: { ...style, color: style.base } });
There's a working example here.
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);
});
}