Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@
"darkDefault": "carto-dark",
"lightDefault": "carto-light",
"providers": {
"_comment_carto": "Carto is the default free-tier provider. Optional: specify 'domain' for Carto enterprise (e.g. 'mycompany' for 'https://{s}.mycompany.cartocdn.com').",
"_comment_carto": "Carto is the default provider. From August 2026 CARTO REQUIRES a Basemaps API key on raster tile requests: without a key here, every Carto layer (map, live map, node detail, geo-filter maps and the standalone geofilter-builder) still loads, but the tiles come back stamped 'API KEY REQUIRED -- carto.com/basemapsapikey'. Get a free key at carto.com/basemapsapikey and put it in 'key' (e.g. 'YOUR_CARTO_BASEMAP_KEY'); the field name matches upstream CoreScope so the same config works on both. WARNING: the key is sent to the browser. Follow any domain/referrer restrictions offered when CARTO issues the key, use a key dedicated to this deployment, monitor its usage, and rotate it if abused. Optional: 'domain' for Carto enterprise -- dot-separated enterprise SUBDOMAIN LABELS only, e.g. 'mycompany' for 'https://{s}.mycompany.cartocdn.com'. A value containing a scheme, '/', '?', '#' or whitespace, or with empty/over-long labels, is ignored and the public Carto base is used instead (it would otherwise move the tile host and send 'key' there). NOTE on 'enabled': false -- it removes Carto from the registered main-map / layer-picker styles only (enable another provider below to replace them). It does NOT stop all Carto use: the dedicated geo-filter maps (the Customize geo-filter tab and modal, and the standalone geofilter-builder page) call Carto directly and still need 'key' set to avoid the watermark.",
"carto": {
"enabled": true,
"domain": ""
"domain": "",
"key": ""
},
"_comment_osm": "OSM providers: 'mapbox', 'thunderforest', 'maptiler'. WARNING: Tokens are sent to the browser. Apply origin/referrer restrictions in your provider dashboard.",
"osm": {
Expand Down
8 changes: 6 additions & 2 deletions public/customize-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -1835,7 +1835,10 @@
var modalClosingLine = null;

_gfModalMap = L.map(mapDiv, { zoomControl: true });
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
// #7: resolve through the shared helper so the CARTO Basemaps API key
// (map.tiles.providers.carto.key) is applied here too. The modal is
// opened by user action, long after /api/config/client has landed.
L.tileLayer(window.MC_getCartoTileUrl('/light_all/{z}/{x}/{y}{r}.png'), {
attribution: '© OpenStreetMap © CartoDB', maxZoom: 19
}).addTo(_gfModalMap);

Expand Down Expand Up @@ -2044,7 +2047,8 @@
if (!mapEl || typeof L === 'undefined') return;

_gfMap = L.map(mapEl, { zoomControl: false, dragging: false, scrollWheelZoom: false, doubleClickZoom: false, touchZoom: false });
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
// #7: same shared-helper resolution as the geo-filter modal above.
L.tileLayer(window.MC_getCartoTileUrl('/light_all/{z}/{x}/{y}{r}.png'), {
attribution: '© OpenStreetMap © CartoDB', maxZoom: 19
}).addTo(_gfMap);

Expand Down
35 changes: 31 additions & 4 deletions public/geofilter-builder.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,40 @@ <h1>GeoFilter Builder</h1>
</div>

<script src="geofilter-draft.js"></script>
<!-- #7: this standalone page has no roles.js and no /api/config/client fetch
of its own, so it previously hardcoded a keyless CARTO URL and now gets
watermarked tiles. Pull in the shared provider module purely for
MC_getCartoTileUrl — it is self-contained (no roles.js dependency) and
its parse-time registry init is harmless here. -->
<script src="map-tile-providers.js"></script>
<script>
const map = L.map('map').setView([50.5, 4.4], 8);

L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap © CartoDB',
maxZoom: 19
}).addTo(map);
const CARTO_TILE_PATH = '/dark_all/{z}/{x}/{y}{r}.png';

// #7: this page has no roles.js, so it owns its own config load. The CARTO
// layer is created only AFTER /api/config/client settles — creating it first
// and swapping the URL afterwards would already have fired keyless requests
// and cached watermarked tiles. On failure we still add the layer (keyless),
// so an offline / API-less use of the builder keeps working instead of
// showing an empty map. Exactly one layer is created on either path.
function addCartoLayer() {
L.tileLayer(window.MC_getCartoTileUrl(CARTO_TILE_PATH), {
attribution: '© OpenStreetMap © CartoDB',
maxZoom: 19
}).addTo(map);
}

fetch('/api/config/client')
.then(function (r) { return r.json(); })
.then(function (cfg) {
if (cfg && cfg.map) {
window.MC_MAP_CFG = cfg.map;
if (typeof window.MC_initTileRegistry === 'function') window.MC_initTileRegistry(true);
}
})
.catch(function () { /* standalone/offline use: fall back to keyless tiles */ })
.then(addCartoLayer);

let points = [];
let markers = [];
Expand Down
41 changes: 30 additions & 11 deletions public/live.js
Original file line number Diff line number Diff line change
Expand Up @@ -1471,19 +1471,38 @@
}
}
const _liveInitTile = _liveResolveTile(isDark);
let tileLayer = L.tileLayer(_liveInitTile.url, { maxZoom: 19, attribution: _liveInitTile.attribution }).addTo(liveAutoLayerGroup);
if (isDark && _liveInitTile.refUrl) {
_liveDarkRefLayer = L.tileLayer(_liveInitTile.refUrl, { maxZoom: 19, attribution: _liveInitTile.attribution }).addTo(liveAutoLayerGroup);
// #7: same deferral as map.js — the layer is built now but only joins the
// map once /api/config/client has settled, so the very first CARTO
// request already carries carto.key instead of being watermarked and
// cached. Map creation, zoom/layer controls and panes are unaffected.
let tileLayer = L.tileLayer(_liveInitTile.url, { maxZoom: 19, attribution: _liveInitTile.attribution });
// One idempotent config-ready step for both tile-dependent pieces — see
// the equivalent block in map.js for the reasoning. The layer picker is
// built here rather than immediately because it materialises a real
// L.tileLayer per registry style at build time; building it before the
// key is known would offer the user selectable keyless CARTO layers.
let _liveTilesReady = false;
function _liveAttachTiles() {
if (_liveTilesReady) return; // exactly once per map init
_liveTilesReady = true;
const darkNow = document.documentElement.getAttribute('data-theme') === 'dark' ||
(document.documentElement.getAttribute('data-theme') !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches);
// _liveSyncDarkTiles re-resolves the URL against the loaded config and
// owns the Esri labels overlay + CSS filter, so it creates the ref
// layer rather than duplicating it here.
_liveSyncDarkTiles(darkNow);
if (!liveAutoLayerGroup.hasLayer(tileLayer)) tileLayer.addTo(liveAutoLayerGroup);
// Layer Control, passing 'topright' to put it on the right.
if (typeof window.MC_createLayerControl === 'function') {
window.MC_createLayerControl(map, liveAutoLayerGroup, 'topright');
}
}
if (typeof window.MC_applyTileFilter === 'function') window.MC_applyTileFilter();

// Add Zoom Control
L.control.zoom({ position: 'topright' }).addTo(map);
// Guarded like every other cross-file MC_* call here — see map.js.
if (typeof window.MC_whenTileConfigReady === 'function') window.MC_whenTileConfigReady(_liveAttachTiles);
else _liveAttachTiles();

// Add Layer Control, passing 'topright' to put it on the right
if (typeof window.MC_createLayerControl === 'function') {
window.MC_createLayerControl(map, liveAutoLayerGroup, 'topright');
}
// Add Zoom Control — no tile dependency, so it stays immediate.
L.control.zoom({ position: 'topright' }).addTo(map);

// Add custom Leaflet Control for Fullscreen
const LiveFullscreenControl = L.Control.extend({
Expand Down
Loading