How to Draw Polyline on mapmyindia using React? - reactjs

I am just exploring Mapmyindia.
I have gone through the basic location display on the map.
I am not getting how to display polyline on map.
code for location
app.js
import "./App.css";
import SideBar from "./componants/SideBar";
import Map from "mapmyindia-react";
// import MapmyIndia, { MapMarker } from "react-mapmyindia";
function App() {
return (
<div className="App">
Hello
<Map
markers={[
{
position: [21.1588329, 72.7688111],
draggable: true,
zoom: 15,
title: "Marker title",
onClick: (e) => {
console.log("clicked ");
},
onDragend: (e) => {
console.log("dragged");
},
onMouseover: (e) => {
console.log("Mouse over");
},
},
]}
/>
{/* <Map /> */}
<SideBar></SideBar>
</div>
);
}
export default App;
Which result this
Now, Please help with drawing polyline.

I guess you're using the npm package for maps. If you go through the library's code in gitHub, you can see the owner has only added marker functionality. You can simply copy the code from there and add it manually to your project and then add the polyline functionality and then pass the data as props from your app.js file like you're doing for markers.
renderPolylines = () => {
const { polylines = [] } = this.props;
if (!this.map) {
return;
}
polylines.map(m => {
if (m?.position && Array.isArray(m.position)) {
const { position, color, weight, opacity } = m;
let points = [];
position.map(p => {
const { lat, lng } = p;
const center = new L.LatLng(lat, lng);
points.push(
new L.LatLng(center.lat, center.lng))/*array of wgs points*/
})
const polyline = new L.Polyline(points, { color, weight, opacity });
this.map.addLayer(polyline);
this.polylines.push(polyline);
}
});
};
Props for rendering polyline from app.js
polylines = {[
{
position: [
{
lat: 18.5014,
lng: 73.805,
},
{
lat: 18.5414,
lng: 73.855,
},
{
lat: 18.5514,
lng: 73.855,
},
{
lat: 18.5614,
lng: 73.855,
},
],
color: "red",
weight: 4,
opacity: 0.5,
},
]}

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

Centralize google map issue

import React, {
useState
} from "react";
import GoogleMapReact from "google-map-react";
function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin[1], pin[0]));
});
return bounds;
};
return ( <
div >
<
div style = {
{
height: "100vh",
width: "100%"
}
} >
<
GoogleMapReact bootstrapURLKeys = {
{
key: AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk
}
}
defaultCenter = {
tallinn.center
}
defaultZoom = {
tallinn.zoom
}
onGoogleApiLoaded = {
({
map,
maps
}) => apiIsLoaded(map, maps, latLgn)
}
yesIWantToUseGoogleMapApiInternals >
{
latLgn.map((item, index) => ( <
div lat = {
item[1]
}
lng = {
item[0]
}
key = {
index
} >
</div>
))
} </GoogleMapReact>
</div>
</div>
);
}
export default Gmap
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I have a problem with centralizing the map I am rendering.
although I have a state with the "lat" and "lng" as stated in the docs still, when I run the app with npm start or refreshing the page, it will centre itself somewhere in the ocean.\
PS. I will paste my "map" component only.
import React, { useState } from "react";
import GoogleMapReact from "google-map-react";
export default function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin[1], pin[0]));
});
return bounds;
};
return (
<div>
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{key: AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk }}
defaultCenter={tallinn.center}
defaultZoom={tallinn.zoom}
onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps, latLgn)}
yesIWantToUseGoogleMapApiInternals
>
{latLgn.map((item, index) => (
<div lat={item[1]} lng={item[0]} key={index}< </div>
))}
</GoogleMapReact>
</div>
</div>
);
}
ps. let me know if I should provide more information
UPDATE
I have created a sample project here.
I checked your code and I noticed a couple of things.
First, in your getMapBounds function, the bounds returned are null. This is because the values of pin[1] and pin[0] are undefined. This might be the reasoon why the map is centering in the middle of the ocean like this as this is the center of the world and coordinates are 0,0. You must use pin.lat and pin.lng instead so that it will correctly populate the value for bounds.
Second, it seems that you would like to put markers on the coordinates of your latLgn. To achieve this, you can follow the AnyReactComponent function as mentioned in the google-map-react docs instead of putting a div directly inside the latLgn.map.
Lastly, inside your latLgn.map, you must not use item[1] and item[0] as they are also both undefined instead use item.lat and item.lng.
Here's the code snippet for my working code:
import React, { useState } from "react";
import "./style.css"
import GoogleMapReact from "google-map-react";
const AnyReactComponent = ({ text }) => <div>{text}</div>;
export default function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin.lat, pin.lng));
console.log(pin[0])
});
return bounds;
};
return (
<div>
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{key: "YOUR_API_KEY" }}
defaultCenter={tallinn.center}
defaultZoom={tallinn.zoom}
onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps, latLgn)}
yesIWantToUseGoogleMapApiInternals
>
{latLgn.map((item, index) => (
<AnyReactComponent lat={item.lat} lng={item.lng} key={index} text={"(" +item.lat + "," + item.lng +")"}> </AnyReactComponent>
))}
</GoogleMapReact>
</div>
</div>
);
}
I also noticed your question in the comment section. You can use online ides like stackblitz to provide an sscce of your code and just remove the API key. You can put a note in your question that you need to put API key to see how the code works. You can refer to my working code above.

How to Fit Boundaries and Center React Google Maps

I am new to React Google Maps. I am using this library to create a map with several locations and trying fit the boundaries and center it. I have used this example.
After I populate the map, I want the map to change its center based on the places which have been populated rather than the default center. I am unable to achieve this. The following is my code.
import React, { useState, useEffect } from "react"
import {
GoogleMap,
LoadScript,
Marker,
InfoWindow,
} from "#react-google-maps/api"
const Map = () => {
const mapStyles = {
height: "400px",
width: "100%",
}
const defaultCenter = {
lat: 39.76866069032195,
lng: -86.15818042985295,
}
const locations = [
{
name: "Location 1",
location: {
lat: 41.3954,
lng: 2.162,
},
},
{
name: "Location 2",
location: {
lat: 41.3917,
lng: 2.1649,
},
},
{
name: "Location 3",
location: {
lat: 41.3773,
lng: 2.1585,
},
},
{
name: "Location 4",
location: {
lat: 41.3797,
lng: 2.1682,
},
},
{
name: "Location 5",
location: {
lat: 41.4055,
lng: 2.1915,
},
},
]
const [selected, setSelected] = useState({})
const [map, setMap] = useState(null)
const onSelect = item => {
setSelected(item)
}
const Somefunction = map => {
const bounds = new window.google.maps.LatLngBounds()
locations.map((location, i) => {
bounds.extend(new window.google.maps.LatLng(location.lat, location.lng))
})
map.fitBounds(bounds)
setMap(map)
}
return (
<div className="map-container">
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap
mapContainerStyle={mapStyles}
zoom={13}
center={defaultCenter}
onLoad={map => {
const bounds = new window.google.maps.LatLngBounds()
map.fitBounds(bounds)
setMap(map)
}}
>
{locations.map(item => {
return (
<Marker
key={item.name}
position={item.location}
onClick={() => onSelect(item)}
/>
)
})}
{selected.location && (
<InfoWindow
position={selected.location}
clickable={true}
onCloseClick={() => setSelected({})}
>
<p>{selected.name}</p>
</InfoWindow>
)}
</GoogleMap>
</LoadScript>
</div>
)
}
export default Map
With the current code, the map gets centered in some bizarre location and not in the middle of the 5 locations listed above.
I would also appreciate if someone can shed light on how to change the zoom level for different screen size automatically.
The problem I see in the use of functional components is that it is not as flexible as using class based components in terms of this kind of implementation. To be more precise, it would be more beneficial to make use of the react life cycles such as ComponentDidMount which you can only do when using class based components.
Furthermore, you may want to stay away from using 3rd party packages/libraries and instead implement it locally by dynamically adding the script. That way, you would be able to follow Google's official documentation instead of the the 3rd party's. The following is a sample I made to demonstrate this as well as making use of the bounds:
StackBlitz sample: https://stackblitz.com/edit/react-bounds-65524739
or
App.js
import React, { Component } from "react";
import { render } from "react-dom";
import Map from "./components/map";
import "./style.css";
class App extends Component {
render() {
return (
<Map
id="myMap"
options={{
center: { lat: 39.76866069032195, lng: -86.15818042985295 },
zoom: 8
}}
/>
);
}
}
export default App;
map.js
import React, { Component } from "react";
import { render } from "react-dom";
class Map extends Component {
constructor(props) {
super(props);
this.state = {
map: "",
locations: [
{
name: "Location 1",
location: {
lat: 41.3954,
lng: 2.162
}
},
{
name: "Location 2",
location: {
lat: 41.3917,
lng: 2.1649
}
},
{
name: "Location 3",
location: {
lat: 41.3773,
lng: 2.1585
}
},
{
name: "Location 4",
location: {
lat: 41.3797,
lng: 2.1682
}
},
{
name: "Location 5",
location: {
lat: 41.4055,
lng: 2.1915
}
}
]
};
}
onScriptLoad() {
const map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options
);
this.addMarker(map);
}
addMarker(map) {
const bounds = new window.google.maps.LatLngBounds();
this.state.locations.map((location, i) => {
new google.maps.Marker({
position: location.location,
map: map
});
bounds.extend(
new window.google.maps.LatLng(
location.location.lat,
location.location.lng
)
);
});
map.fitBounds(bounds);
}
componentDidMount() {
if (!window.google) {
var s = document.createElement("script");
s.type = "text/javascript";
s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`;
var x = document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(s, x);
// Below is important.
//We cannot access google.maps until it's finished loading
s.addEventListener("load", e => {
this.onScriptLoad();
});
} else {
this.onScriptLoad();
}
}
render() {
return <div className="map" id={this.props.id} />;
}
}
export default Map;

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;
};
});
}, []);

Update in real time tooltip with react-leaflet when changing language with i18n

I am currently displaying a Map, thanks to react-leaflet, with a GeoJSON Component. I'm also displaying some tooltips on hover over some countries and cities(for example, when I hover France, a tooltip display "France"). I'm also using i18n for internationalization.
The internationalization works fine for the country tooltips, they are updated in real time.
I have a function updateDisplay, that switch between a GeoJson component for the countries, or a list of Marker for the cities, on zoom change.
The problem is, that when i'm switching languages, it works fine for the whole page, but not for the city tooltips. They are updated only when I zoom (so when the updateDisplay is called).
I would have the expected behaviour : regardless of the zoom, I would like that the city tooltips update in real time, when i switch language.
I hope I've made myself clear
Here is my code :
/**
* Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
*/
export default function CustomMap(): ReactElement {
const { t }: { t: TFunction } = useTranslation();
const countryToString = (countries: string[]): string => countries.map(c => t(c)).join(", ");
// Contains the json containing the polygons of the countries
const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
let geoJson: JSX.Element = <GeoJSON
key='my-geojson'
data={data}
style={() => ({
color: '#4a83ec',
weight: 1,
fillColor: "#1a1d62",
fillOpacity: 0.25,
})}
onEachFeature={(feature: geojson.Feature<geojson.GeometryObject>, layer: Layer) => {
layer.on({
'mouseover': (e: LeafletMouseEvent) => {
const country = countries[e.target.feature.properties.adm0_a3];
layer.bindTooltip(countryToString(country.tooltip as string[]));
layer.openTooltip(country.latlng);
},
'mouseout': () => {
layer.unbindTooltip();
layer.closeTooltip();
},
});
}}
/>
// Contains a list of marker for the cities
const cityMarkers: JSX.Element[] = cities.map(
(
c: position,
i: number
) => {
return (
// Here are the tooltips that doesn't update in real time, when we switch language
// FIX ME
<Marker key={c.latlng.lat + c.latlng.lng} position={c.latlng}>
<Tooltip>{t(c.tooltip as string)}</Tooltip>
</Marker>
);
}
);
const [state, setState] = useState<state>({
zoom: 3,
display: geoJson,
});
// Update on zoom change
function onZoom(e: LeafletMouseEvent): void {
const zoom = e.target._zoom;
const newDisplay = updateDisplay(zoom);
setState({
...state,
zoom,
display: newDisplay,
});
}
// Called on every zoom change, in order to display either the GeoJson, or the cities Marker
function updateDisplay(zoom: number): Marker[] | any {
if (zoom >= 4) {
return cityMarkers;
} else {
return geoJson;
}
}
return (
<Map
style={{ height: "500px" }}
center={[54.370138916189596, -29.918133437500003]}
zoom={state.zoom}
onZoomend={onZoom}
>
<TileLayer url="https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw" />
{state.display}
</Map>
);
}
You can also look at it here : https://github.com/TheTisiboth/WebCV/blob/WIP/src/components/customMap.tsx
It is on the branch WIP
You can do the following to overcome this issue:
Create a boolean flag to keep in memory if the markers have been added
Add the markers on the map using native leaflet code instead of react'leaflet's wrappers.
If the markers are added and zoom >= 4 set the flag to true
if zoom < 4 remove the markers to be able to show countries, set flag to false
When language is changed, if zoom is bigger, equal than 4 and markers have been added remove the previous, add new ones with the new tooltip
you can achieve all these by holding a reference to the map instance.
Here is the whole code you will need, (parts of cities, markers removed):
import React, { useState, ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { Map, Marker, TileLayer, GeoJSON } from "react-leaflet";
import geoJsonData from "../assets/geoJsonData.json";
import { LatLngLiteral, Layer, LeafletMouseEvent } from "leaflet";
import geojson from "geojson";
import { TFunction } from "i18next";
import L from "leaflet";
interface position {
latlng: LatLngLiteral;
tooltip: string;
}
interface state {
markers: position[];
zoom: number;
display: position[] | any;
geoJson: JSX.Element;
countries: { [key: string]: position };
}
/**
* Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
*/
export default function CustomMap(): ReactElement {
const mapRef: any = React.useRef();
const { t, i18n }: { t: TFunction; i18n: any } = useTranslation();
const [markersAdded, setMarkersAdded] = useState(false);
i18n.on("languageChanged", (lng: any) => {
if (lng) {
const map = mapRef.current;
if (map && map.leafletElement.getZoom() >= 4 && markersAdded) {
map.leafletElement.eachLayer(function (layer: L.Layer) {
if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
});
state.markers.map((c: position, i: number) => {
L.marker(c.latlng)
.addTo(map.leafletElement)
.bindTooltip(t(c.tooltip));
});
}
}
});
// const countryToString = (countries: string[]): string => countries.join(", ");
// List of position and label of tooltip for the GeoJson object, for each country
const countries: { [key: string]: position } = {
DEU: {
latlng: {
lat: 51.0834196,
lng: 10.4234469,
},
tooltip: "travel.germany",
},
CZE: {
latlng: {
lat: 49.667628,
lng: 15.326962,
},
tooltip: "travel.tchequie",
},
BEL: {
latlng: {
lat: 50.6402809,
lng: 4.6667145,
},
tooltip: "travel.belgium",
},
};
// List of position and tooltip for the cities Markers
const cities: position[] = [
{
latlng: {
lat: 48.13825988769531,
lng: 11.584508895874023,
},
tooltip: "travel.munich",
},
{
latlng: {
lat: 52.51763153076172,
lng: 13.40965747833252,
},
tooltip: "travel.berlin",
},
{
// greece
latlng: {
lat: 37.99076843261719,
lng: 23.74122428894043,
},
tooltip: "travel.athens",
},
{
// greece
latlng: {
lat: 37.938621520996094,
lng: 22.92695426940918,
},
tooltip: "travel.corinth",
},
];
// Contains the json containing the polygons of the countries
const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
let geoJson: JSX.Element = (
<GeoJSON
key='my-geojson'
data={data}
style={() => ({
color: "#4a83ec",
weight: 1,
fillColor: "#1a1d62",
fillOpacity: 0.25,
})}
// PROBLEM : does not update the tooltips when we switch languages
// FIX ME
onEachFeature={(
feature: geojson.Feature<geojson.GeometryObject>,
layer: Layer
) => {
layer.on({
mouseover: (e: LeafletMouseEvent) => {
const country =
state.countries[e.target.feature.properties.adm0_a3];
layer.bindTooltip(t(country?.tooltip));
layer.openTooltip(country?.latlng);
},
mouseout: () => {
layer.unbindTooltip();
layer.closeTooltip();
},
});
}}
/>
);
const [state, setState] = useState<state>({
markers: cities,
zoom: 3,
geoJson: geoJson,
display: geoJson,
countries: countries,
});
// Update on zoom change
function onZoom(e: LeafletMouseEvent): void {
const zoom = e.target._zoom;
const newDisplay = updateDisplay(zoom);
setState({
...state,
zoom,
display: newDisplay,
});
}
// Called on every zoom change, in order to display either the GeoJson, or the cities Marker
function updateDisplay(zoom: number): Marker[] | any {
const map = mapRef.current;
if (zoom >= 4) {
return state.markers.map((c: position, i: number) => {
console.log(t(c.tooltip));
if (map && !markersAdded) {
console.log(map.leafletElement);
L.marker(c.latlng)
.addTo(map.leafletElement)
.bindTooltip(t(c.tooltip));
setMarkersAdded(true);
}
});
} else {
map.leafletElement.eachLayer(function (layer: L.Layer) {
if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
});
setMarkersAdded(false);
return state.geoJson;
}
}
return (
<Map
ref={mapRef}
style={{ height: "500px" }}
center={[54.370138916189596, -29.918133437500003]}
zoom={state.zoom}
onZoomend={onZoom}
>
<TileLayer url='https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw' />
{state.display}
</Map>
);
}
Eng:
"travel": {
"germany": "Munich, Berlin, Hambourg, Münster, Allemagne",
"munich": "Munchen",
"berlin": "Berlin",
"tchequie": "Tchéquie, Prague",
"belgium": "Belgique",
"athens": "Athènes",
"corinth": "Corinthe",
...
}
Fr:
"travel": {
"germany": "Munich, Berlin, Hamburg, Münster, Germany",
"munich": "Munich",
"berlin": "Berlin",
"tchequie": "Czech Republic, Prague",
"belgium": "Belgium",
"athens": "Athens",
"corinth": "Corinth",
...
}
You can make it more clean by reusing the markers removal code chunk and markers addition code chunk respectively.

Resources