Skip to content
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

Generate legend on demand #632

Merged
merged 73 commits into from Jan 3, 2023
Merged

Generate legend on demand #632

merged 73 commits into from Jan 3, 2023

Conversation

1ec5
Copy link
Collaborator

@1ec5 1ec5 commented Dec 22, 2022

This PR adds a legend (map key) to the Web application. Click the Legend button in the lower-left corner to reveal a popup illustrating each element of the map style with a description in plain (American) English. The legend’s contents are generated dynamically on demand based on the features visible in the current viewport.

United States
The legend for the default view shows a bit of everything.

Motivation

A legend can come from anywhere.

— Campy coming-of-age film


This project intentionally departs from established conventions among OSM-based maps in favor of established conventions among U.S. map publishers. While this hopefully makes the map more intuitive and approachable to Americans who grew up refolding print maps, the choices in line style and symbology can be bewildering to those who grew up refolding European maps, or those who have become accustomed to commercial online maps that split the difference between the two cartographic traditions in search of greater marketshare or platform-level versatility.

The need for a legend has grown as OSM Americana has become more well-known among the global OSM community. Most mappers are familiar with openstreetmap-carto from using it for both mapping and trip planning. Over the years, openstreetmap-carto has made many design decisions that reflect the European cartographic tradition and the unique constraints of being a default, well-rounded style for OSM globally.

Coming from that perspective to Americana, mappers may rightly wonder how to tell which road is classified higher than another, or why there appear to be restricted-access roads in unexpected places. Without firsthand experience on U.S. roads, someone from a country with a more basic route marking system would look at our menagerie of route shields and never be able to figure it out on their own.

This project reintroduces American cartographic practices to the OSM community, ensuring that the OSM database accommodates the technical needs of anyone who wishes to reproduce this approach in their own projects. The community is well aware of the need to avoid overreliance on a particular renderer’s approach, but it’s easy to develop bad habits when the results of those habits aren’t apparent. Just as importantly, the nomenclature associated with various tags also needs to be translated for a lay audience so that the community can connect tagging discussions to a realistic end user experience. A legend can be a powerful tool for reframing discourse around the question, “Which distinctions really matter?”

The Web application already integrates OpenMapSamples, but its output is more appropriate for proofing a change during the development process. Educating the end user about the style requires a UI that’s more compact and focused so that they can compare the illustrations to the actual map.

Architecture and implementation

As a form of user documentation, legends can be challenging to maintain. You need to carefully consider what to illustrate and how much detail to provide, and of course the illustrations need to visually match the map. In the past, various OSM-based renderers have attempted to somehow automate the creation of a legend: #332 (comment). However, these tools still require a nontrivial amount of curation, leading some legends to become outdated easily: openstreetmap/openstreetmap-website#1307.

The Americana style is still in its infancy and subject to continual change, so any legend needs to be as data-driven as possible to remain relevant and accurate. Therefore, the legend’s configuration is written in JavaScript and runs as part of building the style. The configuration is located alongside the layer definitions and directly references the various layers that constitute the style. If you change a layer and neglect to update the configuration, the application will most likely crash on launch so you won’t overlook the problem.

The configuration isn’t derived directly from the layer definitions, because there isn’t a straightforward one-to-one mapping between layers and the concepts that a user needs to know. For example, rendering each kind of railroad track requires two layers, one of which is common to all the kinds of railroad tracks. Many layers implement data-driven styling for performance and maintainability, so multiple legend entries may draw from the same set of layers. A few layers, such as one-way arrows, have been omitted from the legend, because the meaning should be eminently obvious to most users. Most bridge and tunnel variations have been omitted for brevity, except on a few features like subways and monorails that routinely traverse one of these structures.

For each feature type in the configuration, the legend picks out a representative feature that’s currently visible on the map and generates an illustration based on the associated layers’ evaluated paint and layout properties. GL JS also has the capability to pick out features from the vector tiles that have loaded but aren’t visible. However, it doesn’t evaluate the style properties for these features, so we wouldn’t be able to automatically derive an illustration. Moreover, showing all feature types from all zoom levels would result in a lengthy, chaotic legend with many contradictions because of how layers can vary by zoom level. The popup can take a second or two to appear because it synchronously queries the map for features once per entry. Initially, I combined all the entries into a single query, but there was too much crosstalk among entries that happen to share layers and it was challenging to filter the results effectively.

z0 z1 z2 z3
z0 z1 z2 z3
z4 z5 z6 z7
z4 z5 z6 z7
z8 z9 z10 z11
z8 z9 z10 z11
z12 z13 z14 z15
z12 z13 z14 z16

Most illustrations are implemented as styled HTML elements. However, line features are implemented as small embedded SVG documents because the CSS profile for HTML lacks a straightforward way to customize dashed lines. So far, I’ve only implemented the paint and layout properties that this style is using. If a future PR starts using another property, it’ll need to be implemented as part of the legend control.

In most sections, the legend descriptions are hard-coded as part of the application. Every style makes different distinctions requiring different descriptions, so we can’t source the descriptions from, say, id-tagging-schema or the OSM Wiki’s data items. The hard-coded descriptions follow American English to a fault. I tried to capture the essence of how a print map would describe a particular entry if a cartographer had arrived at a definition equivalent to the OSM tag’s actual definition by accident. The highway classifications posed a particular challenge. Print maps are all over the place, but I think the typical functional classification terminology is common enough and close enough to OSM tagging to work here.

The “Route markers” section’s descriptions are fetched dynamically from Wikidata. The first time the user opens the legend after loading the page or changing the preferred language, the legend queries Wikidata for all the items associated with OSM’s network=* tags and populates the section with these items’ labels in the user’s preferred language. The shields are grouped by country, with the country names coming from the browser.

QEW
The “Route markers” section includes any visible variants of a standard shield.

LECT
Any network value outside of a country namespace, such as GLCT for the Great Lakes Circle Tour (#569), goes into an “International” section.

MX:MX
Country subsections and route network descriptions are translated into the user’s preferred language to the extent possible. If no description is available, the legend falls back to the raw network value.

The legend is contained inside a standard GL JS popup. Normally a popup is tied to a map marker, but in this case, the Legend button’s location on screen is converted to a geographic location and the popup is anchored to that location. To prevent the popup from becoming visually detached from the button, changing the map camera automatically closes the popup. If we want a more persistent popup, we’d need to implement our own popup UI and style it consistently with the map’s other controls.

Alternatives considered

Many GL JS–based maps integrate the mapbox-gl-inspect or maplibre-gl-inspect plugin, which shows an individual feature’s raw properties on hover. This plugin is very useful for style development but not as meaningful to end users, who wouldn’t know what to make of raw OpenMapTiles keys and values. (They don’t straightforwardly align with OSM tags either.) Moreover, describing a particular feature doesn’t necessarily help the user figure out the distinctions that the style is making between different features. A legend gives a better lay of the land, directing the user’s attention to the elements we consider most important to the style.

GL JS is capable of taking a screenshot of the feature itself, analogous to how mapnik-legendary works. However, this results in legend entries that are too large for an in-map popup and not as easy to comprehend at a glance. We don’t really need to reproduce the representative feature’s geometry, only the styling.

This project has a nice writeup on the OSM Wiki (if I may say so myself). We could double down on documenting the style on the wiki, putting the project out there where mappers cannot but consider it in tagging discussions. We could even maintain our own wiki to explain OSM data from the perspective of this style. However, I don’t think anything so voluminous would satisfy the basic user requirement of a visual guide to the map in front of them.

Future improvements

In the future, we could generalize the legend’s support code a bit and extract it out as a plugin for Mapbox/MapLibre GL JS, as with OpenMapSamples. However, I think that should be a later step once the code stabilizes and we see how it holds up to new additions to the style.

We should make legend descriptions localizable, just like in any other user-facing application. I prototyped a rudimentary solution for localization in #215 (comment), but this PR introduces enough new UI strings that we’d want to shunt the translation table into a separate JSON file.

Once enough network=* tags are tagged in Wikidata to bog down the SPARQL query, we should move the query to a build step and check in the results. Running the query on demand gives mappers instant gratification as they add labels to Wikidata, but it’ll be increasingly wasteful for every user to run this query once per session.

This legend unlocks some possibilities for interactivity, such as highlighting features on the map when you hover over a legend entry. Perhaps a checkbox beside each legend entry could toggle the relevant layers on and off: #25. #76 tracks making an individual map feature clickable to reveal more information about the feature, though that would generally require more information in the vector tiles.


Fixes #75 and fixes #332 to the extent that we have any POIs to define so far.

Moved labels to the left of icons. Capped label width.

Purge and refresh Wikidata query for network metadata whenever the language changes.
Generalized the symbol layer legending to display a swatch for fill layers.
Flattened the label into the label table cell to keep it from overflowing.
Ferries are not rivers.
Pushed a layer list into each entry. This requires querying the map separately for each entry but avoids crosstalk. Replaced bespoke property matching with expression-based filters.
The entry wasn’t getting cloned anyways.
Added inline comments throughout the less straightforward legend methods. Factored out shield image name parsing code and added tests of it.
@1ec5
Copy link
Collaborator Author

1ec5 commented Dec 27, 2022

I'm just thinking we want to head off the kind of thing that I did, which was to immediately edit wikidata wrongly to add missing network information.

Once we have some documentation on the project wiki, then we can encourage folks to create new Wikidata items about networks or assign existing items to network tags. In the meantime, it’s safe enough to add translations to Wikidata items about that have already been tagged with network tags. This is already largely done for English, but speakers of other languages can still benefit from translated labels. They’ll see an English fallback until their language has its own label on Wikidata for a given network.

@1ec5
Copy link
Collaborator Author

1ec5 commented Dec 28, 2022

I’ve restored the link from each network to the respective Wikidata item (but with a more subtle color now). If Wikidata has a label in a fallback language but not your most preferred language, a little language indicator appears off to the side, just like on Wikidata. The link makes it easier to get to the form for translating the network into your most preferred language.

fallbacks

@1ec5
Copy link
Collaborator Author

1ec5 commented Jan 3, 2023

I'm just thinking we want to head off the kind of thing that I did, which was to immediately edit wikidata wrongly to add missing network information.

The “Route markers” section gives a lot of exposure to Wikidata’s items about route networks (what it calls “highway systems”). This is good in that it’ll encourage translations that we probably wouldn’t otherwise get for a niche project. However, editing Wikidata directly requires some care, or at least some circumspection. The data model is more nuanced than OSM, but the existing data is often less nuanced due to imports from Wikipedia, ill-considered merges of items, and subsequent bots running rampant.

If a user sees a legend entry described by only the raw network value where a human-readable description should go, ideally they’d open an issue in this repository and we’d take care of setting up Wikidata for translation, by making sure there’s an appropriate item tagged with its corresponding network tag. However, it’s just as likely that they’d search Wikipedia for an article about the network, land on a list article, go to the linked Wikidata item, and change the label to say what the legend should say. That would be incorrect because the item is about a list of routes, not the network itself. This would be confusing because the rest of the item would continue to be modeled as if it’s a list.

To mitigate this confusion, I spent the last two weeks cleaning up hundreds of Wikidata items, unmerging items where necessary and adding statements about shields and number formats to clarify what each item is supposed to represent. I’ve taken care of most of the national networks and many of the regional route networks supported by this project, as well as some that are not yet supported but obviously established in OSM. There are still a lot of supported networks lacking Wikidata items at the regional and local level, but we can fill those out over time. With these edits in place, I feel a lot more confident about the user experience for contributing translations.

Along the way, I discovered that a number of shields, particularly in Europe, were implemented based on assumptions that appear to be unreliable. I’ve opened a separate issue about removing or reviewing each of these shields.

@1ec5 1ec5 merged commit 820a1fe into main Jan 3, 2023
@1ec5 1ec5 deleted the 1ec5-legends-of-the-hidden-layer-75 branch January 3, 2023 10:35
@ZeLonewolf
Copy link
Owner

Is this a regression or a TO-DO?

image

@1ec5
Copy link
Collaborator Author

1ec5 commented Jan 3, 2023

The labels look fine on my end:

English Channel

Please open a new issue, especially if you see anything amiss in the console.

@ZeLonewolf
Copy link
Owner

Looks like it just takes a bit to AJAX in the label sometimes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create key/legend for POIs Create key/legend for Highway Shields
4 participants