首页 文章

矩阵变换:将SVG路径坐标转换为Leaflet坐标系

提问于
浏览
9

简短版本:如何将SVG路径添加到Leaflet Map ,以便在 Map 坐标更改时路径将更新(例如,在缩放更改或幻灯片上)?

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

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

我最初计划对齐建筑物是使用一次性脚本将路径元素从SVG坐标系转换为lat,long坐标,然后使用我用于绘制边界框的折线函数在 Map 上绘制建筑物:

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

这种方法的问题在于Leaflet折线无法绘制Bezier曲线,这些曲线存在于上面的SVG路径元素中 . 作为一种解决方法,我认为我可以对贝塞尔曲线使用线性近似,尽管这可能会成为一个相当大的工作量 .

最后我意识到上面小提琴中的边界框的SVG使用贝塞尔曲线,这让我想到我可能会使用矩阵变换将构建SVG的坐标空间转换到Leaflet坐标空间 . 上面的小提琴使用样本矩阵变换css规则来变换建筑物层 .

在深入了解这个兔子洞之前,我想问一下:其他人认为最好的方法是将上述SVG中的建筑物的路径添加到小提琴中的Leaflet Map 中?我将非常感谢其他人可以就此问题提供的任何建议!

PROGRESS: 我决定简化这个问题并弄清楚如何使用矩阵变换将一个div("A")转换为另一个div的纵横比("B") . 在这样做时,我做了一个小的Python script,它将输入div A 's pixel coordinates and the desired output div B'的像素坐标作为输入 . 该脚本生成变换矩阵X,使得AX = B.该脚本在内部记录并附带fiddle .

我还创建了一个gist,它导出变换矩阵,将SVG空间中的点投射到适当的lat,lng coords中 . 最糟糕的情况是,我可以对SVG路径元素进行分区,使用变换矩阵获取每个点的点积,并使用传单绘制折线以绘制建筑物 . 那将失去Bezier曲线......

3 回答

  • 2

    这需要相当多的思考,但我找到了解决方案 .

    在阅读之后,我意识到可以通过识别变换矩阵,然后通过乘以输入空间中的每个点,将点从一个坐标空间(例如SVG坐标空间)转换到另一个(例如,纬度/长坐标空间)( SVG)通过该变换矩阵 . 此操作会将给定点转置到 Map 中的适当位置 .

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

    在SVG中有's a catch--one needs to have the points in the SVG represented without any kind of CSS transformation. To get a straightforward representation of a SVG point'的位置,我使用this tool将SVG中的路径元素转换为多边形元素,source可以公开使用 .

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

    • 找到感兴趣的栅格 Map (jpg / tiff) .

    • 使用QGIS,ArcGIS或MapWarper对 Map 进行地理定位 . 这产生了一个地理位置 .

    • 下载并安装GDAL,一个带有Python绑定的强大地理空间库 .

    • 在Adobe Illustrator中的Geotiff(例如建筑物)中感兴趣的要素上运行图像跟踪 . 这产生了一个矢量层;将矢量图层保存为SVG文件 .

    • 如果保存的SVG中有任何 <rect> 或其他几何形状,请将它们转换为路径并重新保存 .

    • 确定SVG和Geotiff的边界框坐标作为Illustrator的输入 . 后者的边界框可以通过运行 gdalinfo {your-geotiff-file.tif} 从GDAL获得

    • 在上面引用的脚本中内联那些边界框坐标 . 然后将SVG分割为 <polygon> 元素数组,并为每个元素将多边形拆分为一个点数组 . 将每个点乘以变换矩阵以找到该点的纬度/长度位置 .

    • 将每个形状的每个点保存为适当的geojson格式,以便将数据加载到客户端 .

    对于's worth, the script I' m用于生成矩阵变换以及将 <polygon> 元素点转换为纬度/长度空间的内容是here . 请注意,脚本中的某些路径需要根据您的情况进行更新 - 例如 . 该脚本将输出geojson推送到我实验室管理的S3存储桶:)

    我希望这可以帮助那些发现自己面临这项任务的人!坦白地说,我花了很多精力,我很确定必须有更优雅的工作流程......

  • 1

    我将此功能添加到我之前在Leaflet Maps上完成的工作中 . 这可能适用于您的申请 . 见:www.svgdiscovery.com/K/K04A.htm

    这使用了Leaflet Map和导入的SVG路径共有的两个关键点 .

  • 0

    以下示例使用折线值来演示在Leaflet贴图的缩放/平移期间应用于svg路径和其他形状的方法 . 基本上创建SVG层并且在其中添加所有svg元素 .

    <head>
      <title>Untitled</title>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js" ></script>
    <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>
    <div id="map"></div>
    
    
    
    </body>
    <script>
    
    // 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; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
      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()
        }
    
    
    }
    
    </script>
    

相关问题