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中的路径元素转换为多边形元素,对此源公开可用。
如果其他人需要完成类似的任务,这是我使用的完整工作流程:
对于它的价值,这里是我用于生成矩阵变换并将
我希望这对发现自己要完成这项任务的其他人有所帮助!坦白地说,我为此付出了如此多的努力感到惊讶,并且我敢肯定必须有一个更加优雅的工作流程...
我将此功能添加到了以前在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: '© OpenStreetMap © 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() } } |