doxa-maps/library/composables/useMapAttribution.js
doxa-maps d5ba21fa76 doxa-maps — portable bundler for embeddable JAMstack maps
Standalone repo that builds embeddable JAMstack map bundles (web components),
composes them into a lazy-loaded SPA via layouts/, and optionally wraps a
Capacitor Android/iOS app — one build, choose your output/CDN. Diátaxis docs.

History reset to a single clean commit; no PII, IPs, or internal terms.
2026-09-10 01:53:07 +00:00

84 lines
3.9 KiB
JavaScript

// 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) =>
`<a href="${p.href}" target="_blank" rel="noopener noreferrer"`
+ (p.title ? ` title="${p.title}"` : '')
+ ` style="color:#2a5d8f;text-decoration:none">${p.label}</a>`).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';