关于javascript:矩阵转换:将SVG路径坐标转换为Leaflet坐标系

Matrix transform: Converting SVG path coordinates to Leaflet coordinate system

简短的版本:如何将SVG路径添加到Leaflet地图中,以便在地图坐标更改时(例如,在缩放或滑动时)路径会更新?

长版:您好,我有一个包含建筑物轮廓的地形图。对图像进行地理校正后,我使用Photoshop将栅格数据转换为SVG。我知道描述SVG周边的边界框的地理坐标,并且知道SVG路径元素的内部坐标。我想知道现在将上述SVG的path元素中描述的建筑物添加到Leaflet映射的最佳方法。

这是一个小提琴,以红色显示SVG图像的边界框,以蓝色显示建筑物:http://jsfiddle.net/duhaime/4vL925Lj/如您所见,建筑物尚未相对于边界框正确定向。

我对齐建筑物的最初计划是使用一次性脚本将SVG坐标系中的路径元素转换为纬度长的坐标,然后使用我用来绘制边界框的折线函数在地图上绘制建筑物:

1
2
3
4
var polyline = L.polyline(
  [upperLeft, upperRight, lowerRight, lowerLeft, upperLeft],
  {color: 'red', className: 'bounding-box', weight: 2}
).addTo(map);

这种方法的问题在于,Leaflet折线无法绘制Bezier曲线,该曲线存在于上面的SVG路径元素中。作为一种解决方法,我认为我可以对Bezier曲线使用线性近似,尽管这可能会花费很多工作。

最终,我意识到上面小提琴中的边界框的SVG使用Bezier曲线,这给了我一个想法,我可以改为使用矩阵变换将建筑物SVG的坐标空间转置为Leaflet坐标空间。上面的小提琴使用样本矩阵变换css规则来变换建筑物层。

在进一步探究这个兔子洞之前,我想问:别人认为将描述SVG中上方建筑物的路径添加到小提琴的Leaflet地图中的最佳方法是什么?我将非常感谢其他人在这个问题上可以提供的任何建议!

进展:我决定简化此问题,并弄清楚如何使用矩阵变换将一个div(" A")转换为另一div(" B")的宽高比。为此,我制作了一个小的Python脚本,将输入div A的像素坐标和所需的输出div B的像素坐标作为输入。该脚本生成变换矩阵X,使得AX = B。该脚本在内部进行了记录,并附带一个小提琴。

我还提出了一个要点,该要点导出了转换矩阵,以将点从SVG空间投影到适当的经纬度坐标。在最坏的情况下,我可以对SVG路径元素进行分区,将每个点的点积与变换矩阵一起使用,并用传单绘制折线以绘制建筑物。那会失去贝塞尔曲线的意志...


这花了很多时间,但我找到了解决方案。

阅读一遍后,我意识到,可以通过标识变换矩阵,然后将输入空间中的每个点相乘,将一个坐标空间(例如SVG坐标空间)中的点移置到另一个坐标空间(例如lat / long坐标空间)中。 SVG)。此操作会将给定点转置到地图上的适当位置。

我写了这个脚本来计算所需的变换矩阵。该脚本将SVG的边界框坐标和从中提取SVG元素的经过地理校正的Geotiff的边界框坐标作为参数。该脚本生成了转换矩阵,并显示了如何将SVG空间中的点乘以矩阵以找到其适当的经/纬度坐标。

有一个陷阱-需要在SVG中表示点而不进行任何类型的CSS转换。为了直观地表示SVG点在SVG中的位置,我使用此工具将SVG中的路径元素转换为多边形元素,对此源公开可用。

如果其他人需要完成类似的任务,这是我使用的完整工作流程:

  • 查找感兴趣的栅格地图(jpg / tiff)。
  • 使用QGIS,ArcGIS或MapWarper对地图进行地理校正。这将产生一个土工。
  • 下载并安装GDAL,这是一个具有Python绑定的强大地理空间库。
  • 在Adobe Illustrator中的Geotiff中对感兴趣的特征(例如建筑物)运行图像跟踪。这将产生一个矢量层。将矢量图层另存为SVG文件。
  • 如果您保存的SVG中有任何或其他几何形状,请将其转换为路径并重新保存。
  • 标识SVG的边界框坐标和作为输入到Illustrator的输入的Geotiff。后者的边界框可以通过运行gdalinfo {your-geotiff-file.tif}从GDAL获得
  • 在上面引用的脚本中内联那些边界框坐标。然后将SVG划分为个元素的数组,对于每个元素,将多边形拆分为点数组。将每个点乘以变换矩阵即可找到该点的经/纬度位置。
  • 将每个形状的每个点保存为适当的geojson格式,以便可以将数据加载到客户端中。
  • 对于它的价值,这里是我用于生成矩阵变换并将元素点转换为经/长空间的脚本。请注意,您需要根据情况更新脚本中的某些路径,例如该脚本将输出geojson推送到我的实验室管理的S3存储桶中:)

    我希望这对发现自己要完成这项任务的其他人有所帮助!坦白地说,我为此付出了如此多的努力感到惊讶,并且我敢肯定必须有一个更加优雅的工作流程...


    我将此功能添加到了以前在Leaflet Maps上完成的工作。这可能适用于您的应用程序。
    请参阅:www.svgdiscovery.com/K/K04A.htm

    这使用了"传单地图"和导入的SVG路径共有的两个关键点。


    以下示例使用折线值来演示此方法,因为该方法将在"传单"地图的缩放/平移期间应用于svg路径和其他形状。
    基本上,将创建一个SVG层,并在其中添加所有svg元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    <head>
      Untitled
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css" />
    <style type="text/css">
    <!--
    #map {
      width: 500px;
      height: 500px;
    }

    -->
    </style>

    </head>

    <body>




    </body>


    // create the map object itself
    centerCoordinates = new L.LatLng(41.307, -72.928);

    var map = new L.Map("map", {
      center: centerCoordinates,
      zoom: 14,
      zoomControl: false
    });

    // position the zoom controls in the bottom right hand corner
    L.control.zoom({
      position: 'bottomright',
      zoom: 14,
      maxZoom: 20,
      minZoom: 12,
    }).addTo(map);



    map.addLayer(new L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
      attribution: '&copy; OpenStreetMap &copy; CartoDB',
      subdomains: 'abcd',
      maxZoom: 19
    }));

    // specify the coordinates of the overlay's bounding box
    var upperLeft = L.latLng(41.329785, -72.927220);
    var lowerLeft = L.latLng(41.304414, -72.945686);
    var upperRight = L.latLng(41.319186, -72.903268);
    var lowerRight = L.latLng(41.293816, -72.921718);
    /*
    // create a red polyline from an array of LatLng points
    var polyline = L.polyline(
      [upperLeft, upperRight, lowerRight, lowerLeft, upperLeft], {
        color: 'red',
        className: 'bounding-box',
        weight: 2
      }
    ).addTo(map);
    */


      //---CREATE SVG---
        map._initPathRoot() //---creates an svg layer---
        var MySVG=document.querySelector("svg") //---access svg element---

        var NS="http://www.w3.org/2000/svg"
        //---place svg elems in here---
        var SvgElemG=document.createElementNS(NS,"g")
        MySVG.appendChild(SvgElemG)

         //---zooming the map's SVG elements---
        map.on("viewreset", adjustSVGElements);

    //---add svg polygon---
    var polygon=document.createElementNS(NS,"polyline")
    polygon.setAttribute("stroke-width",1)
    polygon.setAttribute("fill","none")
    polygon.setAttribute("stroke","red")
     //---convert latLng to x,y---
    var xyUL=map.latLngToLayerPoint(upperLeft)
    var xyLL=map.latLngToLayerPoint(lowerLeft)
    var xyLR=map.latLngToLayerPoint(lowerRight)
    var xyUR=map.latLngToLayerPoint(upperRight)
    var points=[xyUL.x,xyUL.y,xyLL.x,xyLL.y,xyLR.x,xyLR.y,xyUR.x,xyUR.y,xyUL.x,xyUL.y].toString()
    polygon.setAttribute('points',points)

    //--required for zoom---
    var svgPnt=L.point(0,0) //--reference for translate--
    var latLng=map.layerPointToLatLng(svgPnt)
    var lat=latLng.lat
    var lng=latLng.lng
    polygon.setAttribute("lat",lat)
    polygon.setAttribute("lng",lng)
    //---retain the zoom level at its creation--
    polygon.setAttribute('initZoom',map.getZoom())

    SvgElemG.appendChild(polygon)

    //--- on map zoom - fired via map event: viewreset---
    function adjustSVGElements()
    {
        var mapZoom=map.getZoom()

        var svgElems=SvgElemG.childNodes
        for(var k=0;k<svgElems.length;k++)
        {
            var svgElem=svgElems.item(k)
            var lat=parseFloat(svgElem.getAttribute("lat"))
            var lng=parseFloat(svgElem.getAttribute("lng"))
            var latLng= new  L.latLng(lat, lng)
            var transX=map.latLngToLayerPoint(latLng).x
            var transY=map.latLngToLayerPoint(latLng).y
            //---trash previous transform---
            svgElem.setAttribute("transform","") //---required for IE
            svgElem.removeAttribute("transform")

            var transformRequestObj=MySVG.createSVGTransform()
            var animTransformList=svgElem.transform
            //---get baseVal to access/place object transforms
            var transformList=animTransformList.baseVal
            //---translate----
            transformRequestObj.setTranslate( transX,  transY)
            transformList.appendItem(transformRequestObj)
            transformList.consolidate()
           //---scale---
            var initZoom=parseFloat(svgElem.getAttribute("initZoom"))
            var scale = (Math.pow(2, mapZoom)/2)/(Math.pow(2, initZoom)/2);
            transformRequestObj.setScale(scale,scale)
            transformList.appendItem(transformRequestObj)
            transformList.consolidate()
        }


    }