Skip to content

Conversation

@remogeissbuehler
Copy link
Contributor

Overview

This PR introduces a heuristic fix to choose the "correct" source layer for vector maps. In particular, when the style.json has multiple sources, such as:

  "sources": {
    "attribution_only": {
      "attribution": "<a href=\"https://example.com/\" target=\"_blank\">&copy; Example</a> ",
      "type": "vector"
    },
    "world": {
      "url": "https://example.com/tiles.json",
      "type": "vector"
    }
  },

this currently fails, as the logic to pick the source to display for a VectorTileLayer is "take the first source with type == 'vector'":

https://github.com/openlayers/ol-mapbox-style/blob/7b69c36a9792581b5d479c24ad9a2789a91593c3/src/apply.js#L253C1-L255C14

So in this case, we pick the one that does not in fact have any tile reference (.url or .tiles), which in turn leads to an exception in utils.js:
Screenshot 2025-03-03 at 01 29 03

Motivation

This exact bug happens for the Maptiler "basic-v2" map. I included it as a new example, which is broken in the current version but works with this fix.

Implementation

To tackle this problem, I added two main fixes:

Check for tiles and url in utils (getTileJson)

getTileJson previously assumed that either .url or .tiles are present. Sadly, this assumption doesn't always hold, so I added an extra check and raise an Error when neither are present for a more meaningful message in case this happens.

Heuristic choice of source

I replaced the above-mentioned logic to choose the source for VectorTileLayer for the following: "choose the first source of correct type that has at least one of the required attributes":
https://github.com/remogeissbuehler/ol-mapbox-style/blob/dc21b6e2783451fa5f3190c38b95a60053f4a6b2/src/apply.js#L259C1-L269C14

Note: I don't think this fully fixes the issue, as there could be multiple relevant sources and we still only pick one (at least a relevant one now). If I understand this spec correctly, the sources on their own don't say much about their display and the layers should instead be used as they refer to the different sources. Implementing this seems like a much bigger change to me though...

@ahocevar
Copy link
Member

ahocevar commented Mar 3, 2025

I'm currently reviewing this.

Quick note: strictly speaking, there is nothing broken, because your new example would work with

applyStyle(layer, styleUrl, {source: 'maptiler_planet'});

According to the docs, the first source is only used if the source option is not specified. applyStyle() applies a subset of a Mapbox Style to a vector or vector tile layer. If you want to get the entire Mapbox Style rendered, use apply() on a map or layer group.

The real problem here is that MapLibre did not add the clarifications "Required if url is not provided" for the tiles options of a vector source, and "Required if tiles is not provided" for the url option. Both of these clarifications are part of the Mapbox Style spec.

So our actual chore here is to ignore vector sources that neither have url nor tiles.

src/util.js Outdated
tileLoadFunction,
});
} else {
promise = Promise.reject(new Error('source has no tiles'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
promise = Promise.reject(new Error('source has no tiles'));
promise = Promise.reject(new Error('source has no `tiles` nor `url`'));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking: should we add a hint like "consider choosing a different source" or something for the next person that misses it like I did? 👀

Copy link
Member

@ahocevar ahocevar Mar 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You won't get here any more in this case, so the error message here is fine. I suggested a new one in the correct place.

src/apply.js Outdated
Comment on lines 251 to 268
const type = layer instanceof VectorTileLayer ? 'vector' : 'geojson';
const desiredType =
layer instanceof VectorTileLayer ? 'vector' : 'geojson';
const requiredAttrs = {
// for the respective source tiles, we expect any of those attributes to be present
'vector': ['url', 'tiles'],
'geojson': ['data'],
};
if (!sourceOrLayers) {
sourceId = Object.keys(glStyle.sources).find(function (key) {
return glStyle.sources[key].type === type;
// NOTE: this is mostly a heuristic way of picking the source, the TODO comment above still applies.
const sourceType = glStyle.sources[key].type;
const typeMatch = sourceType === desiredType;
const hasRequiredAttrs = requiredAttrs[sourceType].some(
function (attr) {
return Object.keys(glStyle.sources[key]).includes(attr);
},
);
return typeMatch && hasRequiredAttrs;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this involved change, it would be smarter to detect the source by the layers that use it, i.e.

          sourceId = glStyle.layers.find(function (layer) {
            return layer.source && glStyle.sources[layer.source].type === type;
          }).source;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this is more elegant, thanks!

@remogeissbuehler remogeissbuehler force-pushed the fix-vector-sources-lookup branch from 6b1dd1d to 707f9bb Compare March 8, 2025 19:00
@remogeissbuehler
Copy link
Contributor Author

Thanks a lot for the super quick review :) Sorry, I was quite busy this week.

I implemented your suggestions and also updated the test cases and documentation string to reflect them. Thanks also for pointing out that the source can be configured, I had missed that 🤦🏼

src/util.js Outdated
tileLoadFunction,
});
} else {
promise = Promise.reject(new Error('source has no tiles'));
Copy link
Member

@ahocevar ahocevar Mar 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You won't get here any more in this case, so the error message here is fine. I suggested a new one in the correct place.

Copy link
Member

@ahocevar ahocevar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahocevar ahocevar merged commit d4617d9 into openlayers:main Mar 10, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants