Compare commits

...

1 Commits

Author SHA1 Message Date
drew08t
5dbed75cbc Geomap: Enforce required attributions 2025-10-28 01:48:19 -07:00
8 changed files with 85 additions and 7 deletions

View File

@@ -473,6 +473,10 @@ A map from a collaborative free geographic world database.
- **Opacity** from 0 (transparent) to 1 (opaque)
- **Display tooltip** - allows you to toggle tooltips for the layer.
{{< admonition type="note" >}}
OpenStreetMap requires attribution by license. When you use this layer, attribution automatically displays in the map controls and cannot be hidden.
{{< /admonition >}}
[About Open Street Map](https://www.openstreetmap.org/about)
#### CARTO basemap layer
@@ -489,6 +493,10 @@ A CARTO layer is from CARTO Raster basemaps.
- **Opacity** from 0 (transparent) to 1 (opaque)
- **Display tooltip** - allows you to toggle tooltips for the layer.
{{< admonition type="note" >}}
CARTO requires attribution by license. When you use this layer, attribution automatically displays in the map controls and cannot be hidden.
{{< /admonition >}}
[About CARTO](https://carto.com/about-us/)
#### ArcGIS MapServer layer
@@ -654,6 +662,10 @@ Enables the mouse wheel to be used for zooming in or out.
Displays attribution for basemap layers.
{{< admonition type="note" >}}
Attribution is required by license for certain map layers, including OpenStreetMap and CARTO. When you use these layers, their attribution automatically displays and cannot be hidden. The toggle becomes **Show optional attribution** and controls whether attribution from other layers is also displayed.
{{< /admonition >}}
{{< figure src="/static/img/docs/geomap-panel/geomap-map-controls-attribution-9-1-0.png" max-width="400px" alt="Geomap panel attribution" >}}
#### Show scale

View File

@@ -67,6 +67,12 @@ export interface MapLayerRegistryItem<TConfig = MapLayerOptions> extends Registr
*/
hideOpacity?: boolean;
/**
* Indicates that this layer requires attribution to be shown by license
* When true, the attribution control will always be displayed regardless of user settings
*/
requiresAttribution?: boolean;
/**
* Function that configures transformation and returns a transformer
* @param options

View File

@@ -9,6 +9,7 @@ import Zoom from 'ol/control/Zoom';
import { Coordinate } from 'ol/coordinate';
import { isEmpty } from 'ol/extent';
import MouseWheelZoom from 'ol/interaction/MouseWheelZoom';
import TileLayer from 'ol/layer/Tile';
import { fromLonLat, transformExtent } from 'ol/proj';
import { Component, ReactNode } from 'react';
import * as React from 'react';
@@ -30,7 +31,7 @@ import { MeasureVectorLayer } from './components/MeasureVectorLayer';
import { GeomapHoverPayload } from './event';
import { getGlobalStyles } from './globalStyles';
import { defaultMarkersConfig } from './layers/data/markersLayer';
import { DEFAULT_BASEMAP_CONFIG } from './layers/registry';
import { DEFAULT_BASEMAP_CONFIG, geomapLayerRegistry } from './layers/registry';
import { ControlsOptions, Options, MapLayerState, MapViewConfig, TooltipMode } from './types';
import { getActions } from './utils/actions';
import { getLayersExtent } from './utils/getLayersExtent';
@@ -396,7 +397,45 @@ export class GeomapPanel extends Component<Props, State> {
this.mouseWheelZoom?.setActive(Boolean(options.mouseWheelZoom));
if (options.showAttribution) {
// Handle attribution visibility per layer based on required vs optional
let hasAnyAttribution = false;
for (const layerState of this.layers) {
const layerType = layerState.options.type;
const layerRegistryItem = layerType ? geomapLayerRegistry.getIfExists(layerType) : null;
const requiresAttribution = layerRegistryItem?.requiresAttribution === true;
// Check if this layer's source has attribution capability
const layer = layerState.layer;
if (layer instanceof TileLayer) {
const source = layer.getSource();
if (source && typeof source.getAttributions === 'function') {
const currentAttributions = source.getAttributions();
// Store original attribution if not already stored
if (!layerState.originalAttribution && currentAttributions) {
layerState.originalAttribution = currentAttributions;
}
// Show attribution if it's required OR if user has enabled optional attributions
if (requiresAttribution || options.showAttribution) {
// Restore original attribution if we had cleared it
if (layerState.originalAttribution && typeof source.setAttributions === 'function') {
source.setAttributions(layerState.originalAttribution);
}
hasAnyAttribution = true;
} else {
// Hide optional attribution by clearing it
if (typeof source.setAttributions === 'function') {
source.setAttributions(null);
}
}
}
}
}
// Add attribution control if there are any attributions to show
if (hasAnyAttribution) {
this.map.addControl(new Attribution({ collapsed: true, collapsible: true }));
}

View File

@@ -27,6 +27,7 @@ export const carto: MapLayerRegistryItem<CartoConfig> = {
name: 'CARTO basemap',
description: 'Add layer CARTO Raster basemaps',
isBaseMap: true,
requiresAttribution: true,
defaultOptions: defaultCartoConfig,
/**

View File

@@ -9,6 +9,7 @@ export const standard: MapLayerRegistryItem = {
name: 'Open Street Map',
description: 'Add map from a collaborative free geographic world database',
isBaseMap: true,
requiresAttribution: true,
/**
* Function that configures transformation and returns a transformer

View File

@@ -26,6 +26,8 @@ export const defaultBaseLayer: MapLayerRegistryItem = {
id: DEFAULT_BASEMAP_CONFIG.type,
name: 'Default base layer',
isBaseMap: true,
// Default uses CARTO which requires attribution
requiresAttribution: true,
create: (map: OpenLayersMap, options: MapLayerOptions, eventBus: EventBus, theme: GrafanaTheme2) => {
const serverLayerType = config?.geomapDefaultBaseLayerConfig?.type;

View File

@@ -7,6 +7,7 @@ import { GeomapPanel } from './GeomapPanel';
import { LayersEditor } from './editor/LayersEditor';
import { MapViewEditor } from './editor/MapViewEditor';
import { getLayerEditor } from './editor/layerEditor';
import { geomapLayerRegistry } from './layers/registry';
import { mapPanelChangedHandler, mapMigrationHandler } from './migrations';
import { defaultMapViewConfig, Options, TooltipMode, GeomapInstanceState } from './types';
@@ -105,6 +106,17 @@ export const plugin = new PanelPlugin<Options>(GeomapPanel)
// The controls section
category = [t('geomap.category-map-controls', 'Map controls')];
// Check if any layer requires attribution
const requiresAttribution = state?.layers?.some((layerState) => {
const layerType = layerState.options.type;
if (layerType) {
const layerRegistryItem = geomapLayerRegistry.getIfExists(layerType);
return layerRegistryItem?.requiresAttribution === true;
}
return false;
});
builder
.addBooleanSwitch({
category,
@@ -123,11 +135,15 @@ export const plugin = new PanelPlugin<Options>(GeomapPanel)
.addBooleanSwitch({
category,
path: 'controls.showAttribution',
name: t('geomap.name-show-attribution', 'Show attribution'),
description: t(
'geomap.description-show-attribution',
'Show the map source attribution info in the lower right'
),
name: requiresAttribution
? t('geomap.name-show-optional-attribution', 'Show optional attribution')
: t('geomap.name-show-attribution', 'Show attribution'),
description: requiresAttribution
? t(
'geomap.description-show-optional-attribution',
'Required attributions are always shown. Toggle this to also show attribution from other layers at your discretion.'
)
: t('geomap.description-show-attribution', 'Show the map source attribution info in the lower right'),
defaultValue: true,
})
.addBooleanSwitch({

View File

@@ -51,6 +51,7 @@ export interface MapLayerState<TConfig = unknown> extends LayerElement {
onChange: (cfg: MapLayerOptions<TConfig>) => void;
isBasemap?: boolean;
mouseEvents: Subject<FeatureLike | undefined>;
originalAttribution?: any; // Store original attribution to restore when showing optional attributions
}
export {