Open astrojams1 opened 6 years ago
Seems like what we'd really need here is a new style-spec property to control whether symbols are clipped or omitted when they cross the viewport boundary. cc @ChrisLoer @ansis
Yeah, the best solution we know of right now is pretty hacky, and it sounds like you've already figured it out -- it's some version of:
If it's any consolation, I know of at least one instance in which that hack is successfully used in production with millions of map views! 😅
We haven't considered a specific option for making the edge of the viewport trigger collisions before. If it turns out to be an important use-case, it would be pretty straightforward to implement technically. What's we've usually talked about doing is a more general-case "implement collision detection for arbitrary geometries" (as in https://github.com/mapbox/mapbox-gl-js/issues/4704#issuecomment-319192573).
BTW, just looking at your image, it seems like you'd actually only want labels to collide with the bottom edge of the map? That level of specificity seems to me like an argument that we should try to make any support for this as general-purpose as possible.
@ChrisLoer To clarify, the image was cropped. I personally want to prevent collision with any edge.
@ChrisLoer can you kindly point me to an example of implementing a "repeating symbol layer that goes along [a] line?" My google searching is coming up blank.
@astrojams1 Sorry, I don't know of any public examples, but the steps should be something like:
symbol-placement: line
, symbol-spacing: 5
(that's a guess for what will work well?).icon-image
that points to a 0 pixel or transparent icon in your style's sprite sheet.Thank you for that, @ChrisLoer. I tried implementing a quick proof-of-concept using your steps. Here is what I came up with:
var viewport = map.getBounds()
map.addSource('viewport-corners', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: [
[viewport._sw.lng, viewport._sw.lat],
[viewport._sw.lng, viewport._ne.lat],
[viewport._ne.lng, viewport._ne.lat],
[viewport._ne.lng, viewport._sw.lat],
[viewport._sw.lng, viewport._sw.lat]
]
}
}
})
map.addLayer({
id: 'viewport-line',
type: 'line',
source: 'viewport-corners',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': 'red',
'line-width': 10
}
})
map.addLayer({
id: 'viewport-line-symbols',
type: 'symbol',
source: 'viewport-corners',
layout: {
'icon-image': 'harbor_icon',
'icon-size': 1,
'symbol-placement': 'line',
'symbol-spacing': 5
}
})
The red line renders as expected, but I don't see the repeating symbol (see image below). FYI, I am using the light-v9
map style.
That looks about right to me. I don't know if "harbor_icon" is part of your style, but it's not part of light-v9. I tried adding your code to a map but used "bus-11" instead (this icon's not invisible, you'll have to add your own, one option is https://www.mapbox.com/mapbox-gl-js/example/add-image/).
Anyway, your code seems to work as expected with that change. You can use map.showCollisionBoxes = true
to see the mechanics of which things collide against other things (also, once you actually load an invisible icon, turning on the collision boxes will allow you to see where the invisible icon is actually being placed).
@ChrisLoer Got it working. Thank you so much for your help! My updated implementation and resulting image are below. I removed the red line because it was only for testing purposes.
var viewport = map.getBounds()
map.addSource('viewport-line', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: [
[viewport._sw.lng, viewport._sw.lat],
[viewport._sw.lng, viewport._ne.lat],
[viewport._ne.lng, viewport._ne.lat],
[viewport._ne.lng, viewport._sw.lat],
[viewport._sw.lng, viewport._sw.lat]
]
}
}
})
var width = 10
var data = new Uint8Array(width*width*4)
map.addImage('pixel',{width: width, height: width, data: data})
map.addLayer({
id: 'viewport-line-symbols',
type: 'symbol',
source: 'viewport-line',
layout: {
'icon-image': 'pixel',
'symbol-placement': 'line',
'symbol-spacing': 5
}
})
Before:
After:
Much better. Thanks again!
We at @nzzdev would greatly appreciate an official style-spec property for this usecase. We will also have to implement this workaround for now.
Is there an official way to do this at this point, or is adding the invisible box the best bet?
I'm currently combining the code in the previous comment with this to prevent the edge crashing after map interaction
map.on('moveend', function () {
if (map.getLayer('viewport-line-symbols')) map.removeLayer('viewport-line-symbols');
if (map.getSource("viewport-line")) map.removeSource('viewport-line')
if (map.hasImage("pixel")) map.removeImage('pixel')
...
@brianjacobs-natgeo at this time, we do not have an official method or option to enable this. The invisible border is still the best way to achieve this functionality. we don't have this on our roadmap right now so there's no timeline for a built-in way to handle this.
I also need this feature and will use this workaround. Thanks for keeping it on your radar.
I will ultimately use this workaround to deal with the issue of labels overlapping an external map legend. I made an issue for that too. https://github.com/mapbox/mapbox-gl-js/issues/9249
Hello!
I am trying to prevent map labels from getting placed such that they get cut off from the edge of the map for my project (IRLMap.com). In the screenshot below, I show examples of labels that are getting cut off marked with 🚫.
My first idea was to create a rectangular polygon at the perimeter of my map, but doing so did not appear to shift the underlying labels. My second idea comes from the insight that I notice my points DO shift underlying labels out of the way: add a series of invisible points at the perimeter of the map, but this feels hacky and strange.
What is the best approach for preventing labels from touching the edge of my map?