Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matrix transform: Converting SVG path coordinates to Leaflet coordinate system

The short version: How can I add SVG paths to a Leaflet map in such a way that the paths will update when the map coordinates change (e.g. on zoom change or slide)?

The long version: Hello, I have a topographic image that contains building outlines. After geo-rectifying the image I used Photoshop to convert the raster data to SVG. I know the geo-coordinates of the bounding box that describe the SVG perimeter, and know the internal coordinates of SVG path elements. I'm wondering the best way to now add the buildings described in the path elements of the SVG above to a Leaflet map.

Here's a fiddle that shows the bounding box of the SVG image in red and the buildings in blue: http://jsfiddle.net/duhaime/4vL925Lj/ As you can see, the buildings are not yet properly oriented with respect to the bounding box.

My initial plan to align the buildings was to use a one-time script to convert the path elements from the SVG coordinate system into lat, long coordinates, then draw the buildings on the map using the polyline function I used to draw the bounding box:

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

The trouble with this approach is that Leaflet polylines can't draw Bezier curves, which are present in the SVG path elements above. As a workaround, I thought I could use linear approximations for the Bezier curves, though this might become a decent amount of work.

Eventually I realized the SVG for the bounding box in the fiddle above uses Bezier curves, which gave me the idea that I might instead use matrix transforms to transpose the coordinate space of the building SVGs into the Leaflet coordinate space. The fiddle above uses a sample matrix transform css rule to transform the building layer.

Before going further down this rabbit hole, I wanted to ask: What do others think is the best way to add the paths that describe the buildings in the SVG above to the Leaflet map in the fiddle? I would be very grateful for any advice others can offer on this question!

PROGRESS: I decided to simplify this problem and figure out how to use a matrix transform to transform one div ("A") into the aspect ratio of another div ("B"). In doing so, I made a small Python script that takes as input the input div A's pixel coordinates and the desired output div B's pixel coordinates. This script generates the transform matrix X such that AX=B. The script is documented internally and has an accompanying fiddle.

I also made a gist that derives the transform matrix to project points from the SVG space into proper lat, lng coords. Worst case scenario, I can partition the SVG path elements, take the dot product of each point with the transform matrix, and draw polylines with leaflet to plot the buildings. That will of lose the Bezier curves though...

like image 922
duhaime Avatar asked Jul 02 '16 02:07

duhaime


2 Answers

This took quite a bit of thinking, but I've found a solution.

After reading around, I realized that one can transpose points from one coordinate space (e.g. the SVG coordinate space) to another (e.g. and the lat/long coordinate space) by identifying a transform matrix, then by multiplying each point in the input space (SVG) by that transform matrix. This operation will transpose the given point into the appropriate location in the map.

I wrote this script to calculate the required transform matrix. The script takes as parameters the SVG's bounding box coordinates and the bounding box coordinates of the georectified geotiff from which the SVG elements were extracted. The script generates the transform matrix and shows how one can multiply points in the SVG space by the matrix to find their appropriate lat/long coordinates.

There'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's location within the SVG, I converted path elements in the SVG to polygon elements using this tool, for which the source is openly available.

In case others need to accomplish a similar task, here was the full workflow I used:

  1. Find a raster map (jpg/tiff) of interest.
  2. Georectify the map with QGIS, ArcGIS, or MapWarper. This produces a geotiff.
  3. Download and install GDAL, a powerful geospatial library with Python bindings.
  4. Run an image trace on a feature of interest in your Geotiff (e.g. buildings) in Adobe Illustrator. This produces a vector layer; save the vector layer as a SVG file.
  5. If there are any <rect> or other geometric shapes in your saved SVG, convert them to paths and re-save.
  6. Identify the bounding box coordinates of your SVG and the Geotiff you fed as input to Illustrator. The bounding box of the latter can be obtained from GDAL by running gdalinfo {your-geotiff-file.tif}
  7. Inline those bounding box coordinates in the script referenced above. Then partition your SVG into an array of <polygon> elements, and for each, split the polygon into an array of points. Multiply each point by the transform matrix to find the point's lat/long position.
  8. Save each point of each shape to an appropriate geojson format so you can load the data into the client.

For what it's worth, the script I'm using for generating the matrix transform and for transposing <polygon> element points into lat/long space is here. Please note some paths in the script would need to be updated for your situation--e.g. the script pushes the output geojson to an S3 bucket my lab manages :)

I hope this helps someone else who finds themselves confronted with this task! I'm frankly amazed this took so much effort, and am pretty sure there must be a more elegant workflow...

like image 147
duhaime Avatar answered Sep 30 '22 04:09

duhaime


I added this feature to work I've previously done on Leaflet Maps. This may apply to your application. See: www.svgdiscovery.com/K/K04A.htm

This uses two key points that are common to both the Leaflet Map and the imported SVG paths.

like image 27
Francis Hemsher Avatar answered Sep 30 '22 05:09

Francis Hemsher