// ==UserScript== // @author DanielOnDiordna // @name IITC plugin: Stadiamaps.com map layers // @category Map Tiles // @version 1.0.0.20231123.150400 // @updateURL https://softspot.nl/ingress/plugins/iitc-plugin-stadiamaps.meta.js // @downloadURL https://softspot.nl/ingress/plugins/iitc-plugin-stadiamaps.user.js // @description [danielondiordna-1.0.0.20231123.150400] Add the map layers and overlays from stadiamaps.com (Stadia, Stamen, Alidade). A personal API Key is required. Setup instructions are available in a dialog. // @id iitc-plugin-stadiamaps@danielondiordna // @namespace https://softspot.nl/ingress/ // @match https://intel.ingress.com/* // @namespace https://softspot.nl/ingress/ // @match https://intel-x.ingress.com/* // @grant none // ==/UserScript== function wrapper(plugin_info) { // ensure plugin framework is there, even if iitc is not yet loaded if(typeof window.plugin !== 'function') window.plugin = function() {}; // use own namespace for plugin window.plugin.stadiamaps = function() {}; var self = window.plugin.stadiamaps; self.id = 'stadiamaps'; self.title = 'Stadia/Stamen maps'; self.version = '1.0.0.20231123.150400'; self.author = 'DanielOnDiordna'; self.changelog = ` Changelog: version 1.0.0.20231123.150400 - first release - used the basemap-stamen plugin as a base - added all Free maps and overlays and the required attributions from https://docs.stadiamaps.com/themes/ - added a dialog with instructions to edit an API key `; self.namespace = 'window.plugin.' + self.id + '.'; self.pluginname = 'plugin-' + self.id; self.localstoragesettings = self.pluginname + '-apikey'; self.settings = {}; self.settings.api_key = ''; // use own namespace for plugin let mapStadia = {}; function storeApiKey() { localStorage[self.localstoragesettings] = self.settings.api_key; } function restoreApiKey() { self.settings.api_key = localStorage[self.localstoragesettings]; } let attributions = { stadiamaps: { // Legally Required Attribution https://docs.stadiamaps.com/attribution/ name: 'Stadia Maps', url: 'https://stadiamaps.com/' }, stamendesign: { // Maps utilizing styles from Stamen Design must also include appropriate attribution name: 'Stamen Design', url: 'https://www.stamen.com/' }, openmaptiles: { // The Stamen Watercolor style does not require OpenMapTiles attribution. name: 'OpenMapTiles', url: 'https://openmaptiles.org/' }, openstreetmap: { name: 'OpenStreetMap contributors', url: 'https://www.openstreetmap.org/about/' } }; function createAttribution(names) { let attributionlist = []; for (let attribution of names) { if (attribution in attributions) { attribution = attributions[attribution]; attributionlist.push(`© ${attribution.name}`); } } return attributionlist.join(' '); } function updateLayersWithApiKey() { // replace api key in all layer urls if (!window.layerChooser?._layers) return; // layers not defined (yet) for (let layer of window.layerChooser._layers) { if (layer.name.match(/^Stadia|Stamen|Alidade|OSM/) && layer.layer._url?.match(/api_key/)) { let newurl = layer.layer._url.replace(/\?api_key=.*$/,'?api_key=' + self.settings.api_key); layer.layer.setUrl(newurl,false); if (!self.settings.api_key) { // disable layers if no api_key is set if (layer.overlay) { window.map.removeLayer(layer.layer); } else { window.layerChooser.showLayer(0); } } } } } function disableLayerWithoutValidApiKey(layer) { if (self.settings.api_key) return; if (layer.name.match(/^Stadia|Stamen|Alidade|OSM/) && layer.layer._url?.match(/api_key/)) { if (layer.overlay) { let index = window.layerChooser._layers.findIndex((el)=>{return el.name == layer.name}); setTimeout(function() { window.map.removeLayer(window.layerChooser._layers[index].layer); },0); // placed in a timeout to be able to update the checkbox menu(`Your selected overlay layer "${layer.name}" has been disabled. An API Key is required.`); } else { let index = window.layerChooser._layers.findIndex((el)=>{return el.name == localStorage['iitc-base-map']}) || 0; // return to previous base layer, or select base layer 0 setTimeout(function() { window.layerChooser.showLayer(index); },0);// placed in a timeout to be able to update the checkbox menu(`Your selected map "${layer.name}" has been disabled. An API Key is required.`); } } } mapStadia.setup = function () { let baseUrl = 'https://tiles.stadiamaps.com/tiles/{style}/{z}/{x}/{y}.{type}?api_key={api_key}'; let L_StadiaTileLayer = window.L.TileLayer.extend({ options: { type: 'png', minZoom: 0, maxZoom: 21, maxNativeZoom: 20, attribution: createAttribution(['stadiamaps','openmaptiles','openstreetmap']) }, initialize: function (name, options) { // options.layer = name.replace(' ','_').toLowerCase(); window.L.TileLayer.prototype.initialize.call(this, baseUrl.replace('{api_key}',self.settings.api_key).replace('{style}',options.style), options); } }); function addLayer(name,style,options) { if (!self.settings.api_key) { if (localStorage['iitc-base-map'] == name) { // revert to the base map delete(localStorage['iitc-base-map']); } } options = options || {}; options.style = style; window.layerChooser.addBaseLayer(new L_StadiaTileLayer(name,options),name); } function addOverlay(name,style,options) { if (!self.settings.api_key) { // if no api_key is set, disable the stored display status if (window.isLayerGroupDisplayed(name,false)) { window.updateDisplayedLayerGroup(name,false); } } options = options || {}; options.style = style; window.addLayerGroup(name,new L_StadiaTileLayer(name,options),false); } // https://docs.stadiamaps.com/map-styles/stamen-toner/ // Maps utilizing styles from Stamen Design must also include appropriate attribution addLayer('Stamen Toner','stamen_toner',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); addLayer('Stamen Toner Lite','stamen_toner_lite',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); addLayer('Stamen Toner Background','stamen_toner_background',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); // Includes only background layers (water, landcover, etc.) and lines (roads, borders, etc.) // https://docs.stadiamaps.com/map-styles/stamen-terrain/ addLayer('Stamen Terrain','stamen_terrain',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); addLayer('Stamen Terrain Background','stamen_terrain_background',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); // Includes only background layers (water, landcover, etc.) // https://docs.stadiamaps.com/map-styles/outdoors/ addLayer('Stadia Outdoors','outdoors'); // https://docs.stadiamaps.com/map-styles/stamen-watercolor/ addLayer('Stamen Watercolor','stamen_watercolor',{ maxNativeZoom: 16, type: 'jpg', attribution: createAttribution(['stadiamaps','openstreetmap'])}); // The Stamen Watercolor style does not require OpenMapTiles attribution. // Note that the Stamen Watercolor tileset is the original raster tileset from Stamen Design, // and it is missing the same tiles as the original. This is particularly noticeable at higher zoom levels. // We recommend configuring your library (ex: Leaflet or OpenLayers) to not request tiles higher than zoom level 16 for best results. // https://docs.stadiamaps.com/map-styles/alidade-smooth/ addLayer('Alidade Smooth','alidade_smooth'); // https://docs.stadiamaps.com/map-styles/alidade-smooth-dark/ addLayer('Alidade Smooth Dark','alidade_smooth_dark'); // https://docs.stadiamaps.com/map-styles/alidade-satellite/ addLayer('OSM Bright','osm_bright'); // Raster Layer Groups - Separate overlays: // setup detail overlays last, so these overlayes can be drawn over other layers addOverlay('Stamen Toner Lines','stamen_toner_lines',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); // Includes only line layers (road, borders, etc.) addOverlay('Stamen Toner Labels','stamen_toner_labels',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); // Includes only label layers (places, road names, etc.) addOverlay('Stamen Terrain Lines','stamen_terrain_lines',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); // Includes only line layers (road, borders, etc.) addOverlay('Stamen Terrain Labels','stamen_terrain_labels',{attribution: createAttribution(['stadiamaps','stamendesign','openmaptiles','openstreetmap'])}); // Includes only label layers (places, road names, etc.) }; function menu(message) { let container = document.createElement('div'); container.innerHTML = ` ${message || ''}

You need to set up a personal API Key before you can use Stadia, Stamen and Alidade maps and overlays.

Step 1:
Create an account at stadiamaps.com
Use your own e-mail address and choose your own password.

Step 2:
You will need to verify your email with a token. Check your e-mail.

Step 3:
Select a plan: "Stadia Free"
Step 4 will be skipped if you choose Free.

Step 5:
Setup Access & Authentication:
"Choose Mobile / Native App" and click on "Continue with an API Key"

Step 6:
Your API key is already displayed inside the Stadia-hosted URLs, but you could best follow these last steps.

Step 7:
Click "Log In" and Login to Stadia Maps
Click "Manage Properties" to see you API Key.
Select and copy the API Key.

Step 8:
Enter (paste) the API Key inside the plugin:

Your API Key:

${self.title} version ${self.version} by ${self.author}

`; container.querySelector(`[name=${self.id}api_key_button]`).addEventListener('click',function(e) { let new_api_key = prompt('Enter your personal Stadia maps API Key:',self.settings.api_key); if (new_api_key == null) return; // cancel input self.settings.api_key = new_api_key; storeApiKey(); updateLayersWithApiKey(); container.querySelector(`[name=${self.id}api_key_text]`).textContent = self.settings.api_key || '(not set)'; },false); container.querySelector(`[name=${self.id}api_key_text]`).textContent = self.settings.api_key || '(not set)'; window.dialog({ html: container, id: self.pluginname + '-dialog', title: self.title, width: 'auto' }).dialog('option', 'buttons', { 'Changelog': function() { alert(self.changelog); }, 'Close': function() { $(this).dialog('close'); }, }); } function setup() { restoreApiKey(); mapStadia.setup(); window.map.on('overlayadd baselayerchange', disableLayerWithoutValidApiKey); let toolboxlink = document.body.querySelector('#toolbox').appendChild(document.createElement('a')); toolboxlink.textContent = 'Stadia/Stamen API Key'; toolboxlink.addEventListener('click', function(e) { e.preventDefault(); menu(); }, false); console.log('IITC plugin loaded: ' + self.title + ' version ' + self.version); } // Added to support About IITC details and changelog: plugin_info.script.version = plugin_info.script.version.replace(/\.\d{8}\.\d{6}$/,''); plugin_info.buildName = 'softspot.nl'; plugin_info.dateTimeVersion = self.version.replace(/^.*(\d{4})(\d{2})(\d{2})\.(\d{6})/,'$1-$2-$3-$4'); plugin_info.pluginId = self.id; let changelog = [{version:'This is a softspot.nl plugin by ' + self.author,changes:[]},...self.changelog.replace(/^.*?version /s,'').split(/\nversion /).map((v)=>{v=v.split(/\n/).map((l)=>{return l.replace(/^- /,'')}).filter((l)=>{return l != "";}); return {version:v.shift(),changes:v}})]; setup.info = plugin_info; //add the script info data to the function as a property if (typeof changelog !== 'undefined') setup.info.changelog = changelog; if(!window.bootPlugins) window.bootPlugins = []; window.bootPlugins.push(setup); // if IITC has already booted, immediately run the 'setup' function if(window.iitcLoaded && typeof setup === 'function') setup(); } // wrapper end // inject code into site context var script = document.createElement('script'); var info = {}; if (typeof GM_info !== 'undefined' && GM_info && GM_info.script) info.script = { version: GM_info.script.version, name: GM_info.script.name, description: GM_info.script.description }; script.appendChild(document.createTextNode('('+ wrapper +')('+JSON.stringify(info)+');')); (document.body || document.head || document.documentElement).appendChild(script);