Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

jreiberkyle
Copy link

Changes

  • add BaseGeometry.svg_path_element(yflip='transform', **kwargs)
  • add pre-commit to requirements-dev.txt
  • add tests for _repr_svg_ and svg_path_element

Use Case

I have been using shapely for generating paths for laser cutting. The input for laser cutting is SVG. The current options for SVG export from BaseGeometry are the subclass implementation of svg() and _repr_svg_(). The subclass implementation of svg() outputs the svg as a string that includes baked-in styling and does not account for the y-axis flip between shapeley (y-up) and svg (y-down). _repr_svg_() is a private method so it doesn't feel great to use it and it still returns the output as a string with styling baked in.

Proposed Solution

This PR adds a third option for svg export: an ElementTree Element with the option of y-axis flip (defaults to implementing the flip). The signature is:

svg_path_element(yflip='transform', **kwargs)

It is a simple wrapper around svg() with logic to handle y-flip and logic to convert to an Element. The kwargs are passed through to svg() and yflip is specified as None or "transform". The reason yflip is None and "transform" is because originally I had logic for a third option - "scale". This would use shapely's scale function to actually flip the coordinates. However, on revisit I couldn't find clarity on where this approach would be needed. It's a judgement call whether we want to keep this flexible to allow for more options in the future without changing user interface or if we want to simplify and change yflip to a bool - I'm happy to go either way.

Usage

from xml.etree import ElementTree as ET

from shapely import Polygon

SVG_NAMESPACE = "http://www.w3.org/2000/svg"
SVG_XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"
ET.register_namespace("", SVG_NAMESPACE)
ET.register_namespace("xlink", SVG_XLINK_NAMESPACE)

# triangle that is lower-left half of a unit square
# with lower-left corner offset from origin by +1 in x and y dir.
coords = ((1,1),(1,2),(2,1),(1,1))
polygon = Polygon(coords)

# create SVG
doc = ET.Element("svg", xmlns=SVG_NAMESPACE)
doc.set("xmlns:xlink", SVG_XLINK_NAMESPACE)
doc.set("preserveAspectRatio", "xMinYMin meet")

xmin, ymin, xmax, ymax = polygon.bounds
doc.set("width", "100.0")
doc.set("height", "100.0")
doc.set("viewBox", "1,1,1,1")
scale_factor = 0.010 # change the stroke width
doc.append(
    polygon.svg_path_element(yflip='transform', scale_factor=scale_factor)
)

# SVG string for piping into svg file
print(ET.tostring(doc, encoding='unicode'))

Limitations

  • Because this is a wrapper around svg(), the styling is still baked in but at least it is much easier to traverse the tree to change it at will.
  • The output of svg_path_element only describes the svg path, not the entire svg document. It is left to the user to figure out how to make the document and it's not trivial (see usage). But the argument can be made that creating svg documents is outside of the scope of shapely. If not, we can consider a function I included in an earlier PR (RFC: add svg export support to basegeometry #1295, superseded by this one):
def svg_doc_element(elements, width, height, xmin, ymin, xmax, ymax):
    doc = ET.Element("svg", xmlns=SVG_NAMESPACE)
    doc.set("xmlns:xlink", SVG_XLINK_NAMESPACE)
    doc.set("width", f"{width}")
    doc.set("height", f"{height}")
    doc.set("viewBox", f"{xmin} {ymin} {xmax - xmin} {ymax - ymin}")
    doc.set("preserveAspectRatio", "xMinYMin meet")

    for e in elements:
        doc.append(e)
    return doc

The primary benefit of this function being that it handles the xml headers

Alternatives

The truth is that someone could work around not having svg_path_element with e.g.:

point = Point(1,1)
path = ET.fromstring(point.svg())
_, ymin, _, ymax = point.bounds
path.set("transform", f"matrix(1,0,0,-1,0,{ymax + ymin})")

So this method is not entirely necessary.

However, including it does allow simplification of the _repr_svg_() function, which now focuses on constructing the SVG document instead of actually performing the y-axis transformation.

…f svg, add tests for svg_path_element and add tests to ensure __repr_svg output remains valid
@jreiberkyle
Copy link
Author

Going through comments I found the possible use case for "scale" yflip: #1196 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant