Ol-ext: rotation changes image size

466 Views Asked by At

I am using ol.interaction.Transform to rotate one image on the map after selecting it. The final goal is to have the possibility to scale, stretch, rotate and translate (drag&drop) the image.

My problems are that:

  1. when I rotate the image, it does not keep the aspect ratio (the size of the image change furthermore it stretches)

does not keep the aspect ratio

  1. the red dotted line that appears along the image perimeter when the image is selected does not follow the movement of the rotation, e.g this is how it looks before I start the rotation:

enter image description here

and this is how it looks meanwhile I am doing the rotation:

the dotted line doesn't follow rotation

I would aspect indeed something like this (black dotted line):

enter image description here

How can I fix it?

This is my code:

            var styleCache = {};

            function getStyle(img, scaleX, scaleY) {

                var view = map.getView();
                var resolutionAtEquator = view.getResolution();
                var y = Math.round(img.height * scaleY);
                var x = Math.round(img.width * scaleX);
                var key = img.src + ',' + x + ',' + y;
                var style = styleCache[key];

                if (!style) {
                    var canvas = document.createElement('canvas');
                    canvas.width = x;
                    canvas.height = y;
                    var ctx = canvas.getContext("2d");
                    ctx.drawImage(img, 0, 0, x, y);
                    var resizeImageUrl = canvas.toDataURL();
                    canvas.remove();
                    var keys = Object.keys(styleCache);
                    if (keys.length >= 100) {
                        // delete an old entry to limit the cache size
                        delete styleCache[keys[0]];
                    }
                    var style = new ol.style.Style({
                        image: new ol.style.Icon({
                           src: resizeImageUrl,
                           opacity: imageOpacity,
                        })
                    });
                    styleCache[key] = style;
                }
                return style;
            }

            styles = [
              new ol.style.Style({
                fill: new ol.style.Fill({
                  color: transparent
                })
              }),
              new ol.style.Style({
                stroke: new ol.style.Stroke({
                  color: transparent,
                  width: width + 2
                })
              }),
              new ol.style.Style({
                stroke: new ol.style.Stroke({
                  color: transparent,
                  width: width
                })
              }),
              new ol.style.Style({
                image: new ol.style.Circle({
                  radius: width * 2,
                  fill: new ol.style.Fill({
                    color: blue
                  }),
                  stroke: new ol.style.Stroke({
                    color: transparent,
                    width: width / 2
                  })
                }),
                zIndex: Infinity
              })
            ];

            var florplanStyle = new ol.style.Style({
                image: new ol.style.Icon({
                   src: img.src,
                   opacity: imageOpacity,
                })
            });

            styleFunction = function(feature, resolution) {

               var rayDrawValueX = img.width/2;
               var resAdjustX = rayDrawValueX * resolution;


               var rayDrawValueY = img.height/2;
               var resAdjustY = rayDrawValueY * resolution;

               var rotation = feature.get('rotation');

               if (rotation !== undefined) {
                    var extent = feature.getGeometry().getExtent();
                    var coordinates = feature.getGeometry().getCoordinates()[0];

                    var getBottomLeft = ol.extent.getBottomLeft(extent);
                    var getBottomRight = ol.extent.getBottomRight(extent);
                    var getTopLeft = ol.extent.getTopLeft(extent);
                    var getTopRight = ol.extent.getTopRight(extent);
                    var center = ol.extent.getCenter(extent);

                    var dx = center[0] - getBottomLeft[0];
                    var dy = 0;
                    var scaleX = Math.sqrt(dx * dx + dy * dy)/resAdjustX;

                    var dx = 0;
                    var dy = getTopRight[1] - center[1];

                    var scaleY = Math.sqrt(dx * dx + dy * dy)/resAdjustY;

                    var florplanStyle2 = getStyle(img, scaleX, scaleY);
                    florplanStyle2.setGeometry(new ol.geom.Point(center));
                    florplanStyle2.getImage().setRotation(rotation);
                    return debug ? styles.concat([florplanStyle2]) : florplanStyle2;

                } else if (feature.getGeometry().getCenter) {
                    //scrolling map case
                    florplanStyle.setGeometry(new ol.geom.Point(feature.getGeometry().getCenter()));
                    // get rotation from drawn feature or geometry
                    florplanStyle.getImage().setRotation(feature.getGeometry().get('rotation'));
                    florplanStyle.getImage().setScale(feature.getGeometry().getRadius()/resAdjustX);
                    return florplanStyle;
                } else {
                   return styles;
                }
            };

            if ( this.nord && this.sud && this.est && this.ovest && this.floorplanImage && this.opacity) {

                 var extent = ol.proj.transformExtent([this.ovest, this.sud, this.est, this.nord], 'EPSG:4326', 'EPSG:3857');
                 var center = ol.extent.getCenter(extent);
                 var size = ol.extent.getSize(extent);
                 var view = map.getView();
                 var resolutionAtEquator = view.getResolution();
                 var width = ol.extent.getWidth(extent);
                 var height = ol.extent.getHeight(extent);
                 var radius = width/2;
                 var rotation = 0;
                 var circle = circle || new ol.geom.Circle(center, radius);
                 var circleFeature = new ol.Feature(circle);
                 circleFeature.set('rotation', rotation);
                 var geom = ol.geom.Polygon.fromExtent(extent);
                 circleFeature.setGeometry(geom);
                 this.features.push(circleFeature);
                 this.mapView.fit(geom, {minResolution: 0.05});
            } else {
                this.controller.fireEvent('mapstaterequest');
            }

            var raster = new ol.layer.Tile({
                source: new ol.source.OSM()
            });

            var source = new ol.source.Vector({
                wrapX: false,
                features: this.features
            });

            var vector = new ol.layer.Vector({
                source: source,
                style: styleFunction
            });
            vector.setMap(map);

            var draw = new ol.interaction.Draw({
                source: source,
                type: 'Circle',
                geometryFunction: function(coordinates, geometry) {
                    var center = coordinates[0];
                    var last = coordinates[1];
                    var dx = center[0] - last[0];
                    var dy = center[1] - last[1];
                    var radius = dx;
                    var rotation = Math.PI - Math.atan2(dy, dx);
                    geometry = geometry || new ol.geom.Circle(center, radius);
                    geometry.setCenterAndRadius(center, radius);
                    geometry.set('rotation', rotation);
                    return geometry;
                },
                style: styleFunction,
                handler: 'onSaveClick'
            });

            draw.on('drawstart', function () {
                source.clear();
            });

            draw.on('drawend', function (evt) {
                // move rotation from geometry to drawn feature
                var rotation = evt.feature.getGeometry().get('rotation');
                evt.feature.set('rotation', rotation);

                var extent = evt.feature.getGeometry().getExtent();
                var geom = ol.geom.Polygon.fromExtent(extent);

                if(img.width!==img.height){
                    scaleY = img.height/img.width
                    geom.scale(1,scaleY);
                }
                evt.feature.setGeometry(geom);
            });

            this.map.addInteraction(draw);

            var isCorner = true; // use opposite corner to scale/stretch, (false = use center);

            var transform = new ol.interaction.Transform({
                features: this.features,
                translateFeature: false,
                // flip wouldn't be compatible with rotation
                noFlip: true,
                rotate: true,
                modifyCenter: function(){ return isCorner; }
            });

            var startangle = 0;

            transform.on('select', function(e) {
                draw.setActive(e.features.length == 0 );
            });


           transform.on('rotatestart', function(e) {
                startangle = e.feature.get('rotation') || 0;
            });

            transform.on('rotating', function (e) {
                // Set angle attribute to be used on style !
                e.feature.set('rotation', startangle - e.angle);
            });

            this.map.addInteraction(transform);

This is the part of the code where I have the feeling I am doing something wrong, but I don't understand what:

if (rotation !== undefined) {
                    var extent = feature.getGeometry().getExtent();
                    var coordinates = feature.getGeometry().getCoordinates()[0];

                    var getBottomLeft = ol.extent.getBottomLeft(extent);
                    var getBottomRight = ol.extent.getBottomRight(extent);
                    var getTopLeft = ol.extent.getTopLeft(extent);
                    var getTopRight = ol.extent.getTopRight(extent);
                    var center = ol.extent.getCenter(extent);

                    var dx = center[0] - getBottomLeft[0];
                    var dy = 0;
                    var scaleX = Math.sqrt(dx * dx + dy * dy)/resAdjustX;

                    var dx = 0;
                    var dy = getTopRight[1] - center[1];

                    var scaleY = Math.sqrt(dx * dx + dy * dy)/resAdjustY;

                    var florplanStyle2 = getStyle(img, scaleX, scaleY);
                    florplanStyle2.setGeometry(new ol.geom.Point(center));
                    florplanStyle2.getImage().setRotation(rotation);
                    return debug ? styles.concat([florplanStyle2]) : florplanStyle2;

                }
1

There are 1 best solutions below

3
TheOldBlackbeard On

I found a solution for my 2 problems. I felt right about the part of the code I mentioned at the bottom of my question. I was calculating scaleX and scaleY in the wrong way.

This is the correct code that fixed my first problem

when I rotate the image, it does not keep the aspect ratio

if (rotation !== undefined) {
    //style during rotation
    var extent = feature.getGeometry().getExtent();
    var coordinates = feature.getGeometry().getCoordinates()[0];

    var getTopLeft = coordinates[0];
    var getBottomLeft = coordinates[1];
    var getBottomRight = coordinates[2];
    var getTopRight = coordinates[3];

    var center = ol.extent.getCenter(extent);

    var top = new ol.geom.LineString([getTopLeft, getTopRight]).getClosestPoint(center);
    var left = new ol.geom.LineString([getTopLeft, getBottomLeft]).getClosestPoint(center);

    var dx = center[0] - left[0];
    var dy = center[1] - left[1];
    var scaleX = Math.sqrt(dx * dx + dy * dy) / resAdjustX;

    var dx = top[0] - center[0];
    var dy = top[1] - center[1];
    var scaleY = Math.sqrt(dx * dx + dy * dy) / resAdjustY;

    var floorplanStyle2 = getStyle(img, scaleX, scaleY);
    floorplanStyle2.setGeometry(new ol.geom.Point(center));
    floorplanStyle2.getImage().setRotation(rotation);
    return debug ? styles.concat([floorplanStyle2]) : floorplanStyle2;

}

About my second problem

the red dotted line that appears along the image perimeter when the image is selected does not follow the movement of the rotation

In order to have this feature, I had to add keepRectangle: true to my ol.interaction.Transform variable. This feature was not initially working on my app because I was using ol-ext v3.1.2, so I had to change it first to the ol-ext v3.2.24 in order to use it.

This is my complete code:

var iconURL = '/media/floorplans/' + uploadedImage;
var transparent = [255, 255, 255, 0];
var blue = [0, 153, 255, 1];
var width = 3;

img.crossOrigin = 'anonymous';
img.src = iconURL;
this.features = new ol.Collection();

var styleCache = {};

function getStyle(img, scaleX, scaleY) {

    var view = map.getView();
    var resolutionAtEquator = view.getResolution();
    var y = Math.round(img.height * scaleY);
    var x = Math.round(img.width * scaleX);
    var key = img.src + ',' + x + ',' + y;
    var style = styleCache[key];

    if (!style) {
        var canvas = document.createElement('canvas');
        canvas.width = x;
        canvas.height = y;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, x, y);
        var resizeImageUrl = canvas.toDataURL();
        canvas.remove();
        var keys = Object.keys(styleCache);
        if (keys.length >= 100) {
            // delete an old entry to limit the cache size
            delete styleCache[keys[0]];
        }
        var style = new ol.style.Style({
            image: new ol.style.Icon({
                src: resizeImageUrl,
                opacity: imageOpacity,
            })
        });
        styleCache[key] = style;
    }
    return style;
}

// Patch because ol-ext is changing extent of circle during rotation
ol.geom.Circle.prototype.rotate = function(angle, anchor) {
    var point = new ol.geom.Point(this.getCenter());
    point.rotate(angle, anchor);
    this.setCenter(point.getCoordinates());
};

styles = [
    new ol.style.Style({
        stroke: new ol.style.Stroke({
            color: transparent,
            width: width
        })
    }),
    //pointer used to draw the image
    new ol.style.Style({
        image: new ol.style.Circle({
            radius: width * 2,
            fill: new ol.style.Fill({
                color: blue
            }),
        }),
        zIndex: Infinity
    })
];

var floorplanStyle = new ol.style.Style({
    image: new ol.style.Icon({
        src: img.src,
        opacity: imageOpacity,
    })
});

styleFunction = function(feature, resolution) {

    var rayDrawValueX = img.width / 2;
    var resAdjustX = rayDrawValueX * resolution;

    var rayDrawValueY = img.height / 2;
    var resAdjustY = rayDrawValueY * resolution;

    var rotation = feature.get('rotation');

    if (rotation !== undefined) {
        //style during rotation
        var extent = feature.getGeometry().getExtent();
        var coordinates = feature.getGeometry().getCoordinates()[0];

        var getTopLeft = coordinates[0];
        var getBottomLeft = coordinates[1];
        var getBottomRight = coordinates[2];
        var getTopRight = coordinates[3];

        var center = ol.extent.getCenter(extent);

        var top = new ol.geom.LineString([getTopLeft, getTopRight]).getClosestPoint(center);
        var left = new ol.geom.LineString([getTopLeft, getBottomLeft]).getClosestPoint(center);

        var dx = center[0] - left[0];
        var dy = center[1] - left[1];
        var scaleX = Math.sqrt(dx * dx + dy * dy) / resAdjustX;

        var dx = top[0] - center[0];
        var dy = top[1] - center[1];
        var scaleY = Math.sqrt(dx * dx + dy * dy) / resAdjustY;

        var floorplanStyle2 = getStyle(img, scaleX, scaleY);
        floorplanStyle2.setGeometry(new ol.geom.Point(center));
        floorplanStyle2.getImage().setRotation(rotation);
        return debug ? styles.concat([floorplanStyle2]) : floorplanStyle2;

    } else if (feature.getGeometry().getCenter) {
        //style meanwhile drawing
        floorplanStyle.setGeometry(new ol.geom.Point(feature.getGeometry().getCenter()));
        // get rotation from drawn feature or geometry
        floorplanStyle.getImage().setRotation(feature.getGeometry().get('rotation'));
        floorplanStyle.getImage().setScale(feature.getGeometry().getRadius() / resAdjustX);
        return floorplanStyle;
    } else {
        return styles;
    }
};

if (this.nord && this.sud && this.est && this.ovest && this.floorplanImage && this.opacity) {
    //floorplan when floorplan position map is opened
    var extent = ol.proj.transformExtent([this.ovest, this.sud, this.est, this.nord], 'EPSG:4326', 'EPSG:3857');

    var center = ol.extent.getCenter(extent);
    var size = ol.extent.getSize(extent);
    var view = map.getView();
    var resolutionAtEquator = view.getResolution();
    var width = ol.extent.getWidth(extent);
    var height = ol.extent.getHeight(extent);
    var radius = width / 2;
    var rotation = this.rotation;
    var rotationInRadian = rotation * Math.PI / 180;

    var circle = circle || new ol.geom.Circle(center, radius);
    var circleFeature = new ol.Feature(circle);

    circleFeature.set('rotation', rotationInRadian);
    var geom = ol.geom.Polygon.fromExtent(extent);
    geom.rotate(-rotationInRadian, ol.extent.getCenter(geom.getExtent()));
    circleFeature.setGeometry(geom);
    this.features.push(circleFeature);
    this.mapView.fit(geom, {
        minResolution: 0.05
    });
} else {
    this.controller.fireEvent('mapstaterequest');
}

var raster = new ol.layer.Tile({
    source: new ol.source.OSM()
});

var source = new ol.source.Vector({
    features: this.features
});

var vector = new ol.layer.Vector({
    source: source,
    style: styleFunction,
    updateWhileAnimating: true,
});
vector.setMap(map);

var draw = new ol.interaction.Draw({
    source: source,
    type: 'Circle',
    geometryFunction: function(coordinates, geometry) {
        var center = coordinates[0];
        var last = coordinates[1];
        var dx = center[0] - last[0];
        var dy = center[1] - last[1];
        var radius = dx;
        var rotation = Math.PI - Math.atan2(dy, dx);
        geometry = geometry || new ol.geom.Circle(center, radius);
        geometry.setCenterAndRadius(center, radius);
        geometry.set('rotation', rotation);
        return geometry;
    },
    style: styleFunction,
});

draw.on('drawstart', function() {
    source.clear();
});

draw.on('drawend', function(evt) {
    var rotation = evt.feature.getGeometry().get('rotation');
    evt.feature.set('rotation', rotation);

    var extent = evt.feature.getGeometry().getExtent();
    var geom = ol.geom.Polygon.fromExtent(extent);

    if (img.width !== img.height) {
        scaleY = img.height / img.width;
        geom.scale(1, scaleY);
    }
    geom.rotate(-rotation, ol.extent.getCenter(geom.getExtent()));
    evt.feature.setGeometry(geom);
});

this.map.addInteraction(draw);

var isCorner = true; // use opposite corner to scale/stretch, (false = use center);

var transform = new ol.interaction.Transform({
    features: this.features,
    keepRectangle: true,
    // flip wouldn't be compatible with rotation
    noFlip: true,
    translateFeature: false,
    rotate: true,
    handler: 'onSaveClick',
    modifyCenter: function() {
        return isCorner;
    }
});

var startangle;

transform.on('select', function(e) {
    if (e.feature != undefined) {
        draw.setActive(e.feature.length == 0);
    } else {
        draw.setActive(true);
    }
});

transform.on(['rotatestart', 'scalestart'], function(e) {
    startangle = e.feature.get('rotation') || 0;
});

transform.on('rotating', function(e) {
    // Set angle attribute to be used on style !
    e.feature.set('rotation', startangle - e.angle);
});

this.map.addInteraction(transform);