robertleeplummerjr / Leaflet.glify

fully functional, ridiculously fast web gl renderer plugin for leaflet
https://robertleeplummerjr.github.io/Leaflet.glify
MIT License
474 stars 84 forks source link

Polygons Not Rendering Correctly #137

Closed RayLarone closed 3 months ago

RayLarone commented 2 years ago

I have a dataset containing thousands of Car Parking Spaces I want to show on a map. However when I use the L.glify.shapes function the polygons do not render correctly? Is there any way I can resolve this issue? Many thanks

You can see the code here: https://codepen.io/RayL1/pen/WNzbddJ

let map = L.map('map', {maxZoom: 30}).setView([ 51.524676, -0.181212], 22);

let data = {
  "type": "FeatureCollection",
  "features": [
    { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ -0.181100, 51.524730 ], [ -0.181155, 51.524703 ], [ -0.181169, 51.524714 ], [ -0.181114, 51.524741 ], [ -0.181100, 51.524730 ] ] ] } },
    { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ -0.181158, 51.524702 ], [ -0.181212, 51.524676 ], [ -0.181226, 51.524687 ], [ -0.181172, 51.524713 ], [ -0.181158, 51.524702 ] ] ] } },
    { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ -0.181214, 51.524675 ], [ -0.181268, 51.524649 ], [ -0.181282, 51.524660 ], [ -0.181228, 51.524686 ], [ -0.181214, 51.524675 ] ] ] } },
    { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ -0.181271, 51.524648 ], [ -0.181325, 51.524622 ], [ -0.181339, 51.524633 ], [ -0.181285, 51.524659 ], [ -0.181271, 51.524648 ] ] ] } }
  ]
}

This is what is should look like:

L.geoJSON(data, {
  style:{
    "color": "black", 
    "weight":4, 
    "fill":false, 
    "opacity":0.8
  }
}).addTo(map);

Screenshot 2022-06-30 at 22 07 07

This is how it comes out:

L.glify.shapes({
  map: map,
  data: data,
  border:true,
  color:{
    r:0,
    g:0,
    b:245
  }
});

Screenshot 2022-06-30 at 22 07 36

Difference:

Screenshot 2022-06-30 at 22 06 41

RayLarone commented 2 years ago

Ok, so I've been looking through the glify code and it looks like this loss of precision is happening once the lat/lng coordinates get converted to pixel coordinates (latLonToPixel) and then these pixel coordinates are converted to a Float32Array. As these pixel coordinates are pretty long decimals, any precision below a few meters gets lost by the Float32Array. One solution would be to use a Float64Array although I don't think WebGL supports this and it might affect performance.

Another way to solve this would be to reduce the size of these pixel coordinates. If you are creating a map that needs precision down the to meter or centimetre, you are unlikely to be mapping anything bigger than a city, district or country. So, it should be possible to normalise these pixel coordinates around your area of interest, which would solve the polygon rendering issue above, and give you centimetre or even millimetre precision.

Just by way of explanation, if you go to this article (https://leafletjs.com/examples/extending/extending-2-layers.html) and scroll down to section about "The Pixel Origin" you will see that the pixel origin (0, 0) is located in the top left corner of the map, and all geometries are plotted in relation to this pixel origin. This means anything close to this origin will be plotted by glify with a much higher precision than things far away from it. (again due to the Float32Array conversion)

Screenshot 2022-07-04 at 11 44 48

Note that the black border is the correct polygon plotted with base leaflet and the blue fill is plotted by glify. From the above map, you can see that the polygons which are closer to the Pixel Center are much more accurate when compared to the polygons which are far way.

A Solution

One way to solve this would be to move the pixel origin to the centre of the area we are plotting. First we could calculate the centroid of the GeoJson passed as an argument coord_centroid = getCentroid(geojson). Or if this takes too much time we could just pick the first set of coordinates in the geojson (or the map center with map.getCenter()), as this will likely be a better guess than the default pixel centre of (0, 0). Alternatively, the user could just pass the geojson centroid manually as an optional argument.

The next step would be to convert this centroid to pixel coordinates: latLonToPixel(coord_centroid).

Using these centroid pixel coordinates we then translate our geojson so it is as close to the Pixel Origin as possible:

//shapes.ts lines 224-234

for (let i = 0, iMax = triangles.length; i < iMax; i) {
  pixel = map.project(new LatLng(triangles[i++], triangles[i++]), 0);
  vertices.push(
    pixel.x - centroid.x,
    pixel.y - centroid.y,
    chosenColor.r,
    chosenColor.g,
    chosenColor.b,
    alpha
  );
}

We can then offset the mapMatrix so the polygons get set back to their original place on the map:

// shapes.ts lines 266-269

mapMatrix
  .setSize(canvas.width, canvas.height)
  .scaleTo(scale)
  .translateTo(-offset.x + centroid.x, -offset.y + centroid.y);

Original Question

If this gets applied to my original question, we can pass in a centroid of around (51.524730, -0.181100) and the output of these changes gives the following:

Screenshot 2022-07-04 at 12 33 25

If you look at the scale bar below you can see this now has an accuracy of centimetres or even millimeters (which might address this issue https://github.com/robertleeplummerjr/Leaflet.glify/issues/54):

Screenshot 2022-07-04 at 12 35 34

As well as this, the polygons also don't "jitter" as you pan around. (i.e. it should solves issues like this https://github.com/robertleeplummerjr/Leaflet.glify/issues/119)

This solution could also be applied to points and lines.

Do you think this could be implemented? And thanks for the great library :)