5

I have wrote a React project with react-leaflet and hook. My goal is to move and rotate marker every second. However, moving part is working fine. But the rotation of marker is not working. I am really stuck for last few days. I couldn't find a good solution. Please help me.

The map component is as follows.

import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import { useState, useEffect } from "react";
import L from 'leaflet';
import 'leaflet-marker-rotation';

const MySimpleMap = () => {
  const [lat, setLat] = useState(22.899397);
  const [lon, setLon] = useState(89.508279);
  const [heading, setHeading] = useState(30)

  useEffect(() => {
    const interval = setInterval(() => {
      myfun();
    }, 1000);
    return () => {
      clearInterval(interval);
    };
  }, [lat]);

  const defaultIcon = L.icon({
    iconUrl: "https://unpkg.com/[email protected]/dist/images/marker-icon.png",
    iconSize: [20, 40],
    iconAnchor: [18, 18],
    popupAnchor: [0, -10],
    shadowAnchor: [10, 10]
  }); 

  const myfun = () => {    
    setLat(lat + 0.00001);
    setLon(lon + 0.00001);
    setHeading(heading+5);
    console.log("angle:" + heading);
  };
  return (
    <MapContainer className="map" center={[lat, lon]} zoom={21}>
      <TileLayer
        attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[lat, lon]} icon={defaultIcon} rotationAngle={heading} rotationOrigin="center">
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
};

export default MySimpleMap;

The complete problem is in this sandbox: https://codesandbox.io/s/vigilant-wood-kgv4p?file=/src/App.js

1

2 Answers 2

4

Using some of the provided answers for rotating markers in this post , was able to get it to work with your provided codesandbox. The default Marker component does not have rotationAngle or rotationOrigin as valid props, you'd need to do something like done here to get that. So instead we can use re-use the rotation code and plug it into your code by lifting it into a re-usable function:

const applyRotation = (marker, _options) => {
  const oldIE = L.DomUtil.TRANSFORM === "msTransform";
  const options = Object.assign(_options, { rotationOrigin: "center" });
  const { rotationAngle, rotationOrigin } = options;

  if (rotationAngle && marker) {
    marker._icon.style[L.DomUtil.TRANSFORM + "Origin"] = rotationOrigin;

    if (oldIE) {
      // for IE 9, use the 2D rotation
      marker._icon.style[L.DomUtil.TRANSFORM] = `rotate(${rotationAngle} deg)`;
    } else {
      // for modern browsers, prefer the 3D accelerated version
      marker._icon.style[
        L.DomUtil.TRANSFORM
      ] += ` rotateZ(${rotationAngle}deg)`;
    }
  }
};

And then calling it once the rotation has been changed with a useEffect because we also want the Markers position to be updated otherwise the rotation won't work:

  useEffect(() => {
    applyRotation(markerRef.current, { rotationAngle: heading + 5 });
  }, [heading]);

When you call myFunc it updates the position (lat,lon) as well as the header (rotation) so by placing it in the useEffect, on next render Marker should have updated its position.

I have a codebox example here.

The above solution does have some issue's you'll need to sift through the leaflet-rotatedmarker library to get those missing edge cases. However if you can add that library a better solution would be importing that:

import L from "leaflet";
import "leaflet-rotatedmarker";

and using the extended setRotationAngle instead.

  useEffect(() => {
    if (markerRef.current) {
      markerRef.current.setRotationAngle(heading);
    }
  }, [heading]);

You can also use that to lift it into a RotatedMarker component so that your original use case of the props rotationAngle={heading} rotationOrigin="center" will work:

const RotatedMarker = forwardRef(({ children, ...props }, forwardRef) => {
  const markerRef = useRef();

  const { rotationAngle, rotationOrigin } = props;
  useEffect(() => {
    const marker = markerRef.current;
    if (marker) {
      marker.setRotationAngle(rotationAngle);
      marker.setRotationOrigin(rotationOrigin);
    }
  }, [rotationAngle, rotationOrigin]);

  return (
    <Marker
      ref={(ref) => {
        markerRef.current = ref;
        if (forwardRef) {
          forwardRef.current = ref;
        }
      }}
      {...props}
    >
      {children}
    </Marker>
  );
});

Here's the codepen where i tested the above out

Sign up to request clarification or add additional context in comments.

5 Comments

thank you for your excellent fix to the code. Do you have any idea why the marker in your code starts from the zero degree to the desired angle in each update? Therefore, it's giving a vibration is each cycle instead of smooth rotation. What could be the fix to that?
Yea its because the code is missing the initial rotation setting, specifically the initHook for the marker -> github.com/bbecquet/Leaflet.RotatedMarker/blob/master/… , I tried to go with minimal things from the library but you might as well use it or move over those implementation details that I missed. Updated answer above with how that would work if you did just use the lib
Thank you so much Jonathan, you saved my job... if you could explain how the forwardRef works here that'd be great, because the code obviously works, but I can't understand it. Thank you so much sir.
“Regular function or class components don’t receive the ref argument, and ref is not available in props either.” - reactjs.org/docs/forwarding-refs.html. using forwardRef just lets you use the prop “ref” on your components. Used it here so that RotatedMarker could be a wrapper component for Marker where it adds support for the props “rotationAngle” and “rotationOrigin”.
I was able to get it working based on this answer. "leaflet": "^1.7.1", "leaflet-rotatedmarker": "^0.2.0", "react-leaflet": "^3.2.5", In particular the RotatedMarker component worked great after importing "leaflet-rotatedmarker";
0

To live rotate the marker when the rotation angle changes you must update the rotation value using the marker's refrence, you can update your code as follow:

/* your other code */
    const markerRef = useRef(null)

   useEffect(()=>{
      if(markerRef.current) 
        { markerRef.current.setRotationAngle(heading); }
    
     },[heading])
.....
....
<Marker ref={markerRef} rotationAngle={heading}> 
...other code
</Marker>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.