Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVG-Based Symbolizers #320

Closed
artemp opened this issue Oct 11, 2011 · 34 comments
Closed

SVG-Based Symbolizers #320

artemp opened this issue Oct 11, 2011 · 34 comments
Milestone

Comments

@artemp
Copy link
Member

artemp commented Oct 11, 2011

It would be very nice to allow for arbitrary SVG shields as symbolizers on areas, points, and lines.

This would allow (e.g.) US Interstate highway shields and other similar features.

See also: https://lists.berlios.de/pipermail/mapnik-users/2009-April/001809.html

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] We've been looking at implementing this. I'm fairly new to the mapnik codebase, but after a bit of poking here's a plan:

  1. Read SVG symbols:
    • SVG parsing using Gnome's [http://librsvg.sourceforge.net/ librsvg]
  2. Image rendering abstraction to get away from ImageData32 (the current "symbol" data class):
    • Something like an AbstractImage class, wrapping width, height and rendering functions (ImageData32 and VectorImage can derive from this)
    • Make calls to point_symbolizer.get_image() return an AbstractImage (i.e. a VectorImage for SVG symbolizers, and a ImageData32 for raster symbolizers)
  3. Actually render the vector data:
    • Overload Mapnik's cairo_context::add_image() for VectorImage to render the abstracted image as a cairo surface instead of raster data. librsvg can already draw into a cairo surface.
    • Initially make the AGG renderer rely on Cairo for SVG rendering. If someone really wants to make this cleaner they could extend librsvg to render to AGG. That looks like too much work for little gain at the moment. So, we could:
      • Render a surface using Cairo
      • Rasterize surface and pass it back to AGG as an ImageData32

This means:

  • SVG symbol support would depend on both librsvg and cairo
  • It'd be a backwards-compatible extension of the existing way to use images, by adding support for type=SVG in addition to existing PNG support.
  • It'll apply to both point and shield symbolizers

Any feedback before I start writing code?

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[rcoup] Stuff around extending PointSymbolizer to support primitives:

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] cc'ing tom as I know that he may have ideas on this, particularly as it relates to cairo usage

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[rcoup] MapServer is doing a GSoC project currently around adding SVG symbol support, might be worth getting in touch with Kiran/Daniel about their plans:

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] the thread breaks on the month, so notice the may group:
http://lists.osgeo.org/pipermail/mapserver-dev/2009-May/008735.html

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Craig, great stuff - I've noticed your work at github. So, currently Cairo is an optional dependency. Will librsvg usage use make it such that Cairo is a mandatory dependency?

Also, just curious which other svg libs you reviewed and what makes librsvg the best?

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] FYI, see also #343 and #155 which both relate/depend upon scaling of symbols and will be much better with support for svg symbols.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Ah, also just noticed in your plan notes a potential dependecy of the AGG renderer on Cairo for svg rendering. It will be important to make sure this does not incur a performance hit for general rendering without svg symbols when using AGG.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[iandees] It would be extremely handy if this supported the ability to use some field somewhere to specify which SVG file to use or the contents of some text field in the SVG.

For example, it'd be nice to only have to use one SVG file for US Interstates and say that data value X belongs in the center (oh, and scale the width of the SVG to fit the number).

This is just a nice-to-have at this point, though.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] springmeyer:
The Cairo and RSVG dependencies will still be optional, but obviously SVG symbol rendering will depend on having both.
The AGG renderer doesn't do anything more than previously for non-SVG symbols.

iandees:
I agree, but that's probably something that should be done for all image types rather than just SVG symbols. #155 discusses it a little bit, but it should really have its own ticket if this is something we're going to pursue.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Replying to [comment:10 cdestigter]:

springmeyer:
The Cairo and RSVG dependencies will still be optional, but obviously SVG symbol rendering will depend on having both.
The AGG renderer doesn't do anything more than previously for non-SVG symbols.

Great, sounds good. Thanks for the clarification.

iandees:
I agree, but that's probably something that should be done for all image types rather than just SVG symbols. #155 discusses it a little bit, but it should really have its own ticket if this is something we're going to pursue.

Yes, we should close #155 soon and re-open a few tickets clarifying potential goals for raster scaling.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] I've run out of time for my part of this. My latest code will be on github momentarily.

What works and doesn't work:

  • SVG symbols render in both Cairo and AGG renderers (the AGG implementation relies on Cairo being available)
  • The width and height parameters to PointSymbolizer are now optional. If not specified the auto-detected size from the image will be used.
  • Proportional and non-proportional scaling (using width=N, height=0 or height not specified) work in '''Cairo''' only. I haven't had time to implement this in AGG.
  • allow_overlap isn't working properly for scaled symbols.
  • I haven't done any testing compiling ''without'' Cairo support, again due to time constraints.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] Latest source is now at http://github.com/craigds/mapnik/tree/svg

The attached zip file includes some handy testing code (generates 32 map images for different input symbols, scales and allow_overlap values using both renderers).

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] "I've run out of time" = I'll get back to this in a few weeks, but not before. If anyone feels like moving this forward in the meantime, that'd be great.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Craig,

I had a chance to pull down from git, build, and run your tests.

  • build was flawless. At first librsvg was skipped, then I did export PKG_CONFIG_PATH=/opt/local/lib/pkgconfig:$PKG_CONFIG_PATH and it was found during configure
  • nosetests worked without error
  • the auto-detection of sizes and switching of height and width to **kwargs worked nicely. Be aware however that the 'args()' syntax in boost python is only available for boost 1.35 and greater (see r1084), so we'll need to add that check to the Scons compile.
  • I ran your testcase without error
  • Scaling worked great for the SVG file loaded and rendered via Cairo - I assume that the red PNG dot is not able/supposed to scale even when rendered with Cairo?
  • I also modified the tests to render to a PNG with Cairo (test script pasted below)
  • I see what you mean about allow_overlap not quite working when symbols are scaled - although its debatable whether it needs to act on the scaled symbols. I can see situations where the slight overlap of scaled symbols may be desirable.
  • When compiled without librsvg support the nosetests pass as normal but I get a bus error when running the tests (likely because of the **kwargs stuff - so we likely don't need to worry about this now). (backtrace pasted below)

updated testing script snippet:

{{{
def render_cairo(sym, name, width=None, height=None, allow_overlap=False, image=True):
sym.allow_overlap = allow_overlap
fname = '%sx%s_%s_%s_overlap_to_cairo' % (width or '-', height or '-', name, allow_overlap and 'allow' or 'no')
print "Rendering %s" % fname
m = get_map(sym)
if image:
s = cairo.ImageSurface(cairo.FORMAT_ARGB32, m.width, m.height)
mapnik.render(m, s)
f = os.path.join(output_path,'%s.png' % fname)
s.write_to_png(f)
s.finish()
else:
f = open(os.path.join(output_path, '%s.svg' % fname), 'wb')
s = cairo.SVGSurface(f.name, m.width, m.height)
mapnik.render(m, s)
s.finish()
f.close()
}}}

backtrace of crash when running tests without librsvg compiled in:

{{{
Process: Python [4887]
Path: /Library/Frameworks/Python.framework/Versions/2.5/Resources/Python.app/Contents/MacOS/Python
Identifier: Python
Version: ??? (???)
Code Type: X86 (Native)
Parent Process: bash [1953]

Date/Time: 2009-05-26 19:29:02.890 -0700
OS Version: Mac OS X 10.5.6 (9G55)
Report Version: 6

Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_PROTECTION_FAILURE at 0x0000000000000000
Crashed Thread: 0

Thread 0 Crashed:
0 libmapnik.dylib 0x00c23462 mapnik::agg_renderermapnik::Image32::process(mapnik::point_symbolizer const&, mapnik::feature<mapnik::geometry<mapnik::vertex<double, 2> >, boost::shared_ptrmapnik::raster > const&, mapnik::proj_transform const&) + 114
1 _mapnik.so 0x009c1ffb mapnik::feature_style_processormapnik::agg_renderer<mapnik::Image32 >::apply_to_layer(mapnik::Layer const&, mapnik::agg_renderermapnik::Image32&, mapnik::projection const&, double) + 2507
2 _mapnik.so 0x009b8a7f render(mapnik::Map const&, mapnik::Image32&, unsigned int, unsigned int) + 335
3 _mapnik.so 0x009b8d0c render_to_file1(mapnik::Map const&, std::basic_string<char, std::char_traits, std::allocator > const&, std::basic_string<char, std::char_traits, std::allocator > const&) + 76
4 _mapnik.so 0x009c4822 boost::python::objects::caller_py_function_impl<boost::python::detail::caller<void ()(mapnik::Map const&, std::basic_string<char, std::char_traits, std::allocator > const&, std::basic_string<char, std::char_traits, std::allocator > const&), boost::python::default_call_policies, boost::mpl::vector4<void, mapnik::Map const&, std::basic_string<char, std::char_traits, std::allocator > const&, std::basic_string<char, std::char_traits, std::allocator > const&> > >::operator()(object, object) + 306
5 ...python-xgcc40-mt-1_39.dylib 0x00f11e74 boost::python::objects::function::call(object, object) const + 468
6 ...python-xgcc40-mt-1_39.dylib 0x00f136e5 boost::detail::function::void_function_ref_invoker0<boost::python::objects::(anonymous namespace)::bind_return, void>::invoke(boost::detail::function::function_buffer&) + 37
7 ...python-xgcc40-mt-1_39.dylib 0x00f1a719 boost::function0::operator()() const + 41
8 ...python-xgcc40-mt-1_39.dylib 0x00f19b0c boost::python::detail::exception_handler::operator()(boost::function0 const&) const + 204
9 _mapnik.so 0x009b9ee4 boost::detail::function::function_obj_invoker2<boost::_bi::bind_t<bool, boost::python::detail::translate_exception<mapnik::config_error, void (
)(mapnik::config_error const&)>, boost::_bi::list3boost::arg<1, boost::arg<2>, boost::_bi::value<void (*)(mapnik::config_error const&)> > >, bool, boost::python::detail::exception_handler const&, boost::function0 const&>::invoke(boost::detail::function::function_buffer&, boost::python::detail::exception_handler const&, boost::function0 const&) + 36
10 ...python-xgcc40-mt-1_39.dylib 0x00f19bdf boost::python::handle_exception_impl(boost::function0) + 63
11 ...python-xgcc40-mt-1_39.dylib 0x00f0fb62 function_call + 98
12 org.python.python 0x003fadac PyObject_Call + 45 (abstract.c:1861)
13 org.python.python 0x00482577 PyEval_EvalFrameEx + 6282 (ceval.c:3823)
14 org.python.python 0x0048771d PyEval_EvalCodeEx + 1819 (ceval.c:2875)
15 org.python.python 0x0041c103 function_call + 220 (funcobject.c:517)
16 org.python.python 0x003fadac PyObject_Call + 45 (abstract.c:1861)
17 org.python.python 0x004846e1 PyEval_EvalFrameEx + 14836 (ceval.c:3892)
18 org.python.python 0x0048771d PyEval_EvalCodeEx + 1819 (ceval.c:2875)
19 org.python.python 0x004878d1 PyEval_EvalCode + 87 (ceval.c:520)
20 org.python.python 0x004ab031 PyRun_FileExFlags + 260 (pythonrun.c:1273)
21 org.python.python 0x004ab3cb PyRun_SimpleFileExFlags + 640 (pythonrun.c:879)
22 org.python.python 0x004b8bbe Py_Main + 3077 (main.c:532)
23 org.python.python 0x00001f8e 0x1000 + 3982
24 org.python.python 0x00001eb5 0x1000 + 3765

Thread 0 crashed with X86 Thread State (32-bit):
eax: 0xbfffd840 ebx: 0x00c233fb ecx: 0x00000000 edx: 0x00837094
edi: 0x00837ea0 esi: 0x00837090 ebp: 0xbfffd878 esp: 0xbfffd640
ss: 0x0000001f efl: 0x00210246 eip: 0x00c23462 cs: 0x00000017
ds: 0x0000001f es: 0x0000001f fs: 0x00000000 gs: 0x00000037
cr2: 0x00000000
}}}

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] I've fixed the scaling in both AGG and Cairo for both raster and vector symbols (latest code is on github)

I didn't try running the nose tests until after implementing the scaling stuff, but they seem to work for me okay, even without librsvg.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Awesome, the svg_symbols_test ran well (still using the modified cairo png out).

Looks fantastic, and works as expected. The only small things I notice are a small shift between location of the red dots in the agg and cairo scaled raster point icons, and slightly slower quality in the resampling in the agg output for the raster icons.

Looks like it may be time to think about steps to merge this into trunk.

Thinking about the XML loading and saving issues... I wonder about the benefit/value of being able to inline SVG as well as reading from a file.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Hmm, so I'm starting to realized that the dependecies for librsvg are surprisingly tangled. Libxml2 and libart are fine, but it needs rendering via gtk it looks like, and the gtk framework needs a whole smack of other stuff.

Craig, what os and what build procedure have you been using to compile librsvg? What are the minimum features we need from it?

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] I've just been using the librsvg package in Ubuntu 8.10 (librsvg 2.22.3-0ubuntu1).

A major attraction of librsvg is that it will render to a Cairo context with very little code (just call the rsvg_handle_render_cairo() function).

As a result the code that actually depends on librsvg is fairly minimal and could be replaced by something else if someone can suggest an alternative and someone can donate the time.

However, since librsvg is an optional dependency anyway, I would assume that depending on gtk is reasonable. Correct me if I'm wrong..

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Replying to [comment:19 cdestigter]:

Great, thanks for the info.

I've just been using the librsvg package in Ubuntu 8.10 (librsvg 2.22.3-0ubuntu1).

okay. I was wondering if you had been compiling from source and found tricks that I was missing to build a 'lite' version of librsvg. I was trying on Mac osx from source and from my build notes it looks like these are the additional dependencies (beyond cairo stuff) needed to build libsvg:

{{{
libglib-2.0
libgio-2.0
libgdk_pixbuf-2.0
libgsf-1
libpango-1.0
libpangoft2-1.0
libgobject-2.0
libintl
libpangocairo-1.0
libgmodule-2.0
libbz2

hopefully optional once I figure out the builds:
libXrender
libX11
libXau
libXdmcp
libcroco-0.6
}}}

A major attraction of librsvg is that it will render to a Cairo context with very little code (just call the rsvg_handle_render_cairo() function).

As a result the code that actually depends on librsvg is fairly minimal and could be replaced by something else if someone can suggest an alternative and someone can donate the time.

Okay, good to know.

However, since librsvg is an optional dependency anyway, I would assume that depending on gtk is reasonable. Correct me if I'm wrong..

Yes, totally reasonable. I also did a bit more background reading on librsvg and it certainly sounds like the right choice above other options that might have less depedencies. There are other good reasons for Mapnik to delve into these gnome libraries - particularly pango for unicode text support.

I guess what prompted my earlier head scratching is thinking about Windows builds, and that these types of dependencies may make a fully functioning Mapnik on win32 a more distant goal.

The lead cartographer on the OSM project runs on windows, and I'm ultra sensitive to that given that the OSM stylesheets could benefit immensely from your awesome svg work.

But, realistically we don't even have cairo or libxml2 support in the windows builds currently, so this concern is fully independent of librsvg stuff. Sorry about the sidetracking, and thanks so much for the hard work!

-dane

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] Connected with Artem about this last week and he mentioned looking into the svg parsing within AGG behind the http://www.antigrain.com/svg/index.html. Source code is in 'agg-src/examples/svg_viewer'.

Also, I was part of brief discussions on mapserver IRC about their GSOC project yesterday. It looks like they are also going to be looking into using the AGG parsing:

http://lists.osgeo.org/pipermail/mapserver-dev/2009-July/008910.html

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] For now, moving this ticket to 0.7.0 since that is the proper spot to continue discussion and hopefully integrate a solution.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[rcoup] The AGG SVG example is a demo. It supports line, polygon, path, and rect objects, and simple fills/strokes/opacities/transforms. Better than nothing, certainly, but no gradients, symbols, curves, etc which are arguably the good bits :)

Craig's system is fairly pluggable so it should be able to use homegrown/AGG code as a fallback is librsvg isn't available. Not sure if that's a desirable solution though - you'd get vastly differing output depending on which SVG parser you used.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] The above patch applies to svn r1287 (use -p1 )

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] fyi, see thread starting with: http://lists.cairographics.org/archives/cairo/2009-October/018332.html

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] Using qt might prove worthwhile, but it's a moderate time investment. As I mentioned above, rsvg has the rsvg_handle_render_cairo() function which makes things easy.

A bit of flicking around in the Qt docs suggests we'd have to do the following to switch to using Qt's SVG module:

  • Make SVGSymbol wrap a [http://qt.nokia.com/doc/4.5/qsvgrenderer.html QSVGRenderer]
    • SVGSymbol.load_from_file wraps [http://qt.nokia.com/doc/4.5/qsvgrenderer.html#load load]
    • SVGSymbol.rasterize() is straightforward:
    • instantiates a [http://qt.nokia.com/doc/4.5/qpainter.html QPainter]
    • calls painter.begin(), passing it a [http://qt.nokia.com/doc/4.5/qimage.html QImage];
    • passes the painter to [http://qt.nokia.com/doc/4.5/qsvgrenderer.html#render render]
    • returns an Image32 using the pixel data from the QImage
    • SVGSymbol.render_to_context() is more tricky. I can't find any existing Qt-to-Cairo implementations (anyone?) so we will have to make our own implementations of [http://qt.nokia.com/doc/4.5/qpaintengine.html QPaintEngine] and [http://qt.nokia.com/doc/4.5/qpaintdevice.html QPaintDevice]:
    • CairoQPaintDevice - wrap a Cairo context
    • CairoQPaintEngine - write wrapper code for [http://qt.nokia.com/doc/4.5/qpaintengine.html 15 drawX() methods] to draw into a CairoQPaintDevice. This could take a while...

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[rcoup] Does QT have QPaint* implementations that produce SVG or PDF?

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[cdestigter] [http://qt.nokia.com/doc/4.5/qsvggenerator.html QSvgGenerator] produces SVGs, so I guess we could use that instead of Cairo for outputting SVGs. It's probably cleaner to implement the draw() methods for Cairo than to create a whole new mapnik renderer though.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[MaZderMind] I updated cdestigter's patch to the current svn version 1433. It solves the scale problem with svg icons and it works for point_symbolizer and shield_symbolizer. It does not yet work for line_pattern_symbolizer, polygon_pattern_symbolizer and raster_symbolizer(?). I'm working on this, as I'm working on a vector-only version of the osm style.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[MaZderMind] I'm sorry i attached the wrong patch. It was missing svg_reader.cpp. I replaced the patch with a correct one.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] discussing how to get this (or a modified patch) landed in trunk seems like a top priority to me, so bumping priority level of ticket....

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[artem] Implemented in trunk for AGG renderer.

@artemp
Copy link
Member Author

artemp commented Oct 11, 2011

[springmeyer] implemented for cairo in r2532 (along with gradients for both agg and cairo - #654)

@artemp artemp closed this as completed Oct 11, 2011
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

No branches or pull requests

1 participant