<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="https://blog.geothings.ch/feed_style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <tabi:metadata xmlns:tabi="https://github.com/welpo/tabi">
        <tabi:base_url>https://blog.geothings.ch</tabi:base_url>
        <tabi:separator>
            •
        </tabi:separator>
        <tabi:about_feeds>
            This is a web feed, also known as an Atom feed. Subscribe by copying the URL from the address bar into your newsreader. Visit About Feeds to learn more and get started. It's free.
        </tabi:about_feeds>
        <tabi:visit_the_site>
            Visit website
        </tabi:visit_the_site>
        <tabi:recent_posts>
            Recent posts
        </tabi:recent_posts>
        <tabi:last_updated_on>
            Updated on $DATE
        </tabi:last_updated_on>
        <tabi:default_theme></tabi:default_theme>
        <tabi:post_listing_date>date</tabi:post_listing_date>
        <tabi:current_section>pdf</tabi:current_section>
    </tabi:metadata><link rel="extra-stylesheet" href="https://blog.geothings.ch/skins/mint.css?h=504215cf6bc10586b487" /><title>geothings - pdf</title>
        <subtitle>spatial data | open source | oversharing</subtitle>
    <link href="https://blog.geothings.ch/tags/pdf/atom.xml" rel="self" type="application/atom+xml"/>
    <link href="https://blog.geothings.ch/tags/pdf/" rel="alternate" type="text/html"/>
    <generator uri="https://www.getzola.org/">Zola</generator><updated>2025-05-30T00:00:00+00:00</updated><id>https://blog.geothings.ch/tags/pdf/atom.xml</id><entry xml:lang="en">
        <title>Serve PDF and DXF files using qgis-server</title>
        <published>2025-05-30T00:00:00+00:00</published>
        <updated>2025-05-30T00:00:00+00:00</updated>
        <author>
            <name>jujube</name>
        </author>
        <link rel="alternate" href="https://blog.geothings.ch/blog/pdf-dxf-qgis-server/" type="text/html"/>
        <id>https://blog.geothings.ch/blog/pdf-dxf-qgis-server/</id>
        
            <content type="html">&lt;p&gt;You might be faced with the need to distribute “plan” files. For some businesses, paper maps (pdf) and CAD drawings (dxf) are still the primary source of information, and sometimes you can’t propose anything else. If you’re working with QGIS there are simple ways to serve those files without much to do, and all with opensource solutions using &lt;code&gt;qgis&lt;/code&gt; and &lt;code&gt;qgis-server&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;You’ll need… Well, &lt;code&gt;qgis&lt;/code&gt; and &lt;code&gt;qgis-server&lt;/code&gt;, and that’s it. First install &lt;code&gt;qgis-server&lt;/code&gt; using instructions for you environment, you might have a look at the &lt;a rel=&quot;external&quot; href=&quot;https://docs.qgis.org/3.40/en/docs/server_manual/getting_started.html&quot;&gt;official doc&lt;/a&gt; or this &lt;a rel=&quot;external&quot; href=&quot;https://geothings.ch/blog/geoservices-with-qgis-server/#install-and-configure-qgis-server-and-nginx&quot;&gt;short tuto&lt;/a&gt;. While working on this article, I followed the &lt;a rel=&quot;external&quot; href=&quot;https://docs.qgis.org/3.40/en/docs/server_manual/containerized_deployment.html#&quot;&gt;containerized deployment instruction&lt;/a&gt; from the QGIS documentation. I’ll link a &lt;a rel=&quot;external&quot; href=&quot;https://codeberg.org/jujube/pdf-dxf-qgis-server&quot;&gt;repository&lt;/a&gt; with the resources for you to follow along.&lt;/p&gt;
&lt;p&gt;The next step is the preparation of your exports using &lt;code&gt;qgis&lt;/code&gt;. Your projects will serve as configuration, where you’ll define symbologies, renderings, services, etc. We’ll show some possibilities to configure pdf and dxf exports.&lt;/p&gt;
&lt;h2 id=&quot;export-pdf-files&quot;&gt;Export PDF files&lt;/h2&gt;
&lt;p&gt;For simple cases it’s the easiest setup, you’ll just need to configure a &lt;code&gt;layout&lt;/code&gt; in your project. Define a map that suits your taste (or better your objectives) and save everything in a location where your server has the right to read your project and the linked data. And that’s it for the most part. You can then have a look at a resulting map by querying an url of the like:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http://localhost:8080/qgis-server?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SERVICE=WMS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;VERSION=1.3.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;REQUEST=GetPrint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;MAP=project.qgz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;TEMPLATE=layout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;CRS=EPSG:3857&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;map0:extent=-591992,-1689591,4318167,3574909&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;FORMAT=application/pdf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we divide the query we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http://localhost:8080/qgis-server&lt;/code&gt; the url of your qgis-server.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SERVICE=WMS&lt;/code&gt; tells the server to use its WMS service.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VERSION=1.3.0&lt;/code&gt; version of the service.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REQUEST=GetPrint&lt;/code&gt; run the GetPrint request.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MAP=project.qgz&lt;/code&gt; path to the qgis project to read.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TEMPLATE=layout&lt;/code&gt; the name of a template defined within the project that describes a &lt;code&gt;layout&lt;/code&gt;, where you’ll define your map.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRS=3857&lt;/code&gt; the coordinate system to use, usually the one defined in your project.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map0:extent=xmin,ymin,xmax,ymax&lt;/code&gt; the bounding box defining the extent to print. In the &lt;code&gt;CRS&lt;/code&gt; system.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FORMAT=application/pdf&lt;/code&gt; specify the output format, here pdf.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your browser will show you something like the following:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.geothings.ch/blog/pdf-dxf-qgis-server/export_pdf.png&quot; alt=&quot;export-pdf&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In this simple case, we could imagine a service where you define an extraction extent, which then will be passed to your printing service in the form of a &lt;code&gt;mapx:extent&lt;/code&gt; parameter. You’d receive your pdf as a response.&lt;/p&gt;
&lt;h2 id=&quot;export-pdf-files-atlas-version&quot;&gt;Export PDF files: atlas version&lt;/h2&gt;
&lt;p&gt;You can create &lt;a rel=&quot;external&quot; href=&quot;https://docs.qgis.org/3.40/en/docs/training_manual/forestry/forest_maps.html&quot;&gt;&lt;code&gt;atlases&lt;/code&gt; in QGIS&lt;/a&gt; which allows you to produce maps using predefined extents. Your &lt;code&gt;qgis-server&lt;/code&gt; also supports those and you can request a specific map from an atlas by using the &lt;code&gt;ATLAS_PK&lt;/code&gt; parameter in you query.&lt;/p&gt;
&lt;p&gt;If we create a &lt;code&gt;layout_atlas&lt;/code&gt; in our project, using a grid as overlay (see the &lt;a rel=&quot;external&quot; href=&quot;https://codeberg.org/jujube/pdf-dxf-qgis-server&quot;&gt;example project&lt;/a&gt;), we could then query our atlas pages using the following:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http://localhost:8080/qgis-server?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SERVICE=WMS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;VERSION=1.3.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;REQUEST=GetPrint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;MAP=project.qgz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;TEMPLATE=layout_atlas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;CRS=EPSG:3857&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;ATLAS_PK=1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which would then send back something like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.geothings.ch/blog/pdf-dxf-qgis-server/export_atlas.png&quot; alt=&quot;export-atlas&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here you’d need to get the atlas elements that intersect your region of interest and pass their &lt;code&gt;id&lt;/code&gt; as &lt;code&gt;ATLAS_PK&lt;/code&gt;. A query like the following could get you this information:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;curl -X POST -H &amp;quot;Content-Type: application/xml&amp;quot; -d @BODY.xml localhost:8080/qgis-server?SERVICE=WFS&amp;amp;VERSION=1.3.0&amp;amp;REQUEST=GetFeature&amp;amp;MAP=project.qgs  &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;BODY.xml&lt;/code&gt; contains the following body:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;yes&amp;quot;?&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;wfs:GetFeature maxFeatures=&amp;quot;5000&amp;quot; version=&amp;quot;1.1.0&amp;quot; service=&amp;quot;WFS&amp;quot; xsi:schemaLocation=&amp;quot;http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd&amp;quot; xmlns:wfs=&amp;quot;http://www.opengis.net/wfs&amp;quot; xmlns:gml=&amp;quot;http://www.opengis.net/gml&amp;quot; xmlns:ogc=&amp;quot;http://www.opengis.net/ogc&amp;quot; xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;wfs:Query typeName=&amp;quot;grid&amp;quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;ogc:Filter&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;ogc:Intersects&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &amp;lt;ogc:PropertyName&amp;gt;geom&amp;lt;/ogc:PropertyName&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          &amp;lt;Polygon xmlns=&amp;quot;http://www.opengis.net/gml&amp;quot; srsName=&amp;quot;EPSG:3857&amp;quot;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            &amp;lt;exterior&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              &amp;lt;LinearRing&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                &amp;lt;posList srsDimension=&amp;quot;2&amp;quot;&amp;gt;1471131.73 1257643.13 2471131.7 1257643.13 2471131.7 257643.1 1471131.7 257643.1 1471131.73 1257643.1&amp;lt;/posList&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;              &amp;lt;/LinearRing&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            &amp;lt;/exterior&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          &amp;lt;/Polygon&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;/ogc:Intersects&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;/ogc:Filter&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;/wfs:Query&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;/wfs:GetFeature&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Extracting the ids from the response you could then query you atlas on the server with:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;http://localhost:8080/qgis-server/?atlas_pk=44,45,46,52,53,54&amp;amp;SERVICE=WMS&amp;amp;VERSION=1.3.0&amp;amp;REQUEST=GetPrint&amp;amp;MAP=project.qgz&amp;amp;TEMPLATE=layout_atlas&amp;amp;CRS=EPSG:3857&amp;amp;FORMAT=application/pdf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;admonition tip&quot;&gt;
&lt;div class=&quot;admonition-icon admonition-icon-tip&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;admonition-content&quot;&gt;
&lt;strong class=&quot;admonition-title&quot;&gt;TIP&lt;/strong&gt;
&lt;p&gt;You’ll need to allow your server to print multiple pages. This can be done in &lt;code&gt;Project &amp;gt; Properties &amp;gt; QGIS Server &amp;gt; WMS &amp;gt; Maximum page number for atlas queries&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;export-dxf&quot;&gt;Export DXF&lt;/h2&gt;
&lt;p&gt;At this point you have everything you need to export dxf files. Make sure you expose the layers you want to export through WFS (&lt;code&gt;Project &amp;gt; Properties &amp;gt; QGIS Server &amp;gt; WFS &amp;gt; Tick Publish for the layers you want&lt;/code&gt;). You can then get your dxf using something like:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;localhost:8080/qgis-server?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SERVICE=WMS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;VERSION=1.3.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;REQUEST=GetMap&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;MAP=project.qgz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;TEMPLATE=layout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;CRS=EPSG:3857&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;map0:extent=-591992,-1689591,4318167,3574909&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;LAYERS=point,line,polygon&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;FORMAT=application/dxf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;amp;FILE_NAME=output.dxf&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;admonition tip&quot;&gt;
&lt;div class=&quot;admonition-icon admonition-icon-tip&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;admonition-content&quot;&gt;
&lt;strong class=&quot;admonition-title&quot;&gt;TIP&lt;/strong&gt;
&lt;p&gt;For dxf exports, the symbology is a bit trickier to get right in my opinion. You might have to play with units to get a configuration and a rendering that suits your needs. Also, &lt;code&gt;Polylines3D&lt;/code&gt; don’t support linetypes and weight, so you might need to use the &lt;code&gt;FORCE_2D&lt;/code&gt; parameter.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;to-conclude&quot;&gt;To conclude&lt;/h2&gt;
&lt;p&gt;PDF and DXF are, after all, rather easy to create with a qgis-server. No need for any other piece of software, &lt;a rel=&quot;external&quot; href=&quot;https://geothings.ch/blog/stop-using-fme/&quot;&gt;potentially expensive&lt;/a&gt;. Make sure to play with the &lt;code&gt;QGIS Server&lt;/code&gt; properties of your project and layers within QGIS to see how it influences the exports. Again, all the material used in this article is accessible in this &lt;a rel=&quot;external&quot; href=&quot;https://codeberg.org/jujube/pdf-dxf-qgis-server&quot;&gt;repo&lt;/a&gt;.&lt;/p&gt;
</content>
        <summary type="html">Ditch that ugly FME workspace for a simple server installation.</summary>
        </entry>
</feed>
