Rotating image / marker image on Google map V3

Viewed 55177

How could I rotate an image (marker image) on a Google map V3?

  • There is an excellent example for V2 here, exactly doing what I need. But for GMap2! They do it with a rotating canvas.
  • Image rotating with JS / JQuery is frequently used, there are multiple answers about this. But how could I apply this to my maps image?
  • One mentioned approach is to have different images for different angles and to switch among them - this is NOT what I want. I do not like to have so many images, I want to rotate by code.

Remark: There are similar questions, but all for V2 and not V3 (as far I can tell). I need it for V3.

17 Answers

I also had a hard time to figure out the way to rotate .png marker. I solved it like below. You can create many markers with same custom image and rotate a specific marker you want to rotate. I hope it helpful to you.

var id = 'my_marker_01';
var img_url = "../img/car.png";
var my_icon = img_url + "#" + id;
var marker = new google.maps.Marker({
    icon: my_icon,
    ...
});

var rotate = 45;
$(`img[src="${my_icon}"]`).css(
    {'-webkit-transform' : 'rotate('+ rotate +'deg)',
       '-moz-transform' : 'rotate('+ rotate +'deg)',
       '-ms-transform' : 'rotate('+ rotate +'deg)',
       'transform' : 'rotate('+ rotate +'deg)'}); 

Nobody mentioned about using pre-rotated icons. Depending on your application, you could take one icon and rotate it +10 degrees, +20 degrees ... +350 degrees and instead of rotating marker itself, just assign different icon to it - one out of 36 if 10 degrees resolution is good enough. That's also very light on client's resources.

In the example below I generated 36 icons, every one of them is 10 degrees rotated. Their names are: icon0.png, icon10.png, icon20.png, ... icon340.png, icon350.png, icon360.png. The 0 and 360 are the very same icon (e.g symlink)

var rotation = 123 // degrees
var iconName = "icon" + (Math.round(rotation/10)*10).toString() + ".png"
var marker = new google.maps.Marker({
    icon: iconName
})

Assuming you only use that image within Google Maps, you can do the following

bearing = 20

document.querySelectorAll('img[src="/images/imageName"]').forEach((node) => {

    node.style['transform'] = `rotate(${bearing}deg)`
    node.style['webkitTransform'] = `rotate(${bearing}deg)`
    node.style['MozTransform'] = `rotate(${bearing}deg)`
    node.style['msTransform'] = `rotate(${bearing}deg)`
    node.style['OTransform'] = `rotate(${bearing}deg)`
})

This reaches down the dom tree and sets the transform for the marker icon to rotate the degrees you want. The image imageName should be facing North

Not to sure if the webkit, Moz, ms and O version are needed but hey ‍♂️ cant hurt

I have found an easy way to rotate the png image marker for the google marker. Create an custom marker overriding google.maps.OverlayView and rotate the image simply with css/inline style

export const createCustomMarker = ({ OverlayView = google.maps.OverlayView, ...args }) => {

class GoogleMarker extends OverlayView {

    options: any = {};
    div: any = null;
    innerHtml: any = null;

    constructor(options) {
        super();
        this.options = options;
        this.setMap(options.map);
    }

    createDiv() {
        const options = this.options;
        this.div = document.createElement('div');
        this.div.style.position = 'absolute';
        this.setRotation(this.options.rotation);
        if (options.icon) {
            this.setInnerHtml(this.getInnerImageHtml(options));
        }
    }

    getInnerImageHtml(options) {
        const size = this.getSize(options);
        const label = this.options.label;
        const labelHtml = label ? `<span style="color:black;margin-left: -40px;width: 100px;text-align: center;display: block;font-weight:bold;">${label}</span>` : "";
        return `<img style="height:${size.height}px;width:${size.width}px" id="${options.id || ''}" src="${options.icon}">${labelHtml}`;
    }

    addListeners() {
        const self = this;
        google.maps.event.addDomListener(this.div, 'click', event => {
            google.maps.event.trigger(self, 'click');
        });

        this.div.onmouseenter = function () {
            debugger
            google.maps.event.trigger(self, 'onmouseenter');
        }

        this.div.onmouseover = function () {
            google.maps.event.trigger(self, 'onmouseover');
        }

        this.div.onmouseleave = function () {
            google.maps.event.trigger(self, 'onmouseleave');
        }

        this.div.onmouseout = function () {
            google.maps.event.trigger(self, 'onmouseout');
        }
    }

    appendDivToOverlay(appendDiv: any) {
        const panes: google.maps.MapPanes = this.getPanes();
        panes.floatPane.appendChild(appendDiv);
    }

    setRotation(degrees: number) {
        if (this.div) {
            this.div.style.transform = 'rotate(' + degrees + 'deg)';
        }
        this.options.rotation = degrees;
    }

    getRotation() {
        return this.options.rotation;
    }

    setInnerHtml(html: string) {
        this.innerHtml = html;
        this.div.innerHTML = this.innerHtml;
    }

    private positionDiv(div: any, options: any) {
        if (div != null) {
            const point = this.getProjection().fromLatLngToDivPixel(options.latlng);
            if (point) {
                const size = this.getSize(options);
                const anchor = options.anchor ? options.anchor : new google.maps.Point((size.width / 2), (size.height / 2))
                const leftAnchor = anchor.x;
                const topAnchor = anchor.y;
                div.style.left = `${point.x - leftAnchor}px`;
                div.style.top = `${point.y - topAnchor}px`;
            }
        }
    }

    private getSize(options) {
        const size = options.size || { height: 52, width: 52 };
        return size;
    }

    draw() {
        if (!this.div) {
            this.createDiv();
            this.appendDivToOverlay(this.div);
            this.addListeners();
        }
        this.positionDiv(this.div, this.options);
    }

    remove() {
        if (this.div) {
            this.div.parentNode.removeChild(this.div);
            this.div = null;
        }
    }

    setVisible(value: boolean) {
        if (this.div) {
            this.div.style["display"] = value ? "block" : "none";
        }
    }

    getVisible() {
        if (this.div) {
            return this.div.style["display"] == "none";
        }
        return false;
    }

    setPosition(position) {
        this.options.latlng = position;
        this.infoOptions.latlng = position;
        this.positionDiv(this.div, this.options);
    }

    getPosition() {
        return this.options.latlng;
    }

    getDraggable() {
        return false;
    }

    isHTML(html: string) {
        return /<([A-Za-z][A-Za-z0-9]*)\b[^>]*>(.*?)<\/\1>/.test(html);
    }
}
return new GoogleMarker(args)

}

After creating this custom marker - Initialize the marker in the following way

import { createCarMarker } from "./marker.component"; // dynamic path to component

let marker = createCarMarker({
        id: id, // will add id to the parent container div
        latlng: new google.maps.LatLng(0, 0), // replace latitude-longitude with your values
        map: this.map,
        size: new google.maps.Size(52, 52), // replace the image size with your values
        rotation: markerData.direction, // Provide values in degrees
        icon: iconUrl, // Replace it with your image url
        label: markerLabel // Provide marker label. Optional field
    });

Now simply rotate the marker using the following method

marker.setRotation(180); // You just need to call only this method every-time the degrees changes.

To listen the changes on the marker.

google.maps.event.addDomListener(marker, 'click', function (event) {

    });

    google.maps.event.addListener(marker, 'onmouseenter', function (event) {

    });

    google.maps.event.addListener(marker, 'onmouseleave', function (event) {

    });

    google.maps.event.addListener(marker, 'onmouseover', function (event) {

    });

    google.maps.event.addListener(marker, 'onmouseout', function (event) {

    });

You can customize the listeners or add new/update in the custom marker class according to your requirement.

If you are using SVG, Then this is the best way to rotate it.

let marker_, svg_, size_ = 100, rotation_ = 50


// Get SVG
fetch('https://upload.wikimedia.org/wikipedia/commons/7/78/Space-shuttle.svg')
.then(response => response.text())
.then(text => {
  svg_ = text;
  svg_ = svg_
    .replace(/^<\?(.+)\?>$/gm, '') // unsupported unnecessary line
    // You can replace anything you want, but first of all check your svg code
    .replace(/width.+\Wheight\S+/, 
      'width="{{width}}" height="{{height}}" transform="{{transform}}" ')
    
  // Load Map
  initMap()
})

function getIcon(rotation){
return {url:`data:image/svg+xml;charset=utf-8,
      ${encodeURIComponent(svg_
      .replace('{{width}}', 100)
      .replace('{{height}}', 100)
      .replace('{{transform}}', `rotate(${rotation},0,0)`))}`,anchor: new google.maps.Point(50, 50),
      origin: new google.maps.Point(0, 0)}

}


// Map
function initMap() {
  const position = {lat: 36.720426, lng: -4.412573};
  const map = new google.maps.Map(document.getElementById("map"), {
    zoom: 19,
    center: position
  })
  marker_ = new google.maps.Marker({
    position: position,
    map: map,
    icon: getIcon(rotation_)
  })
}

// Change rotation
$input_ = document.querySelector('input')
$input_.value = rotation_
$input_.onchange = () => {
  marker_.setIcon(getIcon(parseInt($input_.value))
  )
} 
* {
  padding: 0;
  margin: 0;
}

#map {
  width: 100%;
  height: 100vh;
}

input {
  position: fixed;
  z-index: 1;
  margin: 100px;
  padding: 10px;
  border-radius: 2px;
  background-color: red;
  border: none;
  color: white;
  font-family: 'Roboto';
  width: 70px;
}
<script src="https://maps.google.com/maps/api/js"></script>
<input type="number" placeholder="rotation">
<div id="map"></div>

Related