Rotate marker in Leaflet
Asked Answered
T

5

19

How can I rotate a marker in leaflet? I will have a lot of markers, all with a rotation angle.

I've tried this solution from runanet/coomsie at Leaflet on GitHub, but nothing happens with my marker:

L.Marker.RotatedMarker= L.Marker.extend({    
    _reset: function() {
        var pos = this._map.latLngToLayerPoint(this._latlng).round();

        L.DomUtil.setPosition(this._icon, pos);
        if (this._shadow) {
            L.DomUtil.setPosition(this._shadow, pos);
        }

        if (this.options.iconAngle) {
            this._icon.style.WebkitTransform = this._icon.style.WebkitTransform + ' rotate(' + this.options.iconAngle + 'deg)';
            this._icon.style.MozTransform = 'rotate(' + this.options.iconAngle + 'deg)';
            this._icon.style.MsTransform = 'rotate(' + this.options.iconAngle + 'deg)';
            this._icon.style.OTransform = 'rotate(' + this.options.iconAngle + 'deg)';
        }

        this._icon.style.zIndex = pos.y;
    },

    setIconAngle: function (iconAngle) {

        if (this._map) {
            this._removeIcon();
        }

        this.options.iconAngle = iconAngle;

        if (this._map) {
            this._initIcon();
            this._reset();
        }
    }

});

var rotated = new L.Marker.RotatedMarker([63.42, 10.39]);
rotated.setIconAngle(90);
rotated.addTo(map);

Any other ideas or solutions? (Testing with Firefox 16 on Windows.)

Tryma answered 21/11, 2012 at 13:45 Comment(0)
A
8

Running the code as it is, the icon will disappear when you try to rotate it in Firefox (try rotating on a mouseclick instead of on load and you will see that the icon appears before you try to rotate it), but I'm willing to bet it will work (the first time) in a webkit browser. The reason is the transform lines:

this._icon.style.WebkitTransform = this._icon.style.WebkitTransform + ' rotate(' + this.options.iconAngle + 'deg)';
this._icon.style.MozTransform = 'rotate(' + this.options.iconAngle + 'deg)';

Firefox also uses CSS transforms to position icons, so before rotation it will have Moztransform will have a value of for example "translate(956px, 111px)". The way the code is now, it will replace that with simply "rotate(90deg)" and Firefox won't know where to place the icon.

You want Moztransform to have a value of "translate(956px, 111px) rotate(90deg)", so if you use this code it will work the first time, like in webkit.

this._icon.style.MozTransform = this._icon.style.MozTransform  + ' rotate(' + this.options.iconAngle + 'deg)';

However, it will break on the next rotate, so you really need to set both the translation and rotation in one go, like this:

this._icon.style.MozTransform = L.DomUtil.getTranslateString(pos) + ' rotate(' + this.options.iconAngle + 'deg)';

Then you can get rid of the L.DomUtil.setPosition(this._icon, pos); at the start.

Avocation answered 21/11, 2012 at 15:4 Comment(3)
You answer helped me a lot, only one issue left: Actually my icon doesn't rotate at all on first draw. Tried to change the angle on click with "setIconAngle", that test worked very well with you changes in the code. But why isn't my iconAngle used on first draw?Tryma
Extended the onAdd function from Marker with this._reset() as a workaround to get the icon rotated from start.Tryma
Helpful code and answer. I have implemented this solution - however I have one issue. Whenever I zoom in/out on the map, the rotation of the markers resets back to 0 angle. Did you have this same issue? Is there any reason the marker would reset its angle when map zooms in/out?Robles
H
9

This solution is by far the easiest: https://github.com/bbecquet/Leaflet.RotatedMarker

Note: it only modifies the existing marker, allowing two more options (rotationAngle and rotationOrigin).

The solution works very well. As per the GitHub page, a usage example:

L.marker([48.8631169, 2.3708919], {
    rotationAngle: 45
}).addTo(map);
Habile answered 27/1, 2016 at 8:34 Comment(1)
This worked great after I had some trouble importing it. Here's how it's done: #56653484Tass
A
8

Running the code as it is, the icon will disappear when you try to rotate it in Firefox (try rotating on a mouseclick instead of on load and you will see that the icon appears before you try to rotate it), but I'm willing to bet it will work (the first time) in a webkit browser. The reason is the transform lines:

this._icon.style.WebkitTransform = this._icon.style.WebkitTransform + ' rotate(' + this.options.iconAngle + 'deg)';
this._icon.style.MozTransform = 'rotate(' + this.options.iconAngle + 'deg)';

Firefox also uses CSS transforms to position icons, so before rotation it will have Moztransform will have a value of for example "translate(956px, 111px)". The way the code is now, it will replace that with simply "rotate(90deg)" and Firefox won't know where to place the icon.

You want Moztransform to have a value of "translate(956px, 111px) rotate(90deg)", so if you use this code it will work the first time, like in webkit.

this._icon.style.MozTransform = this._icon.style.MozTransform  + ' rotate(' + this.options.iconAngle + 'deg)';

However, it will break on the next rotate, so you really need to set both the translation and rotation in one go, like this:

this._icon.style.MozTransform = L.DomUtil.getTranslateString(pos) + ' rotate(' + this.options.iconAngle + 'deg)';

Then you can get rid of the L.DomUtil.setPosition(this._icon, pos); at the start.

Avocation answered 21/11, 2012 at 15:4 Comment(3)
You answer helped me a lot, only one issue left: Actually my icon doesn't rotate at all on first draw. Tried to change the angle on click with "setIconAngle", that test worked very well with you changes in the code. But why isn't my iconAngle used on first draw?Tryma
Extended the onAdd function from Marker with this._reset() as a workaround to get the icon rotated from start.Tryma
Helpful code and answer. I have implemented this solution - however I have one issue. Whenever I zoom in/out on the map, the rotation of the markers resets back to 0 angle. Did you have this same issue? Is there any reason the marker would reset its angle when map zooms in/out?Robles
H
2

What works very well for me is adding a data-rotate="[angle]" attribute to each marker. This allows you to call the following JQuery statement on each refresh when necessary:

    $('.your-marker-class').each(function () {            
        var deg = $(this).data('rotate') || 0;
        var rotate = 'rotate(' + $(this).data('rotate') + 'deg) scale(0.5,0.5)';
        $(this).css({
            '-webkit-transform': rotate,
            '-moz-transform': rotate,
            '-o-transform': rotate,
            '-ms-transform': rotate,
            'transform': rotate
        });
    });

Works very fast and with hundreds/thousands of markers. Found this method in some other post somewhere on the internets but seemed right to share here also.

Heavyfooted answered 30/7, 2015 at 13:9 Comment(0)
P
2

If you're using react-leaflet, I built upon this idea (https://github.com/bbecquet/Leaflet.RotatedMarker) to create a React component that extends Marker and accepts both rotation and rotationOrigin as a prop.

// Libs
import L from 'leaflet'

// Components
import { ExtendableMarker } from 'react-leaflet-extendable'

// HOCS
import { withLeaflet } from 'react-leaflet'

const proto_setPos = L.Marker.prototype._setPos

const LeafletMarker = L.Marker.extend({
  _setPos(pos: [number, number]) {
    proto_setPos.call(this, pos)
    this._setRotation(this.options.rotation)
  },
  _setRotation(rotation: number | null | undefined) {
    if (typeof rotation === 'number') {
      this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin || 'center'
      const transform = this._icon.style[L.DomUtil.TRANSFORM] + ` rotate(${rotation}deg)`
      this._icon.style[L.DomUtil.TRANSFORM] = transform
    }
  },
})

const createRotatedMarker = (pos: [number, number], options: any) => {
  return new LeafletMarker(pos, options)
}

class RotatedMarker extends ExtendableMarker {
  public createLeafletElement() {
    return createRotatedMarker(this.props.position, { ...this.props })
  }
}

export default withLeaflet(RotatedMarker)
Polaroid answered 7/3, 2020 at 16:5 Comment(2)
Hi Brad, this did not work for me, can you help me? I'm also adapting it to JS, but this is the error I get: Failed to compile. ./src/components/Map/RotatedMarker.jsx Attempted import error: 'withLeaflet' is not exported from 'react-leaflet'.Batangas
@DanielTkach my guess is that you're importing it wrong. You should be able to find the correct place to import withLeaflet from given the version of react-leaflet that you're using.Polaroid
E
0

For React-Leaflet v3+ Integration, you may use the following code to define custom Rotated Marker (in JSX, using hooks):

import L from 'leaflet';
import { useMap } from 'react-leaflet';
import React from 'react';

const LeafletMarker = L.Marker.extend({
  _setPos(pos) {
    L.Marker.prototype._setPos.call(this, pos); // Ensure calling the original method
    this._setRotation(this.options.rotation);
  },
  _setRotation(rotation) {
    if (typeof rotation === 'number' && this._icon) { // Check if _icon is available
      this._icon.style[L.DomUtil.TRANSFORM + 'Origin'] = this.options.rotationOrigin || 'center';
      const transform = this._icon.style[L.DomUtil.TRANSFORM] + ` rotate(${rotation}deg)`;
      this._icon.style[L.DomUtil.TRANSFORM] = transform;
    }
  },
});

const createRotatedMarker = (position, options) => {
  return new LeafletMarker(position, options);
};

const RotatedMarker = (props) => {
  const map = useMap();

  React.useEffect(() => {
    const marker = createRotatedMarker(props.position, { ...props });

    marker.addTo(map);

    return () => {
      map.removeLayer(marker);
    };
  }, [map, props.position, props.rotation, props.rotationOrigin]); // Add dependencies here

  return null; // Since we're manually handling the marker, this component doesn't render anything itself
};

export default RotatedMarker;

Built upon same logic as RotatedMarker by bbecquet

Emanative answered 8/4 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.