【问题标题】:Matrix transform: Converting SVG path coordinates to Leaflet coordinate system矩阵变换:将 SVG 路径坐标转换为 Leaflet 坐标系
【发布时间】:2016-11-04 11:34:23
【问题描述】:

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

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

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

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

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

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

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

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

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

我还制作了一个gist,它派生了变换矩阵,以将 SVG 空间中的点投影到适当的 lat、lng 坐标中。最坏的情况是,我可以分割 SVG 路径元素,将每个点的点积与变换矩阵相乘,然后用传单绘制折线来绘制建筑物。不过,这将失去贝塞尔曲线......

【问题讨论】:

  • 你看过 D3 + Leaflet 的例子吗?我不确定,但认为它可能涵盖您的用例:bost.ocks.org/mike/leaflet
  • 谢谢@Snorfalorpagus!在该示例中,Mike 读取 geojson 数据,但我有尚未包含地理信息的原始 SVG 数据...
  • 好吧,也许我会和你一起去兔子洞。您的原始 svg 中还需要另外一件事情:包括当前街道的中心线,该中心线也存在于您的历史绘图中。
  • 遇到了这个声称在 Leaflet 中实现贝塞尔曲线的项目:github.com/elfalem/Leaflet.curve
  • fiddle 上使用的 svg (2000_Cooper_Robertson_G_cutout1_full.svg) 不再可用。

标签: javascript matrix svg leaflet linear-algebra


【解决方案1】:

以下示例使用您的折线值来演示该方法,因为它将在 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>

【讨论】:

  • 谢谢@FrancisHemsher!也许我忽略了一些东西,但这种方法不是理所当然地认为我们拥有要映射的对象的经纬度坐标吗?情况似乎是这样,因为您正在利用左上角、右上角等坐标进行初始放置。我的问题是我没有建筑物本身的这些坐标——我只知道边界框的经纬度坐标和 SVG 文件中描述建筑物位置的对象的坐标。
  • 我会尝试处理 rawsvg 文件的 viewBox 和路径的初始位置...我会告诉你的。
  • 如何确定初始折线点的纬度/经度?
  • 折线边界框的坐标给了我,我只画了连接这些点的框。
  • @duhaime 我不能以任何优雅的方式来硬塞它。对不起。我想知道将 Leaflet 与 OSMBuildings 一起使用,并突出显示特定的建筑物,是否会是一种更合理的方法......
【解决方案2】:

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

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

【讨论】:

  • 谢谢弗朗西斯!当您在 svg 中内联 lat lng 位置时,您如何识别这些位置(即它们是路径元素的左上角,还是其他?)每个路径元素是否需要 lat lng 坐标?我正在尝试让您的示例与上面的 svg 一起使用...
  • 这是我用来尝试重现您的示例的小提琴:jsfiddle.net/dduhaime/92xod5tt/4
  • Chrome 浏览器最近(版本 48)弃用了 pathSegList,现在需要 SVGPathSeg API polyfill 转到 github.com/progers/pathseg 获取副本,并在您的文件 脚本块文件中调用它call: type="text/javascript" src='pathSeg.js' 这是函数 removePathPointTransform(path) 所需要的
  • 或者,您可以远程调用我的站点...svgDiscovery.com/K/pathSeg.js
  • 我用你的路径运行了这个,其中一些运行良好。但是,d 值结构通过 removePathPointTransform(path) 在其中许多上崩溃。第一个“崩溃”路径元素: 我无法确定哪些值没有被处理。我将只测试这条路径。
【解决方案3】:

这花了很多心思,但我找到了解决办法。

阅读后,我意识到可以通过识别一个变换矩阵,然后将每个点乘以该变换矩阵的输入空间(SVG)。此操作会将给定点转置到地图中的适当位置。

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

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

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

  1. 查找感兴趣的栅格地图 (jpg/tiff)。
  2. 使用 QGIS、ArcGIS 或 MapWarper 对地图进行地理校正。这会产生一个 geotiff。
  3. 下载并安装GDAL,这是一个具有 Python 绑定功能的强大地理空间库。
  4. 在 Adob​​e Illustrator 中对 Geotiff 中感兴趣的特征(例如建筑物)运行图像跟踪。这会产生一个矢量图层;将矢量图层保存为 SVG 文件。
  5. 如果您保存的 SVG 中有任何 &lt;rect&gt; 或其他几何形状,请将它们转换为路径并重新保存。
  6. 确定 SVG 的边界框坐标和作为 Illustrator 输入的 Geotiff。后者的边界框可以通过运行gdalinfo {your-geotiff-file.tif}从GDAL获得
  7. 在上面引用的脚本中内嵌这些边界框坐标。然后将您的 SVG 划分为 &lt;polygon&gt; 元素的数组,并为每个元素将多边形拆分为点数组。将每个点乘以变换矩阵以找到该点的纬度/经度位置。
  8. 将每个形状的每个点保存为适当的 geojson 格式,以便您可以将数据加载到客户端。

对于它的价值,我用于生成矩阵变换和将&lt;polygon&gt; 元素点转置到纬度/经度空间的脚本是here。请注意脚本中的一些路径需要根据您的情况进行更新——例如该脚本将输出 geojson 推送到我的实验室管理的 S3 存储桶:)

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-24
    • 1970-01-01
    • 2021-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多