@Copilot commented on this pull request.

## Pull request overview

This PR migrates the minimap functionality from Leaflet to MapLibre GL, 
introducing a new modular architecture for map styles while maintaining 
backward compatibility with the existing layer system. The changes extract 
inline style definitions into reusable functions and adapt the minimap 
rendering to use MapLibre instead of Leaflet.

- Extracts map style definitions into a centralized `maplibre.styles.js` file 
with factory functions
- Refactors language setting logic into a shared `OSM.MapLibre.setMapLanguage` 
utility
- Replaces Leaflet-based minimaps with MapLibre GL minimaps in the layer 
switcher

### Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 
comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| app/assets/javascripts/maplibre.styles.js | Introduces centralized style 
definitions for all map layers (raster and vector), with factory functions that 
generate MapLibre style objects |
| app/assets/javascripts/maplibre.map.js | Updates require statements to 
include the new styles and i18n modules (previously had inline Mapnik style 
definition) |
| app/assets/javascripts/maplibre.i18n.js | Adds shared `setMapLanguage` 
function for setting map language based on user preferences, with support for 
asynchronous map loading |
| app/assets/javascripts/leaflet.maptiler.js | Refactors to use the new shared 
`setMapLanguage` utility instead of duplicated language-setting code |
| app/assets/javascripts/leaflet.map.js | Stores the layer type identifier in 
`layerOptions.leafletOsmId` for use in minimap style selection |
| app/assets/javascripts/leaflet.layers.js | Migrates minimap implementation 
from Leaflet to MapLibre GL, using the new style factory functions and 
MapLibre's map initialization API |
</details>






---

💡 <a 
href="/openstreetmap/openstreetmap-website/new/master/.github/instructions?filename=*.instructions.md"
 class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add 
Copilot custom instructions</a> for smarter, more guided reviews. <a 
href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot";
 class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how 
to get started</a>.

>  OSM.MapLibre.Locale = {
   "GeolocateControl.FindMyLocation": 
OSM.i18n.t("javascripts.map.locate.title"),
   "GeolocateControl.LocationNotAvailable": 
OSM.i18n.t("javascripts.map.locate.not_available"),
   "NavigationControl.ZoomIn": OSM.i18n.t("javascripts.map.zoom.in"),
   "NavigationControl.ZoomOut": OSM.i18n.t("javascripts.map.zoom.out"),
   "Popup.Close": OSM.i18n.t("javascripts.map.popup.close")
 };
+

The `OSM.MapLibre.setMapLanguage` function lacks documentation. Consider adding 
a JSDoc comment to explain its purpose, parameters, and the logic for matching 
preferred languages with supported languages. This is especially important 
since the function has recursive behavior (calling itself after the map loads) 
and complex language matching logic.
```suggestion

/**
 * Sets the map's label language based on the user's preferred languages and
 * the languages supported by the current map style.
 *
 * If the map style has not finished loading, this function waits for the
 * `"load"` event and then calls itself again once the style is available.
 *
 * The function retrieves the list of supported languages from the style
 * (`map.style.getLanguage()`), then iterates over `OSM.preferred_languages`
 * in order. Each preferred language is normalized by lowercasing and
 * replacing `-` with `_` before being compared to the supported languages.
 * The first exact match found is applied via `map.setLanguage`.
 *
 * @param {Object} map - The MapLibre map instance whose style and language
 *   will be updated. It is expected to expose `style.loaded()`,
 *   `style.getLanguage()`, `once(event, handler)` and `setLanguage(language)`.
 */
```

> @@ -0,0 +1,36 @@
+OSM.MapLibre.Styles.Mapnik = () => createRasterStyle(19, 
"https://tile.openstreetmap.org/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.CyclOSM = () => createRasterStyle(20, 
"https://{abc}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.CycleMap = (options) => createRasterStyle(21, 
`https://{abc}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=${options.apikey}`);
+OSM.MapLibre.Styles.TransportMap = (options) => createRasterStyle(21, 
`https://{abc}.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=${options.apikey}`);
+OSM.MapLibre.Styles.TransportDarkMap = (options) => createRasterStyle(21, 
`https://{abc}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png?apikey=${options.apikey}`);
+OSM.MapLibre.Styles.OPNVKarte = () => createRasterStyle(18, 
"https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.HOT = () => createRasterStyle(20, 
"https://tile-{abc}.openstreetmap.fr/hot/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.TracestrackTopo = (options) => createRasterStyle(19, 
`https://tile.tracestrack.com/topo__/{z}/{x}/{y}.webp?key=${options.apikey}`);
+OSM.MapLibre.Styles.GPS = (options) => createRasterStyle(21, 
"https://gps.tile.openstreetmap.org/lines/{z}/{x}/{y}.png";);

The `GPS` style function is defined but doesn't use the `options` parameter. 
Since this function accepts `options` as a parameter (unlike `Mapnik` which 
doesn't), either the parameter should be removed if not needed, or if it's 
intended for future use with API keys, this should be documented.
```suggestion
OSM.MapLibre.Styles.GPS = () => createRasterStyle(21, 
"https://gps.tile.openstreetmap.org/lines/{z}/{x}/{y}.png";);
```

> @@ -42,8 +52,14 @@ L.OSM.layers = function (options) {
           setView();
         }
 
-        function setView(options) {
-          miniMap.setView(map.getCenter(), Math.max(map.getZoom() - 2, 0), 
options);
+        function setView(animate = true) {
+          const center = map.getCenter();
+          const zoom = Math.max(Math.floor(map.getZoom() - 3), -1);

The zoom level is calculated as `Math.floor(map.getZoom() - 3)`, which can 
result in negative values (minimum -1). While MapLibre might handle negative 
zoom gracefully, the previous Leaflet implementation used 
`Math.max(map.getZoom() - 2, 0)` to ensure a minimum zoom of 0. The change from 
`-2` to `-3` and allowing negative zoom should be intentional, but the 
inconsistency in allowing zoom -1 might cause unexpected behavior.
```suggestion
          const zoom = Math.max(Math.floor(map.getZoom() - 3), 0);
```

> +        const style = OSM.MapLibre.Styles[styleId](layer.options);
+        const miniMap = new maplibregl.Map({
+          container,
+          style,
+          interactive: false,
+          attributionControl: false,
+          fadeDuration: 0
+        });
 

The minimap should handle errors when the style fails to load (e.g., missing 
API keys for layers that require them). Since the style is retrieved 
dynamically via `OSM.MapLibre.Styles[styleId](layer.options)`, if the styleId 
doesn't exist or the function throws an error, the minimap creation will fail 
without proper error handling. Consider adding a try-catch block or error event 
listener.
```suggestion

        // Ensure the style function exists before calling it
        const styleFn = OSM.MapLibre && OSM.MapLibre.Styles && 
OSM.MapLibre.Styles[styleId];
        if (typeof styleFn !== "function") {
          console.error("Failed to initialize minimap: unknown or invalid 
styleId", {
            styleId: styleId,
            layerOptions: layer.options
          });
          return;
        }

        let miniMap;
        try {
          const style = styleFn(layer.options);
          miniMap = new maplibregl.Map({
            container,
            style,
            interactive: false,
            attributionControl: false,
            fadeDuration: 0
          });
        } catch (e) {
          console.error("Failed to create minimap instance", e);
          return;
        }

        // Log runtime style/map errors instead of failing silently
        if (miniMap && typeof miniMap.on === "function") {
          miniMap.on("error", function (event) {
            console.error("Minimap encountered an error", event);
          });
        }
```

> +      if (OSM.isDarkMap() && L.OSM[leafletOsmDarkId]) {
+        layerOptions.leafletOsmId = leafletOsmDarkId;
+      } else if (L.OSM[leafletOsmId]) {
+        layerOptions.leafletOsmId = leafletOsmId;
+      } else {
+        layerOptions.leafletOsmId = "TileLayer";
+      }
+
+      const layerConstructor = L.OSM[layerOptions.leafletOsmId];

The variable `leafletOsmId` is stored in `layerOptions` but this creates 
confusion since the value is now used for MapLibre styles, not Leaflet layers. 
Consider renaming this to `maplibreStyleId` or `styleId` to better reflect its 
new purpose in the MapLibre migration.
```suggestion
      let leafletLayerId;
      if (OSM.isDarkMap() && L.OSM[leafletOsmDarkId]) {
        leafletLayerId = leafletOsmDarkId;
      } else if (L.OSM[leafletOsmId]) {
        leafletLayerId = leafletOsmId;
      } else {
        leafletLayerId = "TileLayer";
      }

      const layerConstructor = L.OSM[leafletLayerId];
```

> @@ -0,0 +1,36 @@
+OSM.MapLibre.Styles.Mapnik = () => createRasterStyle(19, 
"https://tile.openstreetmap.org/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.CyclOSM = () => createRasterStyle(20, 
"https://{abc}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.CycleMap = (options) => createRasterStyle(21, 
`https://{abc}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=${options.apikey}`);
+OSM.MapLibre.Styles.TransportMap = (options) => createRasterStyle(21, 
`https://{abc}.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=${options.apikey}`);
+OSM.MapLibre.Styles.TransportDarkMap = (options) => createRasterStyle(21, 
`https://{abc}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png?apikey=${options.apikey}`);
+OSM.MapLibre.Styles.OPNVKarte = () => createRasterStyle(18, 
"https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.HOT = () => createRasterStyle(20, 
"https://tile-{abc}.openstreetmap.fr/hot/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.TracestrackTopo = (options) => createRasterStyle(19, 
`https://tile.tracestrack.com/topo__/{z}/{x}/{y}.webp?key=${options.apikey}`);
+OSM.MapLibre.Styles.GPS = (options) => createRasterStyle(21, 
"https://gps.tile.openstreetmap.org/lines/{z}/{x}/{y}.png";);
+OSM.MapLibre.Styles.ShortbreadColorful = (options) => 
`https://vector.openstreetmap.org/styles/shortbread/${options.styleName}`;
+OSM.MapLibre.Styles.ShortbreadEclipse = (options) => 
`https://vector.openstreetmap.org/styles/shortbread/${options.styleName}`;
+OSM.MapLibre.Styles.OpenMapTiles = (options) => 
`https://api.maptiler.com/maps/openstreetmap/style.json?key=${options.apikey}`;
+

The `createRasterStyle` function lacks documentation. Consider adding a JSDoc 
comment explaining the purpose of the function, the parameters (maxzoom, 
tileUrl), and the return value (MapLibre style object). This would improve code 
maintainability, especially since this function is central to the raster style 
creation.
```suggestion

/**
 * Creates a MapLibre raster style configuration for a given raster tile source.
 *
 * If the tile URL template contains the `{abc}` placeholder, it will be 
expanded
 * into separate URLs for the `a`, `b`, and `c` subdomains.
 *
 * @param {number} maxzoom - Maximum zoom level supported by the raster tile 
source.
 * @param {string} tileUrl - URL template for raster tiles, optionally 
containing `{abc}`.
 * @returns {object} A MapLibre Style Specification object configured for a 
single raster source.
 */
```

-- 
Reply to this email directly or view it on GitHub:
https://github.com/openstreetmap/openstreetmap-website/pull/6683#pullrequestreview-3633170084
You are receiving this because you are subscribed to this thread.

Message ID: 
<openstreetmap/openstreetmap-website/pull/6683/review/[email protected]>
_______________________________________________
rails-dev mailing list
[email protected]
https://lists.openstreetmap.org/listinfo/rails-dev

Reply via email to