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

时间:2016-07-02 02:08:45

标签: javascript matrix svg leaflet linear-algebra

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

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

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

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

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

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

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

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

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

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

3 个答案:

答案 0 :(得分:2)

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

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

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

有一个问题 - 人们需要在没有任何CSS转换的情况下表示SVG中的点。为了直观地表示SVG中SVG点的位置,我使用this tool将SVG中的路径元素转换为多边形元素,source是公开可用的。

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

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

&#13;
&#13;
<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>
&#13;
&#13;
&#13;