{"id":4993,"date":"2024-07-09T06:38:24","date_gmt":"2024-07-09T04:38:24","guid":{"rendered":"https:\/\/neogeo.fr\/navigating-the-era-of-vector-tiles-techniques-and-applications\/"},"modified":"2026-10-23T16:27:40","modified_gmt":"2026-10-23T14:27:40","slug":"navigating-the-era-of-vector-tiles-techniques-and-applications","status":"publish","type":"post","link":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/","title":{"rendered":"Navigating the Era of Vector Tiles: Techniques and Applications"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">What is a tile?<\/h2>\n\n<p class=\"wp-block-paragraph\">To display spatial data on a map, there are different data formats, including the vector tile, which brings new possibilities for displaying data (fast data access, easy style modification, direct user interaction with objects).<\/p>\n\n<p class=\"wp-block-paragraph\">Indeed, a digital map can theoretically display data at any scale and in any location, but this flexibility can cause performance issues.<\/p>\n\n<p class=\"wp-block-paragraph\">Tile mosaicking improves display speed and efficiency:<\/p>\n\n<ul class=\"wp-block-list\">\n<li>By limiting the available scales. Each scale is twice as detailed as the previous one. <\/li>\n\n\n\n<li>By using a fixed grid. The areas we want to display are made up of collections of appropriate tiles. <\/li>\n<\/ul>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"218\" height=\"226\" src=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/grilles.png\" alt=\"\" class=\"wp-image-1342\"\/><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">Most tile systems start with a single tile covering the entire world, called &#8220;zoom level 0,&#8221; and each subsequent zoom level increases the number of tiles by a factor of 4, doubling both vertically and horizontally.<\/p>\n\n<h3 class=\"wp-block-heading\">Tile coordinates<\/h3>\n\n<p class=\"wp-block-paragraph\">Each tile in a mosaicked map is referenced by its zoom level, horizontal position, and vertical position. The commonly used addressing scheme is XYZ. <\/p>\n\n<p class=\"wp-block-paragraph\">Here is an example at zoom level 2:<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"256\" height=\"257\" src=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/grilles-2.png\" alt=\"\" class=\"wp-image-1346\" srcset=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/grilles-2.png 256w, https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/grilles-2-150x150.png 150w\" sizes=\"auto, (max-width: 256px) 100vw, 256px\" \/><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">We will retrieve the tile using the following XYZ addressing:<\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">http:&#047;&#047;server\/{z}\/{x}\/{y}.format<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">For example, you can view the image tile of Australia at the following address; the same principle applies to vector tiles:<a href=\"https:\/\/tile.openstreetmap.org\/2\/3\/2.png\">https:\/\/tile.openstreetmap.org\/2\/3\/2.png<\/a><\/p>\n\n<h3 class=\"wp-block-heading\">How a vector tile works<\/h3>\n\n<p class=\"wp-block-paragraph\">Vector tiles look like raster tiles, but instead of an image, they contain data describing the elements that make them up using vector paths. Instead of sets of pixels, there are sets of point coordinates, to which several attribute pieces of information can be associated. <\/p>\n\n<p class=\"wp-block-paragraph\">Like their raster cousins, vector tiles can be cached, both server-side and browser-side, allowing users to navigate huge maps quickly, even on lightweight devices.<\/p>\n\n<h2 class=\"wp-block-heading\">How is it used?<\/h2>\n\n<h3 class=\"wp-block-heading\">Generating vector tiles on the fly from PostGIS<\/h3>\n\n<p class=\"wp-block-paragraph\">There are different building blocks for publishing data on the fly from PostGIS. Here is our selection:<\/p>\n\n<ul class=\"wp-block-list\">\n<li>pg_tileserv =&gt; <a href=\"https:\/\/github.com\/CrunchyData\/pg_tileserv\">https:\/\/github.com\/CrunchyData\/pg_tileserv<\/a>: a tile server coded in Go and produced by Crunchy Data. We prefer to use the dockerized version in our applications =&gt; <a href=\"https:\/\/hub.docker.com\/r\/pramsey\/pg_tileserv\">https:\/\/hub.docker.com\/r\/pramsey\/pg_tileserv<\/a> <\/li>\n\n\n\n<li>Martin =&gt; <a href=\"https:\/\/github.com\/maplibre\/martin\">https:\/\/github.com\/maplibre\/martin<\/a>: very similar to pg_tileserv, coded in Rust and produced by MapLibre.<\/li>\n\n\n\n<li>GeoServer: also allows the generation of vector tiles<\/li>\n<\/ul>\n\n<p class=\"wp-block-paragraph\">For the purposes of this blog, we will focus on using pg_tileserv.<\/p>\n\n<h3 class=\"wp-block-heading\">pg_tileserv<\/h3>\n\n<p class=\"wp-block-paragraph\">By default, pg_tileserv allows you to generate vector tiles from the geographic tables in the database.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"803\" height=\"525\" src=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/pg_tileserv.png\" alt=\"\" class=\"wp-image-1343\" srcset=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/pg_tileserv.png 803w, https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/pg_tileserv-300x196.png 300w, https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/pg_tileserv-768x502.png 768w\" sizes=\"auto, (max-width: 803px) 100vw, 803px\" \/><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">The real benefit of using a tool like pg_tileserv lies in generating tiles based on more refined analysis.<\/p>\n\n<p class=\"wp-block-paragraph\">Here is an example of a query that can be used to display map data. This query will simplify the geometry based on the zoom threshold and filter the data based on the operator and technology. <\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">CREATE OR REPLACE FUNCTION generate_couvertures_tiles(\n  z integer,\n  x integer,\n  y integer,\n  in_operateur bigint,\n  in_techno character varying)\n    RETURNS bytea\n    LANGUAGE &#039;plpgsql&#039;\n    COST 100\n    VOLATILE PARALLEL UNSAFE\nAS $BODY$\n\n#variable_conflict use_variable\nbegin\n  return (\n    WITH\n    bounds AS (\n      SELECT ST_TileEnvelope(z, x, y) AS geom,\n     (CASE\n      when z &gt;= 12 then 0\n      when z = 11 then 0\n      when z = 10 then 10\n      when z = 9 then 100\n      when z = 8 then 300\n      when z = 7 then 900\n      when z = 6 then 1500\n      when z &lt;= 5 then 3000\n      ELSE 1 END\n       ) as simplify_tolerance\n    ),\n\n    mvtgeom AS (\n      SELECT fid, operateur, date, techno, usage, niveau, dept, filename,\n      ST_AsMVTGeom(\n          ST_Simplify(t.geom,simplify_tolerance), bounds.geom) AS geom\n\n      FROM couverture_theorique t, bounds\n      WHERE ST_Intersects(t.geom, bounds.geom )\n      and operateur = in_operateur\n      and techno = in_techno\n    )\n    SELECT ST_AsMVT(mvtgeom)\n    FROM mvtgeom\n  );\nend;\n$BODY$;<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">To call this function, we will use a URL formatted as follows:<\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">https:&#047;&#047;hostname\/tileserv\/public.generate_couvertures_tiles\/{Z}\/{X}\/{Y}.pbf?in_operateur=28020&amp;in_techno=3G<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">Now you&#8217;re ready to let your imagination run wild to retrieve the data you want and meet the needs of your map.<\/p>\n\n<p class=\"wp-block-paragraph\">We can think of creating clusters, thematic analysis, layer aggregation, or whatever else comes to mind.<\/p>\n\n<h2 class=\"wp-block-heading\">What about performance?<\/h2>\n\n<p class=\"wp-block-paragraph\">All this newfound freedom will lead us into increasingly complex queries, and therefore degraded performance. If each tile takes 10 seconds to generate, the user experience will be poor. <\/p>\n\n<p class=\"wp-block-paragraph\">To address this issue, let&#8217;s remember that the vector tile is based on a fixed grid. It is therefore possible to generate a cache (caching). Great! But how do we do that?  <\/p>\n\n<p class=\"wp-block-paragraph\">Here we go:<\/p>\n\n<p class=\"wp-block-paragraph\">  1 &#8211; Create a tile cache table<\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">CREATE TABLE IF NOT EXISTS tiles_cache\n(\n    z integer NOT NULL,\n    x integer NOT NULL,\n    y integer NOT NULL,\n    operateur bigint NOT NULL,\n    techno character varying COLLATE pg_catalog.&quot;default&quot; NOT NULL,\n    mvt bytea NOT NULL,\n    CONSTRAINT tiles_pkey PRIMARY KEY (z, x, y, operateur, techno)\n)<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">2 &#8211; Generate the cache<\/p>\n\n<p class=\"wp-block-paragraph\">To generate the cache, we first retrieve the grids on which we have data. The maximum zoom threshold (max_zoom) can be defined in the following function. <\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">CREATE OR REPLACE FUNCTION gettilesintersectinglayer(\n  liste_operateur bigint,\n  liste_techno character varying)\n    RETURNS TABLE(x integer, y integer, z integer)\n    LANGUAGE &#039;plpgsql&#039;\n    COST 100\n    VOLATILE PARALLEL UNSAFE\n    ROWS 1000\n\nAS $BODY$\nDECLARE\n  tile_bounds public.GEOMETRY;\n  max_zoom INTEGER := 7;\nBEGIN\n  FOR current_zoom IN 1..max_zoom LOOP\n    FOR _x IN 0..(2 ^ current_zoom - 1)\n    LOOP\n      FOR _y IN 0..(2 ^ current_zoom - 1)\n      LOOP\n        tile_bounds := ST_TileEnvelope(current_zoom, _x, _y);\n        IF EXISTS (\n          SELECT 1 FROM couverture_theorique\n          WHERE ST_Intersects(geom, tile_bounds)\n          AND operateur = liste_operateur\n          AND techno = liste_techno\n        )\n        THEN\n          RAISE NOTICE &#039;Traitement %&#039;, current_zoom || &#039;, &#039; || _x || &#039;, &#039; || _y;\n          z := current_zoom;\n          x := _x;\n          y := _y;\n          RETURN NEXT;\n        END IF;\n      END LOOP;\n    END LOOP;\n  END LOOP;\nEND;\n$BODY$;<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">Using this grid table, we will generate all the tiles and insert them into the cache table.<\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">with oper as (\n  SELECT distinct operateur from couverture_theorique\n),\ntechno as (\n  SELECT distinct techno from couverture_theorique\n)\ninsert into tiles_cache_couverture(z, x, y, operateur, techno, mvt)\nselect tile.z, tile.x, tile.y, oper.operateur,  techno.techno,\ngenerate_couvertures_tiles(tile.z, tile.x, tile.y, oper.operateur, techno.techno)\nfrom techno, oper, GetTilesIntersectingLayer(operateur, techno) as tile;<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">  3 &#8211; Mixed cache\/query rendering<\/p>\n\n<p class=\"wp-block-paragraph\">Once the tiles have been inserted into the cache table, when we want to retrieve a tile, we need to route the lookup so that the function either retrieves the tile from the cache table or generates it on the fly.<\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">CREATE FUNCTION couvertures(z integer, x integer, y integer, liste_operateur integer&#091;], liste_techno character varying&#091;]) RETURNS bytea\n    LANGUAGE plpgsql\n    AS $$\n\n#variable_conflict use_variable\nbegin\n  if (z &lt;= 7 and array_length(liste_operateur,1) = 1) then\n    return (\n      SELECT mvt\n      from tiles_cache_couverture\n      Where tiles_cache_couverture.x=x\n        AND tiles_cache_couverture.y=y\n        AND tiles_cache_couverture.z=z\n        and operateur = any(liste_operateur)\n        and techno = any(liste_techno)\n    );\n  else\n    return (\n      WITH\n      bounds AS (\n        SELECTST_TileEnvelope(z, x, y) AS geom,\n       (CASE\n        when z &gt;= 12 then 0\n        when z = 11 then 0\n        when z = 10 then 10\n        when z = 9 then 100\n        when z = 8 then 300\n        when z = 7 then 900\n        when z = 6 then 1500\n        when z &lt;= 5 then 3000\n        ELSE 1 END\n         ) as simplify_tolerance\n      ),\n\n      mvtgeom AS (\n        SELECT fid, operateur, date, techno, usage, niveau, dept, filename,\n        public.ST_AsMVTGeom(\n           ST_Simplify(t.geom,simplify_tolerance), bounds.geom) AS geom\n\n        FROM couverture_theorique t, bounds\n        WHEREST_Intersects(t.geom, bounds.geom )\n        and operateur = any(liste_operateur)\n        and techno = any(liste_techno)\n      )\n      SELECTST_AsMVT(mvtgeom)\n      FROM mvtgeom\n    );\n  end if;\nend;\n$$;<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">Now, by calling the &#8220;coverages&#8221; layer in pg_tileserv, at the smallest zoom levels (below 8), which are the most demanding in terms of computing geometric simplification, we will use the tile cache. However, when we get closer, we will use on-the-fly tile generation since performance is good. <\/p>\n\n<p class=\"wp-block-paragraph\">For the more advanced users, here&#8217;s a little bonus: an example of a cluster layer generated on the database side. The cluster adapts to the zoom threshold, clustering at the departmental level, then the municipal level, then as a natural cluster (St_ClusterDBSCAN) with dynamic spacing for each zoom threshold, and finally displaying by individual object when very close. We could have imagined a honeycomb-style cluster, which I find more effective, since the issue with PostGIS clustering is that it is calculated within the extent of each tile. This means it splits clusters arbitrarily when there is high density between two tiles.    <\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">CREATE FUNCTION cluster_filtres(z integer, x integer, y integer, filtres text&#091;]) RETURNS bytea\n    LANGUAGE plpgsql\n    AS $_$\n\n#variable_conflict use_variable\nDECLARE\n  query text;\n  result bytea;\nbegin\n  --MISE EN PLACE DES FILTRES\n\n  if ( z &lt; 9) then\n    --Vue par departement\n    query := &#039;\n      WITH\n      bounds AS (\n        SELECT ST_TileEnvelope($1, $2, $3) AS geom\n      ),\n      item_fitler AS (\n        --On r\u00e9cup\u00e8re nos donn\u00e9es filtr\u00e9es\n        SELECT distinct t.id\n        FROM table t\n        WHERE &#039; || filtres || &#039;\n      ),\n      tot_dept AS (\n        select code_departement, count(1) as tot_item\n        from departement d\n        INNER JOIN table t ON ST_Intersects(d.geom, t.geom )\n        INNER JOIN item_fitler ON item_fitler.id = t.id\n        group by code_departement\n      ),\n      mvtgeom AS (\n        SELECT tot_dept.code_departement, tot_dept.tot_item,\n        ST_AsMVTGeom(ST_PointOnSurface(tot_dept.geom), bounds.geom) AS geom\n        FROM tot_dept\n        INNER JOIN bounds ON ST_Intersects(tot_dept.geom, bounds.geom )\n        WHERE tot_item is not null\n      )\n      SELECT ST_AsMVT(mvtgeom)\n      FROM mvtgeom\n    &#039;;\n    --RAISE NOTICE &#039;Calling query (%)&#039;, query;\n    EXECUTE query INTO result USING z, x, y, filtres;\n        return result;\n   \n  elsif ( z &lt;= 10) then\n    --Vue par commune, On ajoute un buffer pour r\u00e9cup\u00e9rer les items autour de la tuile sans devoir le faire sur la France enti\u00e8re\n    query := &#039;\n      WITH\n      bounds AS (\n        SELECT ST_TileEnvelope($1, $2, $3) AS geom\n      ),\n      item_fitler AS (\n        --On r\u00e9cup\u00e8re nos donn\u00e9es filtr\u00e9es\n        SELECT distinct t.id\n        FROM table t\n        INNER JOIN bounds ON ST_Intersects(t.geom, ST_Buffer(bounds.geom, 10000) )\n        WHERE &#039; || filtres || &#039;\n      ),\n      tot_com AS (\n        select insee_com, count(1) as tot_item\n        from commune c\n        INNER JOIN table t ON ST_Intersects(c.geom, t.geom )\n        INNER JOIN item_fitler ON item_fitler.id = t.id\n        group by insee_com\n        having count(1) &gt; 0\n      ),\n      mvtgeom AS (\n        SELECT tot_com.insee_com, tot_com.tot_item,\n        ST_AsMVTGeom(ST_PointOnSurface(tot_com.geom), bounds.geom) AS geom\n        FROM tot_com\n        INNER JOIN bounds ON ST_Intersects(tot_com.geom, bounds.geom )\n        WHERE tot_item is not null\n      )\n      SELECT ST_AsMVT(mvtgeom)\n      FROM mvtgeom\n    &#039;;\n    --RAISE NOTICE &#039;Calling query (%)&#039;, query;\n    EXECUTE query INTO result USING z, x, y, filtres;\n        return result;\n   \n  elsif ( z &lt;= 15) then\n    --Vue par cluster\n    query := &#039;\n      WITH\n      bounds AS (\n        SELECT ST_TileEnvelope($1, $2, $3) AS geom\n      ),\n      item_fitler AS (\n        --On r\u00e9cup\u00e8re nos donn\u00e9es filtr\u00e9es\n        SELECT distinct t.id\n        FROM table t\n        INNER JOIN bounds ON ST_Intersects(t.geom, bounds.geom )\n        WHERE &#039; || filtres || &#039;\n      ),\n      clustered_points AS (\n        SELECT ST_ClusterDBSCAN(t.geom, eps :=\n          (CASE\n            when $1 = 11 then 500\n            when $1 = 12 then 385\n            when $1 = 13 then 280\n            when $1 = 14 then 150\n            when $1 = 15 then 75\n            ELSE 1 END\n          ) , minpoints := 1) over() AS cid,\n        t.fid, t.geom\n        FROM table t\n        INNER JOIN item_fitler s ON s.id = t.id\n        group by t.id, t.geom\n      ),\n      mvtgeom AS (\n        SELECT cid, array_agg(id) as ids,\n        count(1) as tot_item,\n        ST_AsMVTGeom(ST_PointOnSurface(ST_Collect(c.geom)), bounds.geom) AS geom\n        FROM clustered_points c, bounds\n        WHERE ST_Intersects(c.geom, bounds.geom )\n        group by cid, bounds.geom\n      )\n      SELECT public.ST_AsMVT(mvtgeom)\n      FROM mvtgeom\n    &#039;;\n    RAISE NOTICE &#039;Calling query (%)&#039;, query;\n    EXECUTE query INTO result USING z, x, y, filtres;\n        return result;\n  else\n    --vue par objet\n    query := &#039;\n      WITH\n      bounds AS (\n        SELECT ST_TileEnvelope($1, $2, $3) AS geom\n      ),\n      item_fitler AS (\n        --On r\u00e9cup\u00e8re nos donn\u00e9es filtr\u00e9es\n        SELECT distinct t.id\n        FROM table t\n        INNER JOIN bounds ON ST_Intersects(t.geom, bounds.geom )\n        WHERE &#039; || filtres || &#039;\n      ),\n      mvtgeom as (\n        SELECT ST_AsMVTGeom(t.geom, bounds.geom) AS geom, t.id\n        FROM item_fitler t , bounds\n        WHERE ST_Intersects(t.geom, bounds.geom )\n      )\n      SELECT ST_AsMVT(mvtgeom)\n      FROM mvtgeom\n    &#039;;\n    EXECUTE query INTO result USING z, x, y, filtres;\n        return result;\n  end if;\nend;\n$_$;<\/code><\/pre>\n\n<h2 class=\"wp-block-heading\">So, what are the benefits?<\/h2>\n\n<h3 class=\"wp-block-heading\">Basemap<\/h3>\n\n<p class=\"wp-block-paragraph\">When it comes to choosing basemaps, there are many advantages.<\/p>\n\n<p class=\"wp-block-paragraph\">You can fairly easily customize a basemap by modifying the display settings you want to use for each element. To do this, you can rely on style files like those provided by Etalab: <a href=\"https:\/\/openmaptiles.geo.data.gouv.fr\/\">https:\/\/openmaptiles.geo.data.gouv.fr\/<\/a>. <\/p>\n\n<p class=\"wp-block-paragraph\">If you host the style files yourself, you can modify them to choose the style of the elements. Here&#8217;s what the configuration of an element from a vector tile looks like: <\/p>\n\n<pre class=\"wp-block-code\"><code class=\"\" data-line=\"\">    {\n      &quot;id&quot;: &quot;landuse-commercial&quot;,\n      &quot;type&quot;: &quot;fill&quot;,\n      &quot;source&quot;: &quot;openmaptiles&quot;,\n      &quot;source-layer&quot;: &quot;landuse&quot;,\n      &quot;filter&quot;: &#091;\n        &quot;all&quot;,\n        &#091;&quot;==&quot;, &quot;$type&quot;, &quot;Polygon&quot;],\n        &#091;&quot;==&quot;, &quot;class&quot;, &quot;commercial&quot;]\n      ],\n      &quot;layout&quot;: {\n        &quot;visibility&quot;: &quot;visible&quot;\n      },\n      &quot;paint&quot;: {\n        &quot;fill-color&quot;: &quot;hsla(0, 60%, 87%, 0.23)&quot;\n      }\n    },<\/code><\/pre>\n\n<p class=\"wp-block-paragraph\">For example, it is possible to extrude buildings if you want a 3D rendering<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"803\" height=\"374\" src=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/3D.png\" alt=\"\" class=\"wp-image-1344\" srcset=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/3D.png 803w, https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/3D-300x140.png 300w, https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/3D-768x358.png 768w\" sizes=\"auto, (max-width: 803px) 100vw, 803px\" \/><\/figure>\n<\/div>\n<p class=\"wp-block-paragraph\">Vector display also allows labels\/icons to always be displayed in the correct reading direction.<\/p>\n\n<h3 class=\"wp-block-heading\">Choosing the layer display order<\/h3>\n\n<p class=\"wp-block-paragraph\">It&#8217;s also worth noting that it is possible to change the display order of layers from the basemap and business layers, which is not possible with WMTS-type basemaps. This allows municipality labels to stand out on top of a business layer. <\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"803\" height=\"441\" src=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/couche.png\" alt=\"\" class=\"wp-image-1345\" srcset=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/couche.png 803w, https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/couche-300x165.png 300w, https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/couche-768x422.png 768w\" sizes=\"auto, (max-width: 803px) 100vw, 803px\" \/><\/figure>\n<\/div>\n<h3 class=\"wp-block-heading\">Vector layer<\/h3>\n\n<p class=\"wp-block-paragraph\">Since the layer is vector-based, it is also possible to retrieve its attributes.<\/p>\n\n<h3 class=\"wp-block-heading\">Style editing tool<\/h3>\n\n<p class=\"wp-block-paragraph\">There are several tools available for modifying the style of vector data. However, I recommend <em>Maputnik<\/em>, which is very comprehensive and accessible =&gt; <a href=\"https:\/\/maplibre.org\/maputnik\/#0.77\/0\/0\">https:\/\/maplibre.org\/maputnik\/#0.77\/0\/0<\/a> <\/p>\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n<p class=\"wp-block-paragraph\">In conclusion, vector tiles represent a significant advance in the dissemination and display of spatial data, enabling the creation of advanced mapping applications. However, they still need to be integrated into robust tools and high-performance methodologies in order to meet the growing need for precision and responsiveness. Vector tile technology is essential for the future of digital mapping, offering a balance between performance, flexibility, and interactivity.  <\/p>\n\n<h2 class=\"wp-block-heading\">Future directions?<\/h2>\n\n<p class=\"wp-block-paragraph\">Authentication and roles through pg_tileserv. In a future blog post, we could explore how to combine token-based authentication, the pg_tileserv service, and PostgreSQL security through Row Level Security. This would allow permissions to be managed at the object level, natively within PostgreSQL.  <\/p>\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<p class=\"wp-block-paragraph\"><em>Author: Matthieu Etourneau<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>What is a tile? To display spatial data on a map, there are different data formats, including the vector tile, which brings new possibilities for displaying data (fast data access, easy style modification, direct user interaction with objects). Indeed, a digital map can theoretically display data at any scale and in any location, but this &#8230; <a title=\"Navigating the Era of Vector Tiles: Techniques and Applications\" class=\"read-more\" href=\"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/\" aria-label=\"Read more about Navigating the Era of Vector Tiles: Techniques and Applications\">Read more<\/a><\/p>\n","protected":false},"author":5,"featured_media":4994,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[60],"tags":[],"class_list":["post-4993","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized-en"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Vector Tiles with PostGIS and pg_tileserv | NEOGEO<\/title>\n<meta name=\"description\" content=\"How to produce, cache, and style vector tiles with PostGIS and pg_tileserv, with SQL examples.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Vector Tiles with PostGIS and pg_tileserv | NEOGEO\" \/>\n<meta property=\"og:description\" content=\"How to produce, cache, and style vector tiles with PostGIS and pg_tileserv, with SQL examples.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/\" \/>\n<meta property=\"og:site_name\" content=\"NEOGEO\" \/>\n<meta property=\"article:published_time\" content=\"2024-07-09T04:38:24+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-23T14:27:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"996\" \/>\n\t<meta property=\"og:image:height\" content=\"559\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Philippe DESTR\u00c9\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Philippe DESTR\u00c9\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/\"},\"author\":{\"name\":\"Philippe DESTR\u00c9\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#\\\/schema\\\/person\\\/69685e84302b110f77d59dc8ee6e2d40\"},\"headline\":\"Navigating the Era of Vector Tiles: Techniques and Applications\",\"datePublished\":\"2024-07-09T04:38:24+00:00\",\"dateModified\":\"2026-10-23T14:27:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/\"},\"wordCount\":1161,\"publisher\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/neogeo.fr\\\/wp-content\\\/uploads\\\/2024\\\/07\\\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg\",\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/\",\"url\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/\",\"name\":\"Vector Tiles with PostGIS and pg_tileserv | NEOGEO\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/neogeo.fr\\\/wp-content\\\/uploads\\\/2024\\\/07\\\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg\",\"datePublished\":\"2024-07-09T04:38:24+00:00\",\"dateModified\":\"2026-10-23T14:27:40+00:00\",\"description\":\"How to produce, cache, and style vector tiles with PostGIS and pg_tileserv, with SQL examples.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/#primaryimage\",\"url\":\"https:\\\/\\\/neogeo.fr\\\/wp-content\\\/uploads\\\/2024\\\/07\\\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg\",\"contentUrl\":\"https:\\\/\\\/neogeo.fr\\\/wp-content\\\/uploads\\\/2024\\\/07\\\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg\",\"width\":996,\"height\":559},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/navigating-the-era-of-vector-tiles-techniques-and-applications\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Navigating the Era of Vector Tiles: Techniques and Applications\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/\",\"name\":\"NEOGEO\",\"description\":\"Optimisez,\u00a0valorisez,\\u2028partagez\u00a0vos\u00a0donn\u00e9es\u00a0spatiales\",\"publisher\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#organization\",\"name\":\"NEOGEO\",\"url\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/neogeo.fr\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/NEOGEO-LOGO.svg\",\"contentUrl\":\"https:\\\/\\\/neogeo.fr\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/NEOGEO-LOGO.svg\",\"width\":145,\"height\":42,\"caption\":\"NEOGEO\"},\"image\":{\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/#\\\/schema\\\/person\\\/69685e84302b110f77d59dc8ee6e2d40\",\"name\":\"Philippe DESTR\u00c9\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b366889cd846213d38b977f63dd60b6af7e9ed5b067a024a76fe0865155ef9db?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b366889cd846213d38b977f63dd60b6af7e9ed5b067a024a76fe0865155ef9db?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b366889cd846213d38b977f63dd60b6af7e9ed5b067a024a76fe0865155ef9db?s=96&d=mm&r=g\",\"caption\":\"Philippe DESTR\u00c9\"},\"url\":\"https:\\\/\\\/neogeo.fr\\\/en\\\/author\\\/pdestreneogeo\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Vector Tiles with PostGIS and pg_tileserv | NEOGEO","description":"How to produce, cache, and style vector tiles with PostGIS and pg_tileserv, with SQL examples.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/","og_locale":"en_US","og_type":"article","og_title":"Vector Tiles with PostGIS and pg_tileserv | NEOGEO","og_description":"How to produce, cache, and style vector tiles with PostGIS and pg_tileserv, with SQL examples.","og_url":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/","og_site_name":"NEOGEO","article_published_time":"2024-07-09T04:38:24+00:00","article_modified_time":"2026-10-23T14:27:40+00:00","og_image":[{"width":996,"height":559,"url":"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg","type":"image\/jpeg"}],"author":"Philippe DESTR\u00c9","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Philippe DESTR\u00c9","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/#article","isPartOf":{"@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/"},"author":{"name":"Philippe DESTR\u00c9","@id":"https:\/\/neogeo.fr\/en\/#\/schema\/person\/69685e84302b110f77d59dc8ee6e2d40"},"headline":"Navigating the Era of Vector Tiles: Techniques and Applications","datePublished":"2024-07-09T04:38:24+00:00","dateModified":"2026-10-23T14:27:40+00:00","mainEntityOfPage":{"@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/"},"wordCount":1161,"publisher":{"@id":"https:\/\/neogeo.fr\/en\/#organization"},"image":{"@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/#primaryimage"},"thumbnailUrl":"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg","inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/","url":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/","name":"Vector Tiles with PostGIS and pg_tileserv | NEOGEO","isPartOf":{"@id":"https:\/\/neogeo.fr\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/#primaryimage"},"image":{"@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/#primaryimage"},"thumbnailUrl":"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg","datePublished":"2024-07-09T04:38:24+00:00","dateModified":"2026-10-23T14:27:40+00:00","description":"How to produce, cache, and style vector tiles with PostGIS and pg_tileserv, with SQL examples.","breadcrumb":{"@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/#primaryimage","url":"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg","contentUrl":"https:\/\/neogeo.fr\/wp-content\/uploads\/2024\/07\/fond-futuriste-formes-geometriques_23-2149116405-e1719847241388.jpg","width":996,"height":559},{"@type":"BreadcrumbList","@id":"https:\/\/neogeo.fr\/en\/navigating-the-era-of-vector-tiles-techniques-and-applications\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/neogeo.fr\/en\/"},{"@type":"ListItem","position":2,"name":"Navigating the Era of Vector Tiles: Techniques and Applications"}]},{"@type":"WebSite","@id":"https:\/\/neogeo.fr\/en\/#website","url":"https:\/\/neogeo.fr\/en\/","name":"NEOGEO","description":"Optimisez,\u00a0valorisez,\u2028partagez\u00a0vos\u00a0donn\u00e9es\u00a0spatiales","publisher":{"@id":"https:\/\/neogeo.fr\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/neogeo.fr\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/neogeo.fr\/en\/#organization","name":"NEOGEO","url":"https:\/\/neogeo.fr\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/neogeo.fr\/en\/#\/schema\/logo\/image\/","url":"https:\/\/neogeo.fr\/wp-content\/uploads\/2026\/06\/NEOGEO-LOGO.svg","contentUrl":"https:\/\/neogeo.fr\/wp-content\/uploads\/2026\/06\/NEOGEO-LOGO.svg","width":145,"height":42,"caption":"NEOGEO"},"image":{"@id":"https:\/\/neogeo.fr\/en\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/neogeo.fr\/en\/#\/schema\/person\/69685e84302b110f77d59dc8ee6e2d40","name":"Philippe DESTR\u00c9","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b366889cd846213d38b977f63dd60b6af7e9ed5b067a024a76fe0865155ef9db?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b366889cd846213d38b977f63dd60b6af7e9ed5b067a024a76fe0865155ef9db?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b366889cd846213d38b977f63dd60b6af7e9ed5b067a024a76fe0865155ef9db?s=96&d=mm&r=g","caption":"Philippe DESTR\u00c9"},"url":"https:\/\/neogeo.fr\/en\/author\/pdestreneogeo\/"}]}},"_links":{"self":[{"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/posts\/4993","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/comments?post=4993"}],"version-history":[{"count":1,"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/posts\/4993\/revisions"}],"predecessor-version":[{"id":4995,"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/posts\/4993\/revisions\/4995"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/media\/4994"}],"wp:attachment":[{"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/media?parent=4993"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/categories?post=4993"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/neogeo.fr\/en\/wp-json\/wp\/v2\/tags?post=4993"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}