<?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>geo-service</tabi:current_section>
    </tabi:metadata><link rel="extra-stylesheet" href="https://blog.geothings.ch/skins/mint.css?h=504215cf6bc10586b487" /><title>geothings - geo-service</title>
        <subtitle>spatial data | open source | oversharing</subtitle>
    <link href="https://blog.geothings.ch/tags/geo-service/atom.xml" rel="self" type="application/atom+xml"/>
    <link href="https://blog.geothings.ch/tags/geo-service/" rel="alternate" type="text/html"/>
    <generator uri="https://www.getzola.org/">Zola</generator><updated>2024-03-22T00:00:00+00:00</updated><id>https://blog.geothings.ch/tags/geo-service/atom.xml</id><entry xml:lang="en">
        <title>Geoservices with qgis-server</title>
        <published>2024-03-22T00:00:00+00:00</published>
        <updated>2024-03-22T00:00:00+00:00</updated>
        <author>
            <name>jujube</name>
        </author>
        <link rel="alternate" href="https://blog.geothings.ch/blog/geoservices-with-qgis-server/" type="text/html"/>
        <id>https://blog.geothings.ch/blog/geoservices-with-qgis-server/</id>
        
            <content type="html">&lt;p&gt;While the geospatial world has its own particularities that sometimes make it difficult to get on-board or communicate with (see &lt;a rel=&quot;external&quot; href=&quot;https://www.geothings.ch/posts/on-gis/#the-spatial-exception&quot;&gt;the spatial exception&lt;/a&gt;), we have to admit that some tools and standards developed are both amazing and essential. Geoservices are crucial for the diffusion of georeferenced data, and they drastically changed the way we apprehend our localization; when was the last time you asked for direction?&lt;/p&gt;
&lt;p&gt;Being WMS, WMTS, WFS, WCS or any W*S, geoservices are everywhere and sometimes are a bit mysterious. But even without understanding the fundamentals of their definition, it’s rather easy to get started.&lt;/p&gt;
&lt;p&gt;We’ll see how to install a (geo)server that will be responsible for serving our data, quickly prepare some data, and even see how you can use these services to update your data safely. We’ll be using &lt;a rel=&quot;external&quot; href=&quot;https://docs.qgis.org/3.34/en/docs/user_manual/index.html&quot;&gt;qgis-server&lt;/a&gt; here, because it’s the most easy to use and the quickest way to get started.&lt;/p&gt;
&lt;h2 id=&quot;some-quick-context&quot;&gt;Some quick context&lt;/h2&gt;
&lt;p&gt;You’ve probably heard about the &lt;a rel=&quot;external&quot; href=&quot;https://www.ogc.org/&quot;&gt;Open Geospatial Consortium&lt;/a&gt; (OGC), but to make it really short, if you hear about a standard regarding geospatial, the OGC is probably behind. At least it’s the case for the geoservices we’ll be diving into here.&lt;/p&gt;
&lt;p&gt;And what are those geoservices? Well most of the time when we talk about “geoservice” we’re referring to WMS, WMTS, WFS, WCS. They are a standardized way of accessing georeferenced data through the internet. The same way you reach a website or an API using an URL, you can access some geographic data if you know some URL (and have the tools that go with it). You can then use the data for your use-case: displaying a background map in your app, showing queryable data for an interactive webmap etc. The most common are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web Map Service (&lt;strong&gt;WMS&lt;/strong&gt;) –&amp;gt; probably the initial service. Used to display an image as .jpg or .png (see it as a background map)&lt;/li&gt;
&lt;li&gt;Web Map Tiled Service (&lt;strong&gt;WMTS&lt;/strong&gt;) –&amp;gt; the more efficient WMS. It’s the same, but images are tiled to restricted the amount of data to be transferred to the client based on the display localization and the zoom level.&lt;/li&gt;
&lt;li&gt;Web Feature Service (&lt;strong&gt;WFS&lt;/strong&gt;) –&amp;gt; the vector service. You can work on the data as vector, request features on the map, and even add, modify or delete features.&lt;/li&gt;
&lt;li&gt;OGC API-Feature (&lt;strong&gt;OGC-APIF&lt;/strong&gt;) –&amp;gt; the more “web standard” WFS, also known as &lt;strong&gt;WFS3&lt;/strong&gt;. Features can be accessed individually through an URL. The OGC states that
&lt;blockquote&gt;
&lt;p&gt;The API building blocks specified in this standard are consistent with the architecture of the Web.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;Web Coverage Service (&lt;strong&gt;WCS&lt;/strong&gt;) –&amp;gt; at the time of writing I’ve never used this standard so I won’t go into details, but from my reading it’s a service close to the WMS standard but providing more information than just an image, allowing more complex interactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are other existing services: vector tiles, WSC, WMC etc. But I either never encountered them or they usually are implemented by other tools than the common geoservers.&lt;/p&gt;
&lt;h2 id=&quot;ok-let-s-get-started&quot;&gt;Ok let’s get started&lt;/h2&gt;
&lt;p&gt;It’s nice and all, but how do we serve a W*S. Well for starters we need a server. Here we’ll be using &lt;a rel=&quot;external&quot; href=&quot;https://docs.qgis.org/3.34/en/docs/server_manual/index.html&quot;&gt;qgis-server&lt;/a&gt; with &lt;a rel=&quot;external&quot; href=&quot;https://nginx.org/en/&quot;&gt;nginx&lt;/a&gt;. The choice for qgis-server is motivated by the simplicity of use, you serve a qgis project. The choice for nginx, well the doc mentions it and it seems very lightweigth. I’m by no mean a system administrator so bear with me as I’ll read the doc with you.&lt;/p&gt;
&lt;h3 id=&quot;install-and-configure-qgis-server-and-nginx&quot;&gt;Install and configure qgis-server and nginx&lt;/h3&gt;
&lt;p&gt;I’m on Ubuntu so I’ll be using &lt;code&gt;apt&lt;/code&gt;, and the installation includes the use of &lt;code&gt;systemd&lt;/code&gt;. I’ll also be working with the &lt;a rel=&quot;external&quot; href=&quot;https://ppa.launchpadcontent.net/ubuntugis/ppa/ubuntu/&quot;&gt;ubuntugis ppa&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First add the ppa:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;add-apt-repository&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; ppa:ubuntugis&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install qgis-server:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; install qgis-server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install nginx if not already present:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; install nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;configure-qgis-server&quot;&gt;Configure qgis-server&lt;/h4&gt;
&lt;p&gt;We’ll then follow instructions from the &lt;a rel=&quot;external&quot; href=&quot;https://docs.qgis.org/3.34/en/docs/server_manual/getting_started.html#systemd&quot;&gt;QGIS documentation&lt;/a&gt; for a qgis-server installation using systemd and nginx. The method defines a socket unit and a service unit as follow. The sockets will be the communication channel between our http server (nginx) and our application server (qgis-server). Create the socket file &lt;code&gt;/etc/systemd/system/qgis-server@.socket&lt;/code&gt; with the following content:&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;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Description=QGIS Server Listen Socket (instance %i)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Socket]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Accept=false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ListenStream=/var/run/qgis-server-%i.sock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SocketUser=www-data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SocketGroup=www-data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SocketMode=0600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WantedBy=sockets.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can start these sockets with the following command:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-9&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 1 2 3 4&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-9&quot;&gt; do&lt;/span&gt;&lt;span class=&quot;z-3 z-i&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; enable --now qgis-server@&lt;/span&gt;&lt;span&gt;$i&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;.socket&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-9&quot;&gt; done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which will start 4 sockets, through which our http server will be able to pass requests to our qgis-server.&lt;/p&gt;
&lt;p&gt;Now for the services. Services are managed by systemd and in our case are responsible for the execution of the qgis-server. Create the file &lt;code&gt;/etc/systemd/system/qgis-server@.service&lt;/code&gt; and add:&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;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Description=QGIS Server Service (instance %i)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;User=www-data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Group=www-data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;StandardOutput=null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;StandardError=journal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;StandardInput=socket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ExecStart=/usr/lib/cgi-bin/qgis_mapserv.fcgi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;EnvironmentFile=/etc/qgis-server/env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WantedBy=multi-user.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the server is configured using a &lt;code&gt;env&lt;/code&gt; file, here &lt;code&gt;/etc/qgis-server/env&lt;/code&gt;. Finally, in the same manner as the sockets, start the 4 services with the following:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-9&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; 1 2 3 4&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-9&quot;&gt; do&lt;/span&gt;&lt;span class=&quot;z-3 z-i&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; enable --now qgis-server@&lt;/span&gt;&lt;span&gt;$i&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt;.service&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;z-9&quot;&gt; done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Services are depending on sockets, you’ll need to start the sockets first then the services.
{: .prompt-warning }&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;configure-nginx&quot;&gt;Configure nginx&lt;/h4&gt;
&lt;p&gt;We’ll then configure nginx for our future qgis-server using &lt;a rel=&quot;external&quot; href=&quot;https://en.wikipedia.org/wiki/FastCGI&quot;&gt;fastcgi&lt;/a&gt; by creating the file &lt;code&gt;/etc/nginx/sites-enabled/qgis-server&lt;/code&gt; and adding the following:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;nginx&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;upstream&lt;/span&gt;&lt;span&gt; qgis-server_backend {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   server&lt;/span&gt;&lt;span&gt; unix:/var/run/qgis-server-1.sock;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   server&lt;/span&gt;&lt;span&gt; unix:/var/run/qgis-server-2.sock;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   server&lt;/span&gt;&lt;span&gt; unix:/var/run/qgis-server-3.sock;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   server&lt;/span&gt;&lt;span&gt; unix:/var/run/qgis-server-4.sock;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;server&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   listen&lt;/span&gt;&lt;span class=&quot;z-14&quot;&gt; 8080&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   access_log&lt;/span&gt;&lt;span&gt;  /var/log/nginx/qgis-server/access.log&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   error_log&lt;/span&gt;&lt;span&gt;  /var/log/nginx/qgis-server/error.log&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;   location&lt;/span&gt;&lt;span&gt; /qgis-server {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;       gzip&lt;/span&gt;&lt;span class=&quot;z-11&quot;&gt; off&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;       include&lt;/span&gt;&lt;span&gt; fastcgi_params&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-9&quot;&gt;       fastcgi_pass&lt;/span&gt;&lt;span&gt; qgis-server_backend&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration will then be included by the nginx.conf file using the &lt;code&gt;include /etc/nginx/sites-enabled/*;&lt;/code&gt; statement.&lt;/p&gt;
&lt;h3 id=&quot;create-a-qgis-project-and-configure-it&quot;&gt;Create a QGIS project and configure it&lt;/h3&gt;
&lt;p&gt;Now it’s time to decide what we want to expose. I have some OSM data in a PostgreSQL database (see &lt;a rel=&quot;external&quot; href=&quot;https://www.geothings.ch/posts/vector-tiles-with-martin/#downloading-some-data&quot;&gt;Serving vector tiles with Martin&lt;/a&gt;) so I’ll use that.&lt;/p&gt;
&lt;p&gt;Create a project, add some data and apply the desired style. A quick example is shown below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.geothings.ch/blog/geoservices-with-qgis-server/qgis-data-prepared.png&quot; alt=&quot;data-with-style&quot; /&gt;
&lt;em&gt;Data with some style&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Once you’re satisfied with the look of your data, you’ll need to configure the project so it will tell qgis-server to proceed with the geoservice you want to serve.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;em&gt;Project -&amp;gt; Properties -&amp;gt; QGIS Server&lt;/em&gt; and complete the configuration as you wish.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Service Capabilities&lt;/em&gt;, check the &lt;em&gt;Enable Service Capabilities&lt;/em&gt; box.&lt;/li&gt;
&lt;li&gt;Select the different services you want to enable under the different tabs.&lt;/li&gt;
&lt;li&gt;Save your project under a folder accessible by the server user (&lt;code&gt;www-data&lt;/code&gt;). If you’re using file data in your project, such as a geopackage, save them in the same place.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you setup a WFS, the tab should look like something like below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.geothings.ch/blog/geoservices-with-qgis-server/setup-wfs.png&quot; alt=&quot;wfs-setup&quot; /&gt;
&lt;em&gt;Data with some style&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;admonition warning&quot;&gt;
&lt;div class=&quot;admonition-icon admonition-icon-warning&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;admonition-content&quot;&gt;
&lt;strong class=&quot;admonition-title&quot;&gt;WARNING&lt;/strong&gt;
&lt;p&gt;Make sure that your tables have a primary key. Otherwise the &lt;em&gt;update&lt;/em&gt; and &lt;em&gt;delete&lt;/em&gt; operations won’t be possible.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;admonition warning&quot;&gt;
&lt;div class=&quot;admonition-icon admonition-icon-warning&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;admonition-content&quot;&gt;
&lt;strong class=&quot;admonition-title&quot;&gt;WARNING&lt;/strong&gt;
&lt;p&gt;And also, be sure that you don’t have special characters in you column or table names. It messes up the WFS 1.0 and 1.1 requests.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can then edit the &lt;code&gt;env&lt;/code&gt; file with the corresponding environment variables. For me it looks 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;QGIS_PROJECT_FILE=/etc/qgis-server/my_project.qgz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;QGIS_SERVER_LOG_STDERR=1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;QGIS_SERVER_LOG_LEVEL=3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use PostgreSQL data and have had troubles getting the server to reach the data, even on localhost but finally managed to have everything working with the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make the project read the data using a &lt;a rel=&quot;external&quot; href=&quot;https://www.postgresql.org/docs/current/libpq-pgservice.html&quot;&gt;PostgreSQL service&lt;/a&gt; (create a pg_service.conf if not existing).&lt;/li&gt;
&lt;li&gt;Copy the pg_service.conf file somewhere accessible for the server.&lt;/li&gt;
&lt;li&gt;Edit &lt;code&gt;/etc/nginx/fastcgi_params&lt;/code&gt; and add &lt;code&gt;fastcgi_param  PGSERVICEFILE /path/to/service/pg_service.conf;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Restart everything.&lt;/li&gt;
&lt;li&gt;Bonus: &lt;code&gt;touch&lt;/code&gt; the project file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If everything works correctly, you should be able to reach &lt;a rel=&quot;external&quot; href=&quot;http://localhost:8080/qgis-server/qgis_mapserv.fcgi?SERVICE=WMS&amp;amp;VERSION=1.3.0&amp;amp;REQUEST=GetCapabilities&quot;&gt;localhost:8080/qgis-server/qgis_mapserv.fcgi?SERVICE=WMS&amp;amp;VERSION=1.3.0&amp;amp;REQUEST=GetCapabilities&lt;/a&gt; for WMS capabilities or &lt;a rel=&quot;external&quot; href=&quot;http://localhost:8080/qgis-server/wfs3/&quot;&gt;http://localhost:8080/qgis-server/wfs3/&lt;/a&gt; for the OGC-APIF description and navigate the different endpoints defined in the OGC-APIF standard.&lt;/p&gt;
&lt;p&gt;You should now be able to also call these URL from QGIS using the correct url. Try to add a WFS/OGC-APIF using the http://localhost:8080/qgis-server/wfs3/ url (qgis-server in this example, but you can setup your server with a different url).&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going further&lt;/h2&gt;
&lt;p&gt;We have the basics setup, now what can we do? Let’s start by alter data.&lt;/p&gt;
&lt;h3 id=&quot;updating-data-through-a-wfs&quot;&gt;Updating data through a WFS&lt;/h3&gt;
&lt;p&gt;If at this point you have everything working, there shouldn’t be much to do to update data through your newly build WFS. Working with the OGC-APIF you’ll be able to add, delete and update data using URL like you would do with any REST API.&lt;/p&gt;
&lt;p&gt;You’ll find the description of your API at the URL &lt;code&gt;http://localhost:8080/qgis-server/wfs3/api&lt;/code&gt; (for this particular setup) and it should look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.geothings.ch/blog/geoservices-with-qgis-server/api-description.png&quot; alt=&quot;api-description&quot; /&gt;
&lt;em&gt;OpenAPI description of an OGC-APIF&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This gives you an idea of the available endpoints to manipulate data.&lt;/p&gt;
&lt;p&gt;To try this, let’s try to &lt;code&gt;GET&lt;/code&gt; one of our feature. You can access a single feature through a &lt;code&gt;collection&lt;/code&gt; and an &lt;code&gt;item&lt;/code&gt;. For me, the URL &lt;a rel=&quot;external&quot; href=&quot;http://localhost:8080/qgis-server/wfs3/collections/planet_osm_polygon/items/2102736&quot;&gt;http://localhost:8080/qgis-server/wfs3/collections/planet_osm_polygon/items/2102736&lt;/a&gt; will point to the Lausanne train station:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.geothings.ch/blog/geoservices-with-qgis-server/lausanne-gare.png&quot; alt=&quot;api-description&quot; /&gt;
&lt;em&gt;Getting an item using its dedicated URL&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And the same can be done using &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;wget&lt;/code&gt; or any network utility you wish:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; --request &amp;quot;GET&amp;quot; &amp;quot;localhost:8080/qgis-server/wfs3/collections/planet_osm_polygon/items/2102736&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-1&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-3 z-i&quot;&gt; jq&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will print 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;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;quot;bbox&amp;quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    6.627779,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    46.51671,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    6.630578,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    46.517298&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;quot;geometry&amp;quot;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;quot;coordinates&amp;quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          6.627779,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          46.517099&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          6.628225,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          46.517038&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          6.628344,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          46.51702&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&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;The piping into &lt;code&gt;jq&lt;/code&gt; (&lt;code&gt;| jq .&lt;/code&gt;) is just for printing purpose, it doesn’t participate in the query.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now let’s delete this object:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; --request &amp;quot;DELETE&amp;quot; &amp;quot;localhost:8080/qgis-server/wfs3/collections/planet_osm_polygon/items/2102736&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that’s it!&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; --request &amp;quot;GET&amp;quot; &amp;quot;localhost:8080/qgis-server/wfs3/collections/planet_osm_polygon/items/2102736&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;now returns:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;&amp;quot;:&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; &amp;quot;Internal server error&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-3&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt;&amp;quot;:&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; &amp;quot;Invalid feature [2102736]&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you can do exactly the same using QGIS. Add your WFS as a layer and work on it like any vector data: add, update ,delete features and then save.&lt;/p&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;I intended to use a PATCH request to update a single attribute, but couldn’t make it work. QGIS seems to systematically call a POST request, even for a single attribute change. If you now how to work with PATCH in this environment let me know.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;protect-our-service-with-authentication&quot;&gt;Protect our service with authentication&lt;/h3&gt;
&lt;p&gt;Accessing information through an URL means that many client might have access to it as well. It’s important to keep in mind that authentication would be necessary if you expect to update information through this API.&lt;/p&gt;
&lt;p&gt;Again, I’m not a system administration or a security expert, but I’ll try to show how to setup a very basic authentication directly using &lt;code&gt;nginx&lt;/code&gt;. &lt;strong&gt;This is not how you secure an API and it won’t replace a modern and secure authentication setup&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I’ll follow the &lt;a rel=&quot;external&quot; href=&quot;https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/&quot;&gt;nginx documention&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;First install &lt;code&gt;apache2-utils&lt;/code&gt; if your on a Debian based distribution:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; apt install apache2-utils&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a password file and a user:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-3 z-i&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-7&quot;&gt; htpasswd -c /etc/apache2/.htpasswd user1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;nginx&lt;/code&gt; configuration, under the location of your API, add a name and specify that you’ll be using a &lt;code&gt;auth_basic_user_file&lt;/code&gt;.&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;location /qgis-server {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [...]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    auth_basic           &amp;quot;Protected Area&amp;quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    auth_basic_user_file /etc/apache2/.htpasswd;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart &lt;code&gt;nginx&lt;/code&gt; for good measure and that it, try to reach your WFS using an URL or with QGIS and you should see a connection popup like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.geothings.ch/blog/geoservices-with-qgis-server/connexion.png&quot; alt=&quot;connection&quot; /&gt;
&lt;em&gt;The route now needs basic authentication to be reached&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Services are a powerful tool to be aware of when working with geographical data. They allow to efficiently share them at a larger scale through the web and even, as we saw, update them. There are many ways to build these services, and &lt;code&gt;qgis-server&lt;/code&gt; is one solution among others, but it has the advantage to work (almost) out of the box with potential QGIS projects that you could already have: a few tweaks and you can be going.&lt;/p&gt;
&lt;p&gt;The possibility to give access to your data through an URL makes sharing them so easy; it reduces the amount of manipulation and tools required, you don’t need to maintain a data extractor or repositories. Just some data and a server.&lt;/p&gt;
</content>
        <summary type="html">Use QGIS as a configuration for your services.</summary>
        </entry>
</feed>
