// useMapAttribution.js — the data-credit line every map MUST carry. // // import { addMapAttribution } from '@map/composables/useMapAttribution' // addMapAttribution(map, { boundaries: true }) // // ═══ WHY THIS EXISTS — IT IS A LICENCE OBLIGATION, NOT A NICETY ═════════════ // Every map in this repo constructs Mapbox with `attributionControl: false` // (useMapInstance.js and all three research profiles), and NOTHING replaced it. So // the live maps were rendering NO attribution at all — not for geoBoundaries, and // not for Mapbox/OpenStreetMap either, whose terms also require it. // // geoBoundaries' own request, verbatim: "we ask that you put the name // 'geoBoundaries' with a link back to this website, www.geoboundaries.org, // somewhere prominent on the page that uses the boundaries." // // gbOpen is ALSO a MIXED-LICENCE collection — it passes through each country's // upstream licence, verified live against their API: // India ADM1 CC BY 2.5 IN (source: github.com/datameet/maps) // Sudan ADM0 CC BY 3.0 IGO (source: HDX) // Brazil ADM0 CC BY 3.0 IGO (source: HDX) // USA ADM0 Public Domain (US Census) // China ADM0 Public Domain (Wikimedia Commons) // Several therefore carry a genuine per-country attribution requirement. A repo // comment previously called this "Apache-2.0-ish", which is wrong and would be the // wrong basis for a publication decision. // // SELF-HOSTED, NO NETWORK. This is plain DOM + inline style: it adds no request, so // it cannot leak a viewer's IP the way a third-party badge would. That matters here // — see the open OPEN_DEFECT_mapbox_cdn finding. const LINKS = { boundaries: { label: 'geoBoundaries', href: 'https://www.geoboundaries.org', title: 'Country and administrative boundaries — geoBoundaries gbOpen (mixed upstream licences; see docs)', }, mapbox: { label: 'Mapbox', href: 'https://www.mapbox.com/about/maps/' }, osm: { label: '© OpenStreetMap', href: 'https://www.openstreetmap.org/copyright' }, }; /** * Add the bottom-right attribution bar to a Mapbox map. * * @param {Object} map a mapboxgl.Map * @param {Object} [opts] * @param {boolean} [opts.boundaries=true] credit geoBoundaries (set false only if * the map draws NO gb polygons at all) * @param {boolean} [opts.basemap=true] credit Mapbox + OpenStreetMap */ export function addMapAttribution(map, opts = {}) { if (!map || typeof map.addControl !== 'function') return null; const { boundaries = true, basemap = true } = opts; const el = document.createElement('div'); el.className = 'doxa-attribution mapboxgl-ctrl'; // Inline style: these maps live in SHADOW ROOTS, so a stylesheet in the host page // would not reach here. Kept muted and small so it reads as a credit line, not UI. el.style.cssText = [ 'font:11px/1.45 system-ui,-apple-system,Segoe UI,Roboto,sans-serif', 'background:rgba(255,255,255,.82)', 'color:#3a4048', 'padding:2px 6px', 'border-radius:3px 0 0 0', 'pointer-events:auto', 'max-width:min(92vw,420px)', ].join(';'); const parts = []; if (boundaries) parts.push(LINKS.boundaries); if (basemap) { parts.push(LINKS.mapbox); parts.push(LINKS.osm); } el.innerHTML = parts.map((p) => `${p.label}`).join(' · '); const control = { onAdd: () => el, onRemove: () => { el.parentNode && el.parentNode.removeChild(el); }, getDefaultPosition: () => 'bottom-right', }; map.addControl(control, 'bottom-right'); return control; } /** The same credit as plain text — for posters, PDFs and print, which have no DOM. */ export const ATTRIBUTION_TEXT = 'Boundaries: geoBoundaries (geoboundaries.org) · © Mapbox © OpenStreetMap';