QGIS – How to Generate a Mapbox Vector Tile Correctly

gdalmapbox-gl-jsogr2ogrqgisvector-tiles

I have a GeoJSON file that has this data

  {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              70.224609375,
              30.524413269923986
            ],
            [
              106.875,
              30.524413269923986
            ],
            [
              106.875,
              47.517200697839414
            ],
            [
              70.224609375,
              47.517200697839414
            ],
            [
              70.224609375,
              30.524413269923986
            ]
          ]
        ]
      }
    }
  ]
}

This file is saved as featureFile.geojson on my local machine. I converted this geojson file to MVT using ogr2ogr command

ogr2ogr -f MVT -dsco MINZOOM=0 -dsco MAXZOOM=10 ./data ./featureFile.geojson

After execution, this command does generate a directory structure for Vector Tiles. There is a metadata.json file which has this data

    {
  "name":"target",
  "description":"",
  "version":2,
  "minzoom":0,
  "maxzoom":10,
  "center":"88.5498047,39.0208070,0",
  "bounds":"70.2246094,30.5244133,106.8750000,47.5172007",
  "type":"overlay",
  "format":"pbf",
  "json":"{\n  \"vector_layers\":[\n    {\n      \"id\":\"featureFile\",\n      \"description\":\"\",\n      \"minzoom\":0,\n      \"maxzoom\":10,\n      \"fields\":{\n      }\n    }\n  ],\n  \"tilestats\":{\n    \"layerCount\":1,\n    \"layers\":[\n      {\n        \"layer\":\"featureFile\",\n        \"count\":1,\n        \"geometry\":\"Polygon\",\n        \"attributeCount\":0,\n        \"attributes\":[\n        ]\n      }\n    ]\n  }\n}"
}

However, when I move any file (ex: data/0/0/1) out of the folder structure and open it on QGIS-LTR on a Mapbox base map, it does not recognize the projection system and renders it in a completely incorrect area.

enter image description here

but if the file is within its folder structure, it renders correctly on QGIS

enter image description here

I don't need to display the MVT on QGIS but instead, I need to render it on my Mapbox Gl JS basemap

I uploaded this entire directory structure on S3 and created an endpoint to retrieve the tiles in the {z}/{x}/{y} format. Although the API calls are successful and the tile is retrieved, it does not show on the map I have used.

The basemap I used on Mapbox-Gl-JS is

  const map = new mapboxgl.Map({
  container: mapContainer.current,
  style: "mapbox://styles/mapbox/outdoors-v11",
  center: [-74.507805277281307, 41.393970909967798],
  zoom: 12,

I have tried to hardcode EPSG:4326 and EPSG:3857 while conversion but neither worked.

Best Answer

After a few rounds of trial and error, I figured out a way to properly generate vector tiles. This is especially useful if you have no idea what the projection system of the shapefiles is.

Step 1: Convert the shapefiles from your source to a GeoJSON. In my case, I retrieved it from Postgres DB. It merges all the shapefiles to form one GeoJSON file

ogr2ogr -f GeoJSON output.geojson PG:'dbname=<> user=<> password=<> host=<>' -sql 'select * from <table_name>' -lco RFC7946=YES

Step 2: Use Tippecanoe to convert this GeoJSON to mbtiles. Please keep the names of the file consistent throughout [All files are named 'output' here and only the extension changes]

tippecanoe -o output.mbtiles -Z 0 -z 15 output.geojson

This step generated a .mbtiles file. Now these mbtiles need to be converted to a Vector tile folder structure

Step 3: Use ogr2ogr tool to convert mbtiles to MVT. [Do not use mbutil since it is not supported anymore and may cause issues]. Here 'data' is the name of my folder but you can name it anything else.

ogr2ogr -f MVT ./data output.mbtiles

Now you will have a folder structure that you can upload to S3. However, there are a few things you need to keep in mind.

  1. All the .pbf files that get generated need to set Content-Encoding: gzip for all the files except for the metadata.json file. You can set it manually from the AWS console or add it to your copy command.

    aws s3 cp --metadata-directive REPLACE --content-encoding 'gzip' <SOURCE_PATH> <DESTINATION_PATH> --recursive --exclude 'metadata.json'

  2. To render the vector tiles in your code, follow this format First add the S3 location as source

     map.addSource("vector-tile-test", {
     "type": "vector",
     "tiles": [
       "https://<your-s3-url>{z}/{x}/{y}.pbf"
     ],
     "maxzoom": 10
    });
    

Then add the Vector tile layer. Note that source-layer value is 'output'. You can find the value of your source layer in metadata.json file as well.

map.addLayer({
    "id": "vector-tile-layer",
    "type": "fill",
    "source": "vector-tile-test",
    "source-layer": "output",
    "paint": {
        "fill-color": "#FF0000",
        "fill-opacity": 1.0,
    }
  });

This should generate your vector tiles and render them accurately.

Related Question