zoukankan      html  css  js  c++  java
  • 改造leaflet图片插件Leaflet.ImageOverlay.Rotated,通过图片矩形对角线上的2个顶点控制图片的移动、旋转和缩放,并且保持图片长宽比不变,图片不变形。

    简介:公司需要做个地图图片编辑功能,将平面图上传到Mapbox地图上,可拖拽、旋转、缩放图片至合适的地理位置上。当时发现MapboxGL不好弄,改成了leaflet地图,leaflet插件很多,找到https://github.com/IvanSanchez/Leaflet.ImageOverlay.Rotated图片编辑插件,但发现操作无法保证图片不变形,所以做了些改造,这里顺便做个笔记以便日后查阅。

    1、Leaflet.ImageOverlay.Rotated插件原理分析:

    添加图片要传入3个顶点坐标,topleft, topright, bottomleft,也是可以拖拽的三个控制点。

    1 var overlay = L.imageOverlay.rotated(imgUrl, topLeftPoint, topRightPoint, bottomLeftPoint, {
    2         opacity: 0.9,
    3         interactive: true,
    4         corners:[],
    5         w: null,
    6         h: null
    7     });

    图片添加到地图上,会生成一个外接矩形框(默认是看不到的,我加了border使其可见,便于分析),然后你拖拽3个顶点marker, 插件内部通过数学计算会推断第四个顶点的位置,然后根据4个顶点的位置显示图片。

                             图(1)

     每次拖拽都会调用reposition方法重设图片位置。 

    1 reposition: function(topleft, topright, bottomleft) {
    2         this._topLeft    = L.latLng(topleft);
    3         this._topRight   = L.latLng(topright);
    4         this._bottomLeft = L.latLng(bottomleft);
    5         this._reset();
    6     },

     重点逻辑在_reset方法中, 每次reset都会根据3个顶点推断第四个顶点,然后计算外接矩形框。

     1 _reset: function () {
     2     var div = this._image;
     3 
     4     // Project control points to container-pixel coordinates
     5     // 1)先将3个顶点经纬度转成平面坐标
     6     var pxTopLeft    = this._map.latLngToLayerPoint(this._topLeft);
     7     var pxTopRight   = this._map.latLngToLayerPoint(this._topRight);
     8     var pxBottomLeft = this._map.latLngToLayerPoint(this._bottomLeft);
     9 
    10     // Infer coordinate of bottom right
    11     // 2)然后第4个顶点坐标右下=右上-左上+左下
    12     var pxBottomRight = pxTopRight.subtract(pxTopLeft).add(pxBottomLeft);
    13 
    14     // pxBounds is mostly for positioning the <div> container
    15     // 3)计算4个顶点的外接矩形,返回的pxBounds由四个顶点中的x、y最值构成,即max和min两个顶点对象
    16     var pxBounds = L.bounds([pxTopLeft, pxTopRight, pxBottomLeft, pxBottomRight]);
    17 
    18     //size是外接矩形的宽高,宽=max.x - min.x, 高=max.y - min.y
    19     var size = pxBounds.getSize();
    20 
    21     //用topLeft减去外接矩形最小顶点坐标得到topLeft相对于该外接矩形的坐标位置
    22     var pxTopLeftInDiv = pxTopLeft.subtract(pxBounds.min);
    23 
    24     // Calculate the skew angles, both in X and Y
    25     //4)计算斜切角,这里用到了css3的skew样式,可改变dom结点的倾斜程度,如下图(2)
    26     var vectorX = pxTopRight.subtract(pxTopLeft);
    27     var vectorY = pxBottomLeft.subtract(pxTopLeft);
    28     var skewX = Math.atan2( vectorX.y, vectorX.x );
    29     var skewY = Math.atan2( vectorY.x, vectorY.y );
    30 
    31     // LatLngBounds used for animations
    32     this._bounds = L.latLngBounds( this._map.layerPointToLatLng(pxBounds.min),
    33                                    this._map.layerPointToLatLng(pxBounds.max) );
    34 
    35     L.DomUtil.setPosition(div, pxBounds.min);
    36 
    37     div.style.width  = size.x + 'px';
    38     div.style.height = size.y + 'px';
    39 
    40     var imgW = this._rawImage.width;
    41     var imgH = this._rawImage.height;
    42     if (!imgW || !imgH) {
    43         return;    // Probably because the image hasn't loaded yet.
    44     }
    45 
    46     //5)计算图片的缩放比例
    47     var scaleX = pxTopLeft.distanceTo(pxTopRight)   / imgW * Math.cos(skewX);
    48     var scaleY = pxTopLeft.distanceTo(pxBottomLeft) / imgH * Math.cos(skewY);
    49 
    50     this._rawImage.style.transformOrigin = '0 0';
    51 
    52     this._rawImage.style.transform =
    53         'translate(' + pxTopLeftInDiv.x + 'px, ' + pxTopLeftInDiv.y + 'px)' +
    54         'skew(' + skewY + 'rad, ' + skewX + 'rad) ' +
    55         'scale(' + scaleX + ', ' + scaleY + ') ';
    56 },

                              图(2)

     由于图片完全由3个顶点的位置控制而没有约束,所以图片会出现长宽不等、邻边夹角变化导致的图片变形,因此要改造。

     2、如何改造,实现图片不变形,即保证图片是矩形且长宽比不变,如下图所示:

      

     

                                      图(3)

    图片ABCD是第一次初始化位置,A和C是2个可拖拽的顶点,每次拖拽只要保证B、D两个顶点是唯一确定的就能能保证图片不变形。

    向量AB在向量AC上的投影c的长度与向量AC的长度比值是常数,即 k1 = |c| / |AC|, k1是固定不变的。证明如下:

    |c| / |AC| = 向量AB · 向量AC /  (向量AC * 向量AC) = |AB|*|AC| *cosα/ |AC|^2 = |AB|/ |AC| * cosα = cosα ^2, 因为对固定长宽比例且不变形的矩形图片角α是不变的,所以|c| / |AC| =  k是常数。

    同理将向量AC逆时针旋转90度得到向量AU,向量AB在向量AU上的投影b的长度与向量AU的长度比值也是常数,即k2 = |b| / |AU|, k2是固定不变的。

    我们发现向量AB=向量b+向量c, 因此我们每次只需要计算出b,c两个向量就可以唯一确定向量AB,进而确定了顶点B的位置。

    同理,其它的顶点也这样计算。

    计算用到了余弦定理、平面向量数学知识,关键代码贴下:

    1)向量的数量积计算:

    1  function getDotProduction(point1, point2){
    2       return point1.x * point2.x + point1.y * point2.y;
    3  }

    2)向量顺时针旋转90度:

    1  function getClockWiseRotate90DegreePoint(point){
    2         return L.point([point.y, -point.x]);
    3     }

    3)计算新顶点的位置:

     1  function getCornerLatLng(point, bottomLeftMarkerLatLng, topRightMarkerLatLng){
     2         var boundsRectBottomLeft= L.point(0, 0);// origin
     3         var boundsRectTopRight= L.point(imgWidth, imgHeight);
     4         var diagonalVector = boundsRectTopRight.subtract(boundsRectBottomLeft); 
     5         var pV = point.subtract(boundsRectBottomLeft); 
     6         var rotate90V = getClockWiseRotate90DegreePoint(diagonalVector);
     7         var scaleX = getDotProduction(diagonalVector, pV) / getDotProduction(diagonalVector, diagonalVector);
     8         var scaleY = -getDotProduction(rotate90V, pV) / getDotProduction(rotate90V, rotate90V);
     9         var bLMarkerPx = L.Projection.SphericalMercator.project(bottomLeftMarkerLatLng);
    10         var tRMarkerPx = L.Projection.SphericalMercator.project(topRightMarkerLatLng);
    11         var vx = bLMarkerPx.add(tRMarkerPx.subtract(bLMarkerPx).multiplyBy(scaleX));
    12         var vy = getClockWiseRotate90DegreePoint(bLMarkerPx.subtract(tRMarkerPx)).multiplyBy(scaleY);
    13         var p = vx.add(vy);
    14         return L.Projection.SphericalMercator.unproject(p);
    15     }

    4)拖拽事件及reset方法:topRightMarker.on('drag dragend', repositionImage);

    bottomLeftMarker.on('drag dragend', repositionImage);
    
    function repositionImage() {
            var tRlnglat = topRightMarker.getLatLng();
            var bLlnglat = bottomLeftMarker.getLatLng();
            var imgWidth = overlay.options.w;
            var imgHeight = overlay.options.h;
            
            var c1 = getCornerLatLng(L.point(0, imgHeight), bLlnglat, tRlnglat);  // raw image topleft
            var c2 = getCornerLatLng(L.point(imgWidth, imgHeight), bLlnglat, tRlnglat);  // raw image topright
            var c3 = getCornerLatLng(L.point(0, 0), bLlnglat, tRlnglat);  // raw image bottomLeft
            var c4 = getCornerLatLng(L.point(imgWidth, 0), bLlnglat, tRlnglat);
    
            overlay.options.corners = [c1, c2, c3, c4];
    overlay.reposition(c1, c2, c3); }

    实现效果:

    项目地址:https://github.com/wxzen/Leaflet.ImageOverlay.Rotated-by-Two-Markers

  • 相关阅读:
    leetcode 347. Top K Frequent Elements
    581. Shortest Unsorted Continuous Subarray
    leetcode 3. Longest Substring Without Repeating Characters
    leetcode 217. Contains Duplicate、219. Contains Duplicate II、220. Contains Duplicate、287. Find the Duplicate Number 、442. Find All Duplicates in an Array 、448. Find All Numbers Disappeared in an Array
    leetcode 461. Hamming Distance
    leetcode 19. Remove Nth Node From End of List
    leetcode 100. Same Tree、101. Symmetric Tree
    leetcode 171. Excel Sheet Column Number
    leetcode 242. Valid Anagram
    leetcode 326. Power of Three
  • 原文地址:https://www.cnblogs.com/davidxu/p/10362996.html
Copyright © 2011-2022 走看看