I have this React code:
import React, { Component } from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import './Map.css';
import L from 'leaflet';
import { geolocated } from 'react-geolocated';
class Map extends Component {
render() {
const DEFAULT_LATITUDE = 32.313268;
const DEFAULT_LONGITUDE = 35.022895;
const latitude = this.props.coords ? this.props.coords.latitude : DEFAULT_LATITUDE;
const longitude = this.props.coords ? this.props.coords.longitude : DEFAULT_LONGITUDE;
return (
<MapContainer className="leaflet-map" center={[latitude, longitude]} zoom={17} scrollWheelZoom={true}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[latitude, longitude]}>
<Popup>
Here you are ^_^
</Popup>
</Marker>
</MapContainer>
);
}
}
export default Map;
How can I add an event to get latitude and longitude of the position when the map clicked?
In the leaflet documentation there is something like:
map.on('click', function(e) {
alert("Lat, Lon : " + e.latlng.lat + ", " + e.latlng.lng)
});
But how can I do this with ReactJS?
First take the map instance using whenCreated prop on MapContainer
<MapContainer
className="leaflet-map"
center={[latitude, longitude]}
zoom={17}
scrollWheelZoom={true}
style={{ height: "100vh" }}
whenCreated={(map) => this.setState({ map })}
>
Second use it on componentDidUpdate when it is defined to listen to on map click event
componentDidUpdate(prevProps, prevState) {
const { map } = this.state;
if (prevState.map !== map && map) {
map.on("click", function (e) {
alert("Lat, Lon : " + e.latlng.lat + ", " + e.latlng.lng);
});
}
}
Demo
Related
I am using react leaflet my next.js app.React Leaflet marker is broken see image. I am using custom market but not work. I 've put together a very simple React / Leaflet demo but the marker icon is not showing at all
import { useState, useEffect, useRef } from "react";
import "leaflet/dist/leaflet.css";
import {
MapContainer,
TileLayer,
Marker,
Popup,
useMap,
useMapEvents,
} from "react-leaflet";
import { useSelector } from "react-redux";
import { useCallback } from "react";
import { useMemo } from "react";
function LocationMarker({ lat, lng }) {
const [position, setPosition] = useState(null);
const [bbox, setBbox] = useState([]);
const map = useMap();
useEffect(() => {
map.flyTo([lat, lng], map.getZoom());
}, [lat]);
return position === null ? null : (
<Marker position={position}>
<Popup>You are here</Popup>
</Marker>
);
}
export default function EditAddedMap({ lat, lng, setLat, setLng }) {
const { resturant } = useSelector((state) => state);
return (
<MapContainer center={[lat, lng]} zoom={13} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<LocationMarker lat={lat} lng={lng} />
<DraggableMarker lat={lat} lng={lng} setLng={setLng} setLat={setLat} />
</MapContainer>
);
}
Try to install leaflet-defaulticon-compatibility found here https://www.npmjs.com/package/leaflet-defaulticon-compatibility
And import it accordingly:
import "leaflet-defaulticon-compatibility";
import "leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css";
So basically I was able to get a database which stores points in it.
Now I was able to push each point separately to make this database. Now I need to display these points on the map. This is my code so far:
App.js
import './App.css';
import * as React from "react";
import firebase from "./firebase";
import { ChakraProvider } from "#chakra-ui/react";
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, useMap } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import * as L from 'leaflet';
import SearchBar from './SearchBar';
import CovidPoint from './CovidPoint';
import LocationMarkers from './LocationMarkers';
class App extends React.Component {
constructor(props){
super(props)
this.state = {
map: null,
points: [<CovidPoint
position={[43.653226, -79.3831843]}
name="point1"
information="random point"
input = {false}
></CovidPoint>]
}
}
changePos = (pos, zoom) => {
const {map} = this.state;
if (map) map.flyTo(pos, zoom);
}
fetchPoints = (newPoints) => {
// fetch info from database
const ref = firebase.database().ref("points")
const pointsref = ref.push()
pointsref.set(newPoints)
this.setState({points: newPoints})
}
componentDidMount() {
const ref = firebase.database().ref("points")
ref.on("value", snapshot => {
console.log("FireB ",snapshot)
if (snapshot && snapshot.exists()) {
this.setState({points: snapshot.val})
}})
console.log("page loaded!")
}
render() {
return (
<div className="App">
<div id="title">
<h1>CovidStopSpots</h1>
<p>A responsive tracker for Covid-19.</p>
</div>
<div id="map">
<MapContainer
id="1"
center={[43.653226, -79.3831843]}
zoom={13}
scrollWheelZoom={false}
whenCreated={(map) => this.setState({ map })}
style={{ height: "100vh " }}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{this.state.points.length > 0 && this.state.points.map(
(point, index) => {
<li key={index}></li>
return point
}) }
{/* <CovidPoint
position={[43.653226, -79.3831843]}
name="point1"
information="random point"
></CovidPoint>
<CovidPoint
position={[50.653226, -79.3831843]}
name="point2"
information="random point"
></CovidPoint> */}
<LocationMarkers points={this.state.points} fetchPoints={this.fetchPoints}></LocationMarkers>
<div id="searchbar">
<SearchBar changePos={this.changePos}/>
</div>
</MapContainer>
</div>
</div>
);
}
}
export default App;
The location Marker file which basically "plots" the points and pushes the to the database.
LocationMarker.js
import { useState } from "react";
import { useMapEvents } from "react-leaflet";
import CovidPoint from "./CovidPoint";
function LocationMarkers(props) {
const [position, setPosition] = useState([]);
useMapEvents({
dblclick(ev) {
console.log("double clicked");
const { lat, lng } = ev.latlng;
setPosition([lat, lng]);
const newPoints = [...props.points];
console.log(newPoints);
newPoints.push(<CovidPoint
position={[lat, lng]}
name="test"
information="test"
input = {true}
></CovidPoint>);
props.fetchPoints(newPoints);
}
});
return null;
}
export default LocationMarkers;
As you can see on this example from documentation, you should use Marker to place a marker. Assuming that you have an array of points (list of coordinates), you can place a list of points like this:
const position = [
[51.505, -0.09],
[51.510, -0.09],
[51.515, -0.09]
]
render(
<MapContainer center={position} zoom={13} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{points?.map((point)=>(
<Marker position={point}>
)}
</Marker>
</MapContainer>,
)
I don't know what is this CovidPoint. But I think you dont need it.
I'm trying to render a list of markers in a map using react-native-maps.
I've successfully rendered the map with <MapView /> but for some reason, when rendering a list of markers on the map, it appears only on android and not on IOS.
The problem is while fetching the data, cause when I tried mocking the array of markers it worked correctly.
MapviewWrapper.js:
import React from "react";
import ProvidersList from "./ProvidersList";
import MapView from "react-native-maps";
import { Layout } from "#ui-kitten/components";
import { StyleSheet, Dimensions } from "react-native";
import { mapsConfig } from "config";
const MapviewWrapper = (props) => {
return (
<Layout>
<MapView
style={styles.mapStyle}
initialRegion={mapsConfig.INITIAL_REGION}
>
<ProvidersList />
</MapView>
</Layout>
);
};
const styles = StyleSheet.create({
mapStyle: {
width: Dimensions.get("window").width,
height: Dimensions.get("window").height,
},
});
export default MapviewWrapper;
ProvidersList.js
import React, { useEffect, useState } from "react";
import { Marker } from "react-native-maps";
import { View } from "react-native";
import { mapsConfig } from "config";
import { getProviders } from "api/wrappers/appService";
const getProvidersAsync = (setProviders) => {
getProviders({
Latitude: mapsConfig.INITIAL_REGION.latitude,
Longitude: mapsConfig.INITIAL_REGION.longitude,
Radius: 1000,
MeasureUnit: 0,
}).then((providers) => setProviders(providers));
};
const ProvidersList = (props) => {
const [providers, setProviders] = useState([]);
useEffect(() => {
getProvidersAsync(setProviders);
}, []);
return (
<View>
{providers.length > 0 &&
providers.map((provider, id) => (
<Marker
key={id}
coordinate={{
longitude: provider.Location.Longitude,
latitude: provider.Location.Latitude,
}}
title={provider.Title}
description={provider.Description}
/>
))}
</View>
);
};
export default ProvidersList;
The problem was because i rendered the list of markers inside a view and not a fragment.
The markers now appear on IOS aswell.
Try this way
<MapView
ref={MapView => (this.MapView = MapView)}
style={styles.map}
initialRegion={this.state.region}
loadingEnabled = {true}
loadingIndicatorColor="#666666"
loadingBackgroundColor="#eeeeee"
moveOnMarkerPress = {false}
showsUserLocation={true}
showsCompass={true}
showsPointsOfInterest = {false}
provider="google">
{this.state.markers.map((marker:any) => (
<MapView.Marker
key={marker.id}
coordinate={marker.coordinate}
title={marker.title}
description={marker.description}
/>
}
</MapView>
I'm new to Leaflet/React-Leaflet and I am trying to plot a circle marker on a map and I'm having trouble. React-Leaflet's documentation is not very good. I have figured out how to add a popup that shows on click though.
Here's my code:
import React from 'react';
import { render } from 'react-dom';
import { Map, Marker, Popup, TileLayer, Tooltip, Circle } from 'react-leaflet';
import DivIcon from 'react-leaflet-div-icon';
import axios from 'axios';
export default class MapView extends React.Component {
constructor(props) {
super(props)
this.state = {
nasaLocations: [],
spacexLocations: []
};
}
componentDidMount() {
axios.get(`https://data.nasa.gov/resource/gvk9-iz74.json`)
.then(res => {
const nasaData = res.data;
this.setState({nasaLocations: nasaData})
console.log(this.state.nasaLocations);
})
axios.get(`https://api.spacexdata.com/v2/launchpads`)
.then(res => {
const spacexData = res.data;
this.setState({spacexLocations: spacexData})
console.log(this.state.spacexLocations);
})
}
render() {
const position = [40.730610, -73.935242];
return (
<Map
style={{height: "100vh"}}
center={position}
zoom={3}>
<TileLayer
url="https://api.mapbox.com/styles/v1/nicknyr/cje7mtk2y6gf92snsydobiahf/tiles/256/{z}/{x}/{y}?access_token=pk.eyJ1Ijoibmlja255ciIsImEiOiJjajduNGptZWQxZml2MndvNjk4eGtwbDRkIn0.L0aWwfHlFJVGa-WOj7EHaA"
attribution="<attribution>" />
<Marker position={position}>
<Popup>
<span>
A pretty CSS3 popup. <br /> Easily customizable.
</span>
</Popup>
</Marker>
</Map>
);
}
}
How can I render a circle on those position coordinates? I've attemped to add a type tag within the Map tag but I can't figure out the proper syntax and how to properly add lat/long coordinates.
I have little experience with Leaflet, but I got a circle to render, here is the example. I passed the latlng coords using an obj. Hope this helps. If this didn't answer the question, let me know. Circles show up on each location, with the pop-up showing the details from the space x api.
import React, { Component } from 'react';
import { Map, TileLayer, Marker, Tooltip,Popup, Circle} from 'react-leaflet';
import axios from 'axios';
const url = 'https://api.spacexdata.com/v2/launchpads';
const leafURL = "https://api.mapbox.com/styles/v1/nicknyr/cje7mtk2y6gf92snsydobiahf/tiles/256/{z}/{x}/{y}?access_token=pk.eyJ1Ijoibmlja255ciIsImEiOiJjajduNGptZWQxZml2MndvNjk4eGtwbDRkIn0.L0aWwfHlFJVGa-WOj7EHaA";
class App extends Component {
constructor(props){
super(props);
this.state = {
latlng: {
lat: 28.5618571,
lng: -80.577366,
},
data: []
}
}
componentWillMount() {
axios.get(url).then(res => {
this.setState({data: res.data})
}).catch(err => {
console.log('error')
})
}
render() {
const {data} = this.state;
console.log(data);
return (
<div>
<Map
style={{height: "100vh"}}
center={this.state.latlng}
zoom={4}>
<TileLayer
url={leafURL}
attribution="<attribution>" />
{data.map((elem, i) => {
return (
<Marker
key={i}
position={{lat:elem.location.latitude, lng: elem.location.longitude}}>
<Popup>
<span>
{elem.full_name}<br />
{elem.status}<br />
{elem.details}<br />
{elem.vehicles_launched.map((elem, i) => {
return ( <p key={i}>{elem}</p>)
})}
</span>
</Popup>
<Circle
center={{lat:elem.location.latitude, lng: elem.location.longitude}}
fillColor="blue"
radius={200}/>
</Marker>
)
})}
</Map>
</div>
);
}
}
export default App;
I've got a React Leaflet map, which renders fine.
I've got a list of plots in state, which appear fine (I can see them if I look at the component state.
Each plot has a GeoJSON polygon property.
I've got a custom marker component, which renders differently based on zoom (either a GeoJSON polygon or a marker at the center of the plot's polygon.)
I'm mapping the plot list and instantiating a custom marker component from each one. But this doesn't render any plots.
What am I missing?
The map component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import { Map, TileLayer, LayersControl, MapControl } from 'react-leaflet';
import { GoogleLayer } from './GoogleLayer';
import { geolocated } from 'react-geolocated';
import 'leaflet-geocoder-mapzen';
import SearchBox from './searchBox';
import Control from 'react-leaflet-control';
import PlotMarker from './plotMarker';
import { centroid } from '#turf/turf';
const { BaseLayer } = LayersControl;
const key = 'MYKEY';
const hybrid = 'HYBRID';
const terrain = 'TERRAIN';
const road = 'ROADMAP';
const satellite = 'SATELLITE';
const centerLat = props => {
if (
props.isGeolocationAvailable &&
props.isGeolocationEnabled &&
props.coords
) {
return props.coords.latitude;
}
return 32.11;
};
const centerLong = props => {
if (
props.isGeolocationAvailable &&
props.isGeolocationEnabled &&
props.coords
) {
return props.coords.longitude;
}
return 34.963;
};
const initialMapCenter = props => {
return [centerLat(props), centerLong(props)];
};
const initialZoomLevel = 11;
const markers = props => {
if (props.plots) {
return (
<div>
{(props.filteredPlots || props.plots).map(
plot =>
plot.feature && (
<PlotMarker
key={plot._id}
geoJSON={plot.feature}
position={centroid(plot.feature).geometry.coordinates}
/>
)
)}
</div>
);
}
};
export class PlotMap extends Component {
render() {
this.props.plots &&
(this.props.filteredPlots || this.props.plots).forEach(plot => {
plot.feature &&
console.log(centroid(plot.feature).geometry.coordinates);
});
return (
<div
className="col-sm-8 m-auto p-0 flex-column float-right"
style={{ height: `85vh` }}>
<Map
center={initialMapCenter(this.props)}
zoom={initialZoomLevel}
zoomControl={true}
onZoomend={e => {
this.props.setZoomLevel(e.target.getZoom());
}}
onMoveEnd={e => {
this.props.setMapCenter(e.target.getCenter());
}}>
<LayersControl position="topright">
<BaseLayer name="Google Maps Roads">
<GoogleLayer googlekey={key} maptype={road} />
</BaseLayer>
<BaseLayer name="Google Maps Terrain">
<GoogleLayer googlekey={key} maptype={terrain} />
</BaseLayer>
<BaseLayer name="Google Maps Satellite">
<GoogleLayer googlekey={key} maptype={satellite} />
</BaseLayer>
<BaseLayer checked name="Google Maps Hybrid">
<GoogleLayer
googlekey={key}
maptype={hybrid}
libraries={['geometry', 'places']}
/>
</BaseLayer>
</LayersControl>
<SearchBox postion="bottomright" />
{markers(this.props)}
</Map>
</div>
);
}
}
function mapStateToProps(state) {
return {
filteredPlots: state.plots.filteredPlots,
plots: state.plots.plots,
mapCenter: state.plots.mapCenter
};
}
export default geolocated({
positionOptions: {
enableHighAccuracy: false
},
userDecisionTimeout: 5000,
suppressLocationOnMount: false
})(connect(mapStateToProps, actions)(PlotMap));
The marker component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import { Marker, GeoJSON } from 'react-leaflet';
export class PlotMarker extends Component {
render() {
const { key, position, geoJSON, zoomLevel } = this.props;
if (zoomLevel > 14) {
return <GeoJSON key={key} data={geoJSON} />;
}
return <Marker key={key} position={position} />;
}
}
function mapStateToProps(state) {
return {
selectedPlot: state.plots.selectedPlot,
plotBeingEdited: state.plots.plotBeingEdited,
zoomLevel: state.plots.zoomLevel
};
}
export default connect(mapStateToProps, actions)(PlotMarker);
The issue turns out to be that GeoJSON uses long-lat, while Leaflet uses lat-long (inherited from Google Maps.) So my markers were appearing in another part of the world. Fixing this is very easy-just call .reverse() on the array of coordinates you're passing into the Marker component as position, like this:
<PlotMarker
key={plot._id}
geoJSON={plot.feature}
position={centroid(plot.feature).geometry.coordinates}
/>