azure-maps

Azure Maps not rendering vector tiles correctly


I'm having an issue with rendering a polygon from a vector tile source at high zoom levels. I'm using https://github.com/NetTopologySuite/NetTopologySuite.IO.VectorTiles to generate the tiles and have confirmed using a simple vuejs using @mapbox/vector-tile npm package that I see what is expected (a simple 4-edge polygon), so I trust the mapbox vector tiles are generated correctly.

I've added 3 different polygon samples I created as individual feature collection in a json file, converted to vector file and rendered using a simple html for azure maps with a single vector tile source plus line/fill layer. The page also renders a tile grid and displays the tile info.

Each sample is just the same polygon but made slightly longer each time. As shown in the images, the rendering gets worse with the size of the polygon.

My gut says the tile generation might be wrong but viewing the tile in a separate vuejs app appears to negate this thought. I could be wrong of course, and it seems implausible to me that such a simple polygon would be a problem for azuremaps/maplibre.

Any ideas what might be wrong please as I'm stuck at this point.

Edit:

I loaded the same sample3 mvt files into qgis (3.36.3) and it renders them fine. This would then imply the tile generation is fine and the problem is with the rendering in maplibre?

enter image description here

Edit 2:

Here's a repo of the tiles for sample3.json: MVT test files repo

Sample1 seems to render fine:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "id": 1,
        "featureName": "pedestrian"
      },
      "geometry": {
        "coordinates": [
          [
            [
              153.02303214664835,
              -27.47153798017556
            ],
            [
              153.02361397317628,
              -27.472110106801587
            ],
            [
              153.02413908737213,
              -27.471707568508087
            ],
            [
              153.02356566267133,
              -27.471135439790835
            ],
            [
              153.02303214664835,
              -27.47153798017556
            ]
          ]
        ],
        "type": "Polygon"
      }
    }
  ]
}

enter image description here

Sample2 starting to see some rendering issue:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "id": 1,
        "featureName": "pedestrian"
      },
      "geometry": {
        "coordinates": [
          [
            [
              153.02303214664835,
              -27.47153798017556
            ],
            [
              153.0239584480898,
              -27.472482826140364
            ],
            [
              153.02448566274165,
              -27.472082152801704
            ],
            [
              153.02356566267133,
              -27.471135439790835
            ],
            [
              153.02303214664835,
              -27.47153798017556
            ]
          ]
        ],
        "type": "Polygon"
      }
    }
  ]
}

enter image description here

Sample3 pretty bad now:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "id": 1,
        "featureName": "pedestrian"
      },
      "geometry": {
        "coordinates": [
          [
            [
              153.02303214664835,
              -27.47153798017556
            ],
            [
              153.0243377004698,
              -27.472866763505785
            ],
            [
              153.0248843639639,
              -27.47245746377172
            ],
            [
              153.02356566267133,
              -27.471135439790835
            ],
            [
              153.02303214664835,
              -27.47153798017556
            ]
          ]
        ],
        "type": "Polygon"
      }
    }
  ]
}

enter image description here


Solution

  • Azure Maps uses MapLibre for rendering vector tiles under the covers, and MapLibre is a fork of MapBox GL JS, so I would be surprised if the issue is there.

    I suspect the issue is with the NetTopologySuite.IO.VectorTiles library. Looking into that repo I see this pull request for an issue that sounds related: https://github.com/NetTopologySuite/NetTopologySuite.IO.VectorTiles/pull/30

    This issue may also be related: https://github.com/NetTopologySuite/NetTopologySuite.IO.VectorTiles/issues/29

    Update:

    When I originally tried the GeoJSON data to create tiles, they worked fine for me. So that's why I asked for your tiles. Digging into this, I found that pretty much every vector tile render that uses JavaScript/WebGL has issues rendering these tiles. Inspecting the tile, it looks like each tile contains the full original polygon. Normally, when creating vector tiles the data being added to a tile is clipped to an area just a bit larger than the bounding box of a tile (usually a few pixels to allow for rendering of outlines without tile seams appearing). Inspecting your tiles it appears as if the full polygon shape is in each tile which is unusual. When zoomed in closer, the polygon ends up crossing multiple tiles. In ordered to be rendered on the map cleanly, the map then has to clip the polygon in JavaScript. The map uses a library called Earcut to do this quickly, however it is not as accurate as libraries that are usually used when creating tiles. It's a tradeoff for performance. When polygons overlap a little bit between tiles, this earcut method works fine, however, when there is a lot of overlap, like in this case, artifacts occur, like you are seeing. This would also explain why these tiles work fine in QGIS, as it's a native desktop application that is using more powerful libraries for clipping the polygons and is able to perform this much faster than JavaScript can.

    So, the solution to resolve this issue is to clip the polygon data to a bounding box a bit larger than each tile it is being added to. I do this automatically when creating tiles just as a performance thing since most of my polygons have more than 4 coordinates, so clipping them reduces the complexity of the data in each tile and usually makes them a lot smaller in file size. I'm assuming that you already have code for importing your GeoJSON and reading them as NetTopologySuite features. Here is a quick sample of how to take a NetTopologySuite Feature, clip its geometry, and write it to a tile.

    //Define the tile info.
    var tileInfo = new NetTopologySuite.IO.VectorTiles.Tiles.Tile(x, y, zoom);
    
    //Create vector tile for the tile info. Normally you woult use VectorTileTree if creating a tiles over an area.
    var vectorTile = new NetTopologySuite.IO.VectorTiles.VectorTile(){
        TileId = tileInfo.Id
    };
    
    //Add a layer to the vector tile. 
    //As an optimization, you could wait to see if any polygons intersect the tile before adding a layer.
    var myDataLayer = new NetTopologySuite.IO.VectorTiles.Layer()
    {
        Name = "myDataLayer"
    };
    vectorTile.Layers.Add(myDataLayer);
    
    //Decide how much to buffer the bounding box of a tile, in percentage.
    //Usually a number between 2 and 5 is plenty for lines and polygons.
    //For points, you may want to use a higher number depending on size of icons being used.
    var tileBufferPercentage = 3; 
    
    //Get a polygon for a buffered bounding box of the tile.
    var tilePolygon = tileInfo.ToPolygon(tileBufferPercentage);
    
    //Check to see if the polygon intersects the tile. No sense adding polygon to tile otherwise.
    if (polygonFeature.Geometry.Intersects(tilePolygon))
    {
        //Clip the polygon to the input polygon, by finding the intersection.
        //Don't overwrite the polygonFeature.Geometry otherwise it will cause issues for other tiles.
        var clippedPolygon = polygonFeature.Geometry.Intersection(tilePolygon);
    
        //Add the clipped polygon to the layer.
        myDataLayer.Features.Add(new NetTopologySuite.Features.Feature(clippedPolygon, polygonFeature.Attributes));
    }