-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#1231 Use polygon centroid for 'point' symbol placements #2678
Conversation
xSum += coord.x - xC; | ||
ySum += coord.y - yC; | ||
len++; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please replace with a simple for
loop for performance and simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I think we should use polygon centroid algorithm like this https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon, which will choose a better center.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent thanks for pointing it out. There was an error in the function on wikipedia that has now been corrected. I have the additional changes but it causes tests to fail if we keep this change as symbol-placment: 'point'.
The side effect is that lineStrings will use the centroid which I believe is incorrect. I did ask if a symbol-placment: 'centroid' should be used in #1231. This would require changes to the style spec as well, I may need some help/pointers to understand how that is also updated/released.
@blanchg thanks for starting this! Lack of this feature has been a huge pain point for us. In my opinion, Pole of Inaccessibility is the ideal way to position labels on polygons. I can't speak to the performance implications of doing it in real time compared to a centroid. If they are negligible then I hope you would consider using this method instead. Here's a Turf module that implements it, and some more background. |
That looks like a great solution, however that turf module uses turf-buffer which uses jsts to do the actual bufferring, that would increase the size of mapbox significantly. I will look to see by how much if we only include the polygon offset/buffer code from jsts. |
@dpieri I found a small library that performs the Pole Of Inaccessibility and extended it to support holes: https://github.com/blanchg/LabelPoint, however it appears to interfere when a lot of polygon draws so that on higher zoom levels the polygons stop rendering completely and the +/- zoom buttons never get displayed. Is there a way to find out why this is occuring (is there some sort of timeout maybe?) without going step by step through the code? |
I'm not sure, maybe @mourner could point you in the right direction. |
Thanks, I found that the simplification can produce zero area polygons with multiple points that was causing issues. |
This is ready now. It continues to use symbol-placment point. If the ring is closed e.g. first and last point are the same and has an area != 0 then it chooses the Pole of Inaccessibility otherwise it falls back to the first point as it was before. This works then for points, lines and polygons that have been simplified down to a point/line. It adds about 1-2kb to the output, fps benchmarks look very similar. I am positive there are things I can do better so please let me know! Thanks |
// http://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/ | ||
module.exports = function (a, b, c) { | ||
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to cut out small functions like this into a separate file. Maybe move to util.js
? Or maybe we should have geom_util.js
for all functions like this.
@blanchg thank you for contributing!
thanks! 👓 @lucaswoj @mourner @mollymerp for review |
0262c40
to
36a7aeb
Compare
Thanks for the reviews @mollymerp and @mourner ! I managed to squish the changes and remove the unrelated ones thanks for pointing that out. I added some unit tests for polygon_poi and I have a render test but I am uncertain how to proceed to get the render test included. I have a feature branch on a fork, I assume I do a PR on the test suite that should only be pulled in once this PR has been? |
I found that building polygons in the render test suite data have outer rings that don't match the tile specification (2.1). They have both positive and negative areas as outer rings. I also found util/classify_rings that handles both cases. Will update to use that. |
36a7aeb
to
d22feac
Compare
Updated to use util/classify_rings which now handles polygons and holes of any winding order. One raster test will fail until it is updated. I still need some information on how to get the test-suite aligned with this PR. |
You can see information on how to update the tests here. Once your tests are satisfactory, make a PR in mapbox/mapbox-gl-test-suite repo and then you can update the sha for that commit in the package.json for this PR. I don't see the raster tests failing, I just see the @mourner I think you should have final review here. |
@mollymerp Thanks for the information. I have that PR submitted now. The debug/tile test fails because one of the labels is in a closed polygon so it is now rendered at the center of that polygon, every other test doesn't render labels on closed polygons. I have added the line style and zoomed to the label that moved slightly in the debug/tile test to highlight why it is different. |
8da9d99
to
0d3eaac
Compare
This is now up to date with the head, squashed, unit tests, there is a render test added and updated the failing debug test. PR in the test-suite and sha pointing to it in package.json. I think finally it is ready to merge 😃 🎉 🎈 |
Fantastic! I'll do a final review tomorrow and merge if everything's good. |
Any idea when this will make it into a point release? |
This got a bit sidetracked but hopefully we will merge this week, followed by a point release next week. |
@blanche hey, can you rebase this branch and the test suite one on master? |
i think you ment @blanchg ;) |
@@ -14,6 +14,8 @@ var clipLine = require('../../symbol/clip_line'); | |||
var util = require('../../util/util'); | |||
var loadGeometry = require('../load_geometry'); | |||
var CollisionFeature = require('../../symbol/collision_feature'); | |||
var poleOfInaccessibility = require('../../util/polygon_poi'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An (admittedly unwritten) convention in GL JS is that file names match the names of the classes / functions they export. Can we rename polygon_poi
-> find_pole_of_inaccessibility
or similar?
This looks good to me! I'm excited to 🚢 . |
uses a copy of @mourner mapbox/polylabel to find it
072f81d
to
ae1b95a
Compare
@mourner @mollymerp @lucaswoj Thank you all for your help, I think this is finally ready :D 👍 |
Set up a JSBin debug page to demonstrate how it works in practice, using the choropleth from examples as a base but adding a population label: http://output.jsbin.com/tajiwociha Generally it's pretty cool, but there are a few issues that are worth noting, although they don't necessarily warrant an immediate fix, and not all are possible to fix (e.g. because of tiling or the the way algorithm works).
@samanpwbb can you play with the sample and tell us what you think? Should we go ahead with the feature in its current state? |
It currently just adds a label for every polygon in a multipolygon. We could alter the algororithm to filter out labels for polygon where anchor point has a distance to the polygon below a certain threshold, but this would probably require a new obscure style-spec property, or have a very good default. Another option is to always only add one label to a multipolygon, choosing an anchor in one of the polygons that fits the best. |
It's also worth noting that the performance cost on the worker side is pretty high — on this example but without a basemap (just leaving the population map alone), placing polygon anchors takes ~18% of the worker time. If I switch from 2px to 4px precision, it takes ~13%. It may be acceptable though, since this is an opt-in feature, and doesn't affect our styles — only custom visualizations where you explicitly want to use auto polygon anchors. |
I can see cases where both would be desireable, but most of the time a multipolygon should only get a single label. you'd almost never want to include more than one label on a choropleth data viz (and this could even result in a map that 'lies' – making it look like a place has double the value that it actually has). On the other hand, a cartographer may want to place a tree icon in the center of each piece of a park multipolygon. I vote to enforce 1 label per multipolygon, leaving open the option of adding a new property to give users control in the future. |
@samanpwbb makes sense! Regarding polygons that have labels placed close to boundaries, I referred to a different case: Here, the label is not further up because it calculates anchor based on the polygon geometry with a buffer, including an "invisible" part on the bottom. And regarding the case below, — it's just a property of the algorithm — in this case a label in the center may have a few pixels smaller distance to the polygon, and considered worse than the one picked; to fix this, we would have to modify the algorithm to somehow prefer points closer to centroid even if they have smaller distance, but this is not trivial. |
After thinking more about the problem and various edge cases, this is my favorite option for handling multi-poly labelling. It wouldn't add a new property and would ideally look good in most cases. I also recognize that this is the hard approach. Again, like I said in the original post, my take on this PR is that it's a major improvement over what we have now no matter what, and I think we should merge something imperfect sooner (and ticket follow up work) rather than wait until it's perfect. |
How do we pick the threshold then? E.g. something arbitrary like 2 "ems" assuming em = 24px? |
Yep something like that sounds good. See how it feels. Let me bring up another idea. I don't know if it's at all technically feasible to incorporate into your label placement algorithm. Archipelago-type features are really common in the world. Louisiana is mostly a single mass, but has an archipelago on it's southern tip. But what about the Philippines? Is there a way to incorporate multipolygons into the label placement algorithm in such a way that the label still ends up getting placed inside a shape, so we are able to handle archipelagos well? |
I found if you ignore polygons that have a cellSize < precision * 2 it seems to handle the US quite well and fixes the label showing up on the islands off Louisiana and Texas when you are zoomed out. This could cause issues for archipelagos though as it won't show any labels until one of the islands is a few pixels big. Seems to speed up the rendering too. ` if (cellSize < precision * 10) { |
Here's an example that I'm using to test. Before centroid change: https://city.tidalforce.org/nycschoolallocation_usm.html With the centroid change (I cloned mapbox-gl-js and switched to the pull request branch and built) It is definitely an improvement. A few questions:
no longer seems to work. as it did in
|
@fedex1 I don't think this PR has any changes that impact the copyright maybe the others can comment on this 👍 for giving it a go and demonstrating feedback |
@blanchg Yes that is correct. Thanks for the reply! with debug lines on. https://city.tidalforce.org/nycschoolallocation_usm_centroid.html |
Rebased and squashed 👉 #3038 |
Could you please explain if it is possible now to show only one text label in polygon center even if it`s splited into several tiles? I would like to avoid situation like on the last image of @fedex1 where label "District 16" is repeated several times inside one district. |
@anna-geo currently not possible unfortunately, and a very hard issue to address. |
@anna-geo I suggest that you do that processing during tile generation time and produce point geometries where the label should go. |
For Issue #1231. This is a first pass that uses a slightly modified centroid function from turf-centroid.js
It works great for simple polygons, but it doesn't handle holes or complex polygons.
For holes, maybe the solution would be to look at using the formula for geometric decomposition
For complex polygons (e.g. river polygons ) a more complex straight polygon tree could be used but I haven't found a nicie clean fast solution for that yet.
This is also my first PR for mapbox-gl-js, so please let me know if I have missed something.