mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Geomap: Add dynamic initial view options (#54419)
* Geomap: Add dynamic initial view options * Add control options for dynamic initial view * Add handling for last only scope * Remove stale todos * Only reinitialize map view during data if fit * Add options for data fit In map init, remove dependency on layers input. Add a boolean to map view config to handle all layers, with default set to true. Also, add layer string to handle currently selected layer. Change some verbage. In map view editor, add controls for data extent options. Only display layer selection when all layers is not selected. Update layer extent function to handle all options permutations. When all layers is selected, return all data for extent calculation. When last only is selected, only return last data point in matching layer for extent calculation. Otherwise, return all data points in matching layer for extent calculation. * Change padding tooltip and hide for last only * Simplify getLayersExtent call * Add enums for data scope options * Update GeomapPanel for refactor merge * Move view init functions back into geomap class Re-apply data change handling and extent calculation handling. * Update padding tooltip verbage * Ensure geomap panel options layers are initialized * Clean up GeomapPanel file (betterer) * refactors / clean up * more cleanup Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
parent
5be04f5336
commit
6e85dfa25a
@ -4,7 +4,10 @@ import { Map as OpenLayersMap, MapBrowserEvent, View } from 'ol';
|
|||||||
import Attribution from 'ol/control/Attribution';
|
import Attribution from 'ol/control/Attribution';
|
||||||
import ScaleLine from 'ol/control/ScaleLine';
|
import ScaleLine from 'ol/control/ScaleLine';
|
||||||
import Zoom from 'ol/control/Zoom';
|
import Zoom from 'ol/control/Zoom';
|
||||||
|
import { Coordinate } from 'ol/coordinate';
|
||||||
|
import { isEmpty } from 'ol/extent';
|
||||||
import MouseWheelZoom from 'ol/interaction/MouseWheelZoom';
|
import MouseWheelZoom from 'ol/interaction/MouseWheelZoom';
|
||||||
|
import { fromLonLat } from 'ol/proj';
|
||||||
import React, { Component, ReactNode } from 'react';
|
import React, { Component, ReactNode } from 'react';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
@ -22,12 +25,13 @@ import { GeomapHoverPayload } from './event';
|
|||||||
import { getGlobalStyles } from './globalStyles';
|
import { getGlobalStyles } from './globalStyles';
|
||||||
import { defaultMarkersConfig } from './layers/data/markersLayer';
|
import { defaultMarkersConfig } from './layers/data/markersLayer';
|
||||||
import { DEFAULT_BASEMAP_CONFIG } from './layers/registry';
|
import { DEFAULT_BASEMAP_CONFIG } from './layers/registry';
|
||||||
import { ControlsOptions, GeomapPanelOptions, MapLayerState, TooltipMode } from './types';
|
import { ControlsOptions, GeomapPanelOptions, MapLayerState, MapViewConfig, TooltipMode } from './types';
|
||||||
import { getActions } from './utils/actions';
|
import { getActions } from './utils/actions';
|
||||||
|
import { getLayersExtent } from './utils/getLayersExtent';
|
||||||
import { applyLayerFilter, initLayer } from './utils/layers';
|
import { applyLayerFilter, initLayer } from './utils/layers';
|
||||||
import { pointerClickListener, pointerMoveListener, setTooltipListeners } from './utils/tootltip';
|
import { pointerClickListener, pointerMoveListener, setTooltipListeners } from './utils/tootltip';
|
||||||
import { updateMap, getNewOpenLayersMap, notifyPanelEditor } from './utils/utils';
|
import { updateMap, getNewOpenLayersMap, notifyPanelEditor } from './utils/utils';
|
||||||
import { initMapView, initViewExtent } from './utils/view';
|
import { centerPointRegistry, MapCenterID } from './view';
|
||||||
|
|
||||||
// Allows multiple panels to share the same view instance
|
// Allows multiple panels to share the same view instance
|
||||||
let sharedView: View | undefined = undefined;
|
let sharedView: View | undefined = undefined;
|
||||||
@ -143,10 +147,12 @@ export class GeomapPanel extends Component<Props, State> {
|
|||||||
optionsChanged(options: GeomapPanelOptions) {
|
optionsChanged(options: GeomapPanelOptions) {
|
||||||
const oldOptions = this.props.options;
|
const oldOptions = this.props.options;
|
||||||
if (options.view !== oldOptions.view) {
|
if (options.view !== oldOptions.view) {
|
||||||
const [updatedSharedView, view] = initMapView(options.view, sharedView, this.map!.getLayers());
|
const [updatedSharedView, view] = this.initMapView(options.view, sharedView);
|
||||||
sharedView = updatedSharedView;
|
sharedView = updatedSharedView;
|
||||||
// eslint-disable-next-line
|
|
||||||
this.map!.setView(view as View);
|
if (this.map && view) {
|
||||||
|
this.map.setView(view);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.controls !== oldOptions.controls) {
|
if (options.controls !== oldOptions.controls) {
|
||||||
@ -164,6 +170,16 @@ export class GeomapPanel extends Component<Props, State> {
|
|||||||
applyLayerFilter(state.handler, state.options, this.props.data);
|
applyLayerFilter(state.handler, state.options, this.props.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Because data changed, check map view and change if needed (data fit)
|
||||||
|
const v = centerPointRegistry.getIfExists(this.props.options.view.id);
|
||||||
|
if (v && v.id === MapCenterID.Fit) {
|
||||||
|
const [, view] = this.initMapView(this.props.options.view);
|
||||||
|
|
||||||
|
if (this.map && view) {
|
||||||
|
this.map.setView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initMapRef = async (div: HTMLDivElement) => {
|
initMapRef = async (div: HTMLDivElement) => {
|
||||||
@ -173,8 +189,7 @@ export class GeomapPanel extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!div) {
|
if (!div) {
|
||||||
// eslint-disable-next-line
|
this.map = undefined;
|
||||||
this.map = undefined as unknown as OpenLayersMap;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { options } = this.props;
|
const { options } = this.props;
|
||||||
@ -187,13 +202,15 @@ export class GeomapPanel extends Component<Props, State> {
|
|||||||
layers.push(await initLayer(this, map, options.basemap ?? DEFAULT_BASEMAP_CONFIG, true));
|
layers.push(await initLayer(this, map, options.basemap ?? DEFAULT_BASEMAP_CONFIG, true));
|
||||||
|
|
||||||
// Default layer values
|
// Default layer values
|
||||||
const layerOptions = options.layers ?? [defaultMarkersConfig];
|
if (!options.layers) {
|
||||||
|
options.layers = [defaultMarkersConfig];
|
||||||
|
}
|
||||||
|
|
||||||
for (const lyr of layerOptions) {
|
for (const lyr of options.layers) {
|
||||||
layers.push(await initLayer(this, map, lyr, false));
|
layers.push(await initLayer(this, map, lyr, false));
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error('error loading layers', ex); // eslint-disable-line no-console
|
console.error('error loading layers', ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const lyr of layers) {
|
for (const lyr of layers) {
|
||||||
@ -201,7 +218,7 @@ export class GeomapPanel extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
this.layers = layers;
|
this.layers = layers;
|
||||||
this.map = map; // redundant
|
this.map = map; // redundant
|
||||||
initViewExtent(map.getView(), options.view, map.getLayers());
|
this.initViewExtent(map.getView(), options.view);
|
||||||
|
|
||||||
this.mouseWheelZoom = new MouseWheelZoom();
|
this.mouseWheelZoom = new MouseWheelZoom();
|
||||||
this.map?.addInteraction(this.mouseWheelZoom);
|
this.map?.addInteraction(this.mouseWheelZoom);
|
||||||
@ -231,6 +248,70 @@ export class GeomapPanel extends Component<Props, State> {
|
|||||||
pointerMoveListener(evt, this);
|
pointerMoveListener(evt, this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
initMapView = (config: MapViewConfig, sharedView?: View | undefined): Array<View | undefined> => {
|
||||||
|
let view = new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1,
|
||||||
|
showFullExtent: true, // allows zooming so the full range is visible
|
||||||
|
});
|
||||||
|
|
||||||
|
// With shared views, all panels use the same view instance
|
||||||
|
if (config.shared) {
|
||||||
|
if (!sharedView) {
|
||||||
|
sharedView = view;
|
||||||
|
} else {
|
||||||
|
view = sharedView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.initViewExtent(view, config);
|
||||||
|
|
||||||
|
return [sharedView, view];
|
||||||
|
};
|
||||||
|
|
||||||
|
initViewExtent(view: View, config: MapViewConfig) {
|
||||||
|
const v = centerPointRegistry.getIfExists(config.id);
|
||||||
|
if (v) {
|
||||||
|
let coord: Coordinate | undefined = undefined;
|
||||||
|
if (v.lat == null) {
|
||||||
|
if (v.id === MapCenterID.Coordinates) {
|
||||||
|
coord = [config.lon ?? 0, config.lat ?? 0];
|
||||||
|
} else if (v.id === MapCenterID.Fit) {
|
||||||
|
const extent = getLayersExtent(this.layers, config.allLayers, config.lastOnly, config.layer);
|
||||||
|
if (!isEmpty(extent)) {
|
||||||
|
const padding = config.padding ?? 5;
|
||||||
|
const res = view.getResolutionForExtent(extent, this.map?.getSize());
|
||||||
|
const maxZoom = config.zoom ?? config.maxZoom;
|
||||||
|
view.fit(extent, {
|
||||||
|
maxZoom: maxZoom,
|
||||||
|
});
|
||||||
|
view.setResolution(res * (padding / 100 + 1));
|
||||||
|
const adjustedZoom = view.getZoom();
|
||||||
|
if (adjustedZoom && maxZoom && adjustedZoom > maxZoom) {
|
||||||
|
view.setZoom(maxZoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: view requires special handling
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
coord = [v.lon ?? 0, v.lat ?? 0];
|
||||||
|
}
|
||||||
|
if (coord) {
|
||||||
|
view.setCenter(fromLonLat(coord));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.maxZoom) {
|
||||||
|
view.setMaxZoom(config.maxZoom);
|
||||||
|
}
|
||||||
|
if (config.minZoom) {
|
||||||
|
view.setMaxZoom(config.minZoom);
|
||||||
|
}
|
||||||
|
if (config.zoom && v?.id !== MapCenterID.Fit) {
|
||||||
|
view.setZoom(config.zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initControls(options: ControlsOptions) {
|
initControls(options: ControlsOptions) {
|
||||||
if (!this.map) {
|
if (!this.map) {
|
||||||
return;
|
return;
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { InlineFieldRow, InlineField } from '@grafana/ui';
|
||||||
|
import { NumberInput } from 'app/core/components/OptionsUI/NumberInput';
|
||||||
|
|
||||||
|
import { MapViewConfig } from '../types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
labelWidth: number;
|
||||||
|
value: MapViewConfig;
|
||||||
|
onChange: (value?: MapViewConfig | undefined) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CoordinatesMapViewEditor = ({ labelWidth, value, onChange }: Props) => {
|
||||||
|
const onLatitudeChange = (latitude: number | undefined) => {
|
||||||
|
onChange({ ...value, lat: latitude });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLongitudeChange = (longitude: number | undefined) => {
|
||||||
|
onChange({ ...value, lon: longitude });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField label="Latitude" labelWidth={labelWidth} grow={true}>
|
||||||
|
<NumberInput value={value.lat} min={-90} max={90} step={0.001} onChange={onLatitudeChange} />
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField label="Longitude" labelWidth={labelWidth} grow={true}>
|
||||||
|
<NumberInput value={value.lon} min={-180} max={180} step={0.001} onChange={onLongitudeChange} />
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
117
public/app/plugins/panel/geomap/editor/FitMapViewEditor.tsx
Normal file
117
public/app/plugins/panel/geomap/editor/FitMapViewEditor.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue, StandardEditorContext } from '@grafana/data';
|
||||||
|
import { InlineFieldRow, InlineField, RadioButtonGroup, Select } from '@grafana/ui';
|
||||||
|
import { NumberInput } from 'app/core/components/OptionsUI/NumberInput';
|
||||||
|
|
||||||
|
import { GeomapInstanceState, GeomapPanelOptions, MapViewConfig } from '../types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
labelWidth: number;
|
||||||
|
value: MapViewConfig;
|
||||||
|
onChange: (value?: MapViewConfig | undefined) => void;
|
||||||
|
context: StandardEditorContext<GeomapPanelOptions, GeomapInstanceState>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Data scope options for 'Fit to data'
|
||||||
|
enum DataScopeValues {
|
||||||
|
all = 'all',
|
||||||
|
layer = 'layer',
|
||||||
|
last = 'last',
|
||||||
|
}
|
||||||
|
enum DataScopeLabels {
|
||||||
|
all = 'All layers',
|
||||||
|
layer = 'Layer',
|
||||||
|
last = 'Last value',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScopeOptions = Object.values(DataScopeValues);
|
||||||
|
|
||||||
|
const DataScopeOptions: Array<SelectableValue<DataScopeValues>> = ScopeOptions.map((dataScopeOption) => ({
|
||||||
|
label: DataScopeLabels[dataScopeOption],
|
||||||
|
value: dataScopeOption,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const FitMapViewEditor = ({ labelWidth, value, onChange, context }: Props) => {
|
||||||
|
const layers = useMemo(() => {
|
||||||
|
if (context.options?.layers) {
|
||||||
|
return context.options.layers.map((layer) => ({
|
||||||
|
label: layer.name,
|
||||||
|
value: layer.name,
|
||||||
|
description: undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [context.options?.layers]);
|
||||||
|
|
||||||
|
const onSelectLayer = useCallback(
|
||||||
|
(selection: SelectableValue<string>) => {
|
||||||
|
onChange({ ...value, layer: selection.value });
|
||||||
|
},
|
||||||
|
[value, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const allLayersEditorFragment = (
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField label="Layer" labelWidth={labelWidth} grow={true}>
|
||||||
|
<Select options={layers} onChange={onSelectLayer} placeholder={layers[0]?.label} />
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangePadding = (padding: number | undefined) => {
|
||||||
|
onChange({ ...value, padding: padding });
|
||||||
|
};
|
||||||
|
|
||||||
|
const lastOnlyEditorFragment = (
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField
|
||||||
|
label="Padding"
|
||||||
|
labelWidth={labelWidth}
|
||||||
|
grow={true}
|
||||||
|
tooltip="sets padding in relative percent beyond data extent"
|
||||||
|
>
|
||||||
|
<NumberInput value={value?.padding ?? 5} min={0} step={1} onChange={onChangePadding} />
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentDataScope = value.allLayers
|
||||||
|
? DataScopeValues.all
|
||||||
|
: !value.allLayers && value.lastOnly
|
||||||
|
? DataScopeValues.last
|
||||||
|
: DataScopeValues.layer;
|
||||||
|
|
||||||
|
const onDataScopeChange = (dataScope: DataScopeValues) => {
|
||||||
|
if (dataScope !== DataScopeValues.all && !value.layer) {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
allLayers: dataScope === String(DataScopeValues.all),
|
||||||
|
lastOnly: dataScope === String(DataScopeValues.last),
|
||||||
|
layer: layers[0].value,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
allLayers: dataScope === String(DataScopeValues.all),
|
||||||
|
lastOnly: dataScope === String(DataScopeValues.last),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField label="Data" labelWidth={labelWidth} grow={true}>
|
||||||
|
<RadioButtonGroup
|
||||||
|
value={currentDataScope}
|
||||||
|
options={DataScopeOptions}
|
||||||
|
onChange={onDataScopeChange}
|
||||||
|
></RadioButtonGroup>
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
{!value?.allLayers && allLayersEditorFragment}
|
||||||
|
{!value?.lastOnly && lastOnlyEditorFragment}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { toLonLat } from 'ol/proj';
|
import { toLonLat } from 'ol/proj';
|
||||||
import React, { FC, useMemo, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
|
|
||||||
import { StandardEditorProps, SelectableValue } from '@grafana/data';
|
import { StandardEditorProps, SelectableValue } from '@grafana/data';
|
||||||
import { Button, InlineField, InlineFieldRow, Select, VerticalGroup } from '@grafana/ui';
|
import { Button, InlineField, InlineFieldRow, Select, VerticalGroup } from '@grafana/ui';
|
||||||
@ -8,9 +8,14 @@ import { NumberInput } from 'app/core/components/OptionsUI/NumberInput';
|
|||||||
import { GeomapPanelOptions, MapViewConfig, GeomapInstanceState } from '../types';
|
import { GeomapPanelOptions, MapViewConfig, GeomapInstanceState } from '../types';
|
||||||
import { centerPointRegistry, MapCenterID } from '../view';
|
import { centerPointRegistry, MapCenterID } from '../view';
|
||||||
|
|
||||||
export const MapViewEditor: FC<
|
import { CoordinatesMapViewEditor } from './CoordinatesMapViewEditor';
|
||||||
StandardEditorProps<MapViewConfig, unknown, GeomapPanelOptions, GeomapInstanceState>
|
import { FitMapViewEditor } from './FitMapViewEditor';
|
||||||
> = ({ value, onChange, context }) => {
|
|
||||||
|
export const MapViewEditor = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
context,
|
||||||
|
}: StandardEditorProps<MapViewConfig, unknown, GeomapPanelOptions, GeomapInstanceState>) => {
|
||||||
const labelWidth = 10;
|
const labelWidth = 10;
|
||||||
|
|
||||||
const views = useMemo(() => {
|
const views = useMemo(() => {
|
||||||
@ -64,39 +69,15 @@ export const MapViewEditor: FC<
|
|||||||
<Select options={views.options} value={views.current} onChange={onSelectView} />
|
<Select options={views.options} value={views.current} onChange={onSelectView} />
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</InlineFieldRow>
|
</InlineFieldRow>
|
||||||
{value?.id === MapCenterID.Coordinates && (
|
{value.id === MapCenterID.Coordinates && (
|
||||||
<>
|
<CoordinatesMapViewEditor labelWidth={labelWidth} value={value} onChange={onChange} />
|
||||||
<InlineFieldRow>
|
)}
|
||||||
<InlineField label="Latitude" labelWidth={labelWidth} grow={true}>
|
{value.id === MapCenterID.Fit && (
|
||||||
<NumberInput
|
<FitMapViewEditor labelWidth={labelWidth} value={value} onChange={onChange} context={context} />
|
||||||
value={value.lat}
|
|
||||||
min={-90}
|
|
||||||
max={90}
|
|
||||||
step={0.001}
|
|
||||||
onChange={(v) => {
|
|
||||||
onChange({ ...value, lat: v });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
|
||||||
</InlineFieldRow>
|
|
||||||
<InlineFieldRow>
|
|
||||||
<InlineField label="Longitude" labelWidth={labelWidth} grow={true}>
|
|
||||||
<NumberInput
|
|
||||||
value={value.lon}
|
|
||||||
min={-180}
|
|
||||||
max={180}
|
|
||||||
step={0.001}
|
|
||||||
onChange={(v) => {
|
|
||||||
onChange({ ...value, lon: v });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
|
||||||
</InlineFieldRow>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<InlineFieldRow>
|
<InlineFieldRow>
|
||||||
<InlineField label="Zoom" labelWidth={labelWidth} grow={true}>
|
<InlineField label={value?.id === MapCenterID.Fit ? 'Max Zoom' : 'Zoom'} labelWidth={labelWidth} grow={true}>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={value?.zoom ?? 1}
|
value={value?.zoom ?? 1}
|
||||||
min={1}
|
min={1}
|
||||||
|
@ -48,6 +48,10 @@ export interface MapViewConfig {
|
|||||||
zoom?: number;
|
zoom?: number;
|
||||||
minZoom?: number;
|
minZoom?: number;
|
||||||
maxZoom?: number;
|
maxZoom?: number;
|
||||||
|
padding?: number;
|
||||||
|
allLayers?: boolean;
|
||||||
|
lastOnly?: boolean;
|
||||||
|
layer?: string;
|
||||||
shared?: boolean;
|
shared?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +60,7 @@ export const defaultView: MapViewConfig = {
|
|||||||
lat: 0,
|
lat: 0,
|
||||||
lon: 0,
|
lon: 0,
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
|
allLayers: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Support hide from legend/tooltip */
|
/** Support hide from legend/tooltip */
|
||||||
|
@ -1,18 +1,39 @@
|
|||||||
import { Collection } from 'ol';
|
|
||||||
import { createEmpty, extend, Extent } from 'ol/extent';
|
import { createEmpty, extend, Extent } from 'ol/extent';
|
||||||
import BaseLayer from 'ol/layer/Base';
|
|
||||||
import LayerGroup from 'ol/layer/Group';
|
import LayerGroup from 'ol/layer/Group';
|
||||||
import VectorLayer from 'ol/layer/Vector';
|
import VectorLayer from 'ol/layer/Vector';
|
||||||
|
|
||||||
export function getLayersExtent(layers: Collection<BaseLayer>): Extent {
|
import { MapLayerState } from '../types';
|
||||||
|
|
||||||
|
export function getLayersExtent(
|
||||||
|
layers: MapLayerState[] = [],
|
||||||
|
allLayers = false,
|
||||||
|
lastOnly = false,
|
||||||
|
layer: string | undefined
|
||||||
|
): Extent {
|
||||||
return layers
|
return layers
|
||||||
.getArray()
|
.filter((l) => l.layer instanceof VectorLayer || l.layer instanceof LayerGroup)
|
||||||
.filter((l) => l instanceof VectorLayer || l instanceof LayerGroup)
|
.flatMap((ll) => {
|
||||||
.flatMap((l) => {
|
const l = ll.layer;
|
||||||
if (l instanceof LayerGroup) {
|
if (l instanceof LayerGroup) {
|
||||||
return getLayerGroupExtent(l);
|
return getLayerGroupExtent(l);
|
||||||
} else if (l instanceof VectorLayer) {
|
} else if (l instanceof VectorLayer) {
|
||||||
return [l.getSource().getExtent()] ?? [];
|
if (allLayers) {
|
||||||
|
// Return everything from all layers
|
||||||
|
return [l.getSource().getExtent()] ?? [];
|
||||||
|
} else if (lastOnly && layer === ll.options.name) {
|
||||||
|
// Return last only for selected layer
|
||||||
|
const feat = l.getSource().getFeatures();
|
||||||
|
const featOfInterest = feat[feat.length - 1];
|
||||||
|
const geo = featOfInterest?.getGeometry();
|
||||||
|
if (geo) {
|
||||||
|
return [geo.getExtent()] ?? [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} else if (!lastOnly && layer === ll.options.name) {
|
||||||
|
// Return all points for selected layer
|
||||||
|
return [l.getSource().getExtent()] ?? [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ import { GeomapPanel } from '../GeomapPanel';
|
|||||||
import { defaultStyleConfig, StyleConfig, StyleConfigState, StyleDimensions } from '../style/types';
|
import { defaultStyleConfig, StyleConfig, StyleConfigState, StyleDimensions } from '../style/types';
|
||||||
import { GeomapPanelOptions, MapLayerState } from '../types';
|
import { GeomapPanelOptions, MapLayerState } from '../types';
|
||||||
|
|
||||||
import { initMapView } from './view';
|
|
||||||
|
|
||||||
export function getStyleDimension(
|
export function getStyleDimension(
|
||||||
frame: DataFrame | undefined,
|
frame: DataFrame | undefined,
|
||||||
style: StyleConfigState,
|
style: StyleConfigState,
|
||||||
@ -77,7 +75,7 @@ async function initGeojsonFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getNewOpenLayersMap = (panel: GeomapPanel, options: GeomapPanelOptions, div: HTMLDivElement) => {
|
export const getNewOpenLayersMap = (panel: GeomapPanel, options: GeomapPanelOptions, div: HTMLDivElement) => {
|
||||||
const [view] = initMapView(options.view, undefined, undefined);
|
const [view] = panel.initMapView(options.view, undefined);
|
||||||
return (panel.map = new OpenLayersMap({
|
return (panel.map = new OpenLayersMap({
|
||||||
view: view,
|
view: view,
|
||||||
pixelRatio: 1, // or zoom?
|
pixelRatio: 1, // or zoom?
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
import { View, Collection } from 'ol';
|
|
||||||
import { Coordinate } from 'ol/coordinate';
|
|
||||||
import { isEmpty } from 'ol/extent';
|
|
||||||
import BaseLayer from 'ol/layer/Base';
|
|
||||||
import { fromLonLat } from 'ol/proj';
|
|
||||||
|
|
||||||
import { MapViewConfig } from '../types';
|
|
||||||
import { centerPointRegistry, MapCenterID } from '../view';
|
|
||||||
|
|
||||||
import { getLayersExtent } from './getLayersExtent';
|
|
||||||
|
|
||||||
export const initViewExtent = (view: View, config: MapViewConfig, layers: Collection<BaseLayer>) => {
|
|
||||||
const v = centerPointRegistry.getIfExists(config.id);
|
|
||||||
if (v) {
|
|
||||||
let coord: Coordinate | undefined = undefined;
|
|
||||||
if (v.lat == null) {
|
|
||||||
if (v.id === MapCenterID.Coordinates) {
|
|
||||||
coord = [config.lon ?? 0, config.lat ?? 0];
|
|
||||||
} else if (v.id === MapCenterID.Fit) {
|
|
||||||
const extent = getLayersExtent(layers);
|
|
||||||
if (!isEmpty(extent)) {
|
|
||||||
view.fit(extent, {
|
|
||||||
padding: [30, 30, 30, 30],
|
|
||||||
maxZoom: config.zoom ?? config.maxZoom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: view requires special handling
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
coord = [v.lon ?? 0, v.lat ?? 0];
|
|
||||||
}
|
|
||||||
if (coord) {
|
|
||||||
view.setCenter(fromLonLat(coord));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.maxZoom) {
|
|
||||||
view.setMaxZoom(config.maxZoom);
|
|
||||||
}
|
|
||||||
if (config.minZoom) {
|
|
||||||
view.setMaxZoom(config.minZoom);
|
|
||||||
}
|
|
||||||
if (config.zoom && v?.id !== MapCenterID.Fit) {
|
|
||||||
view.setZoom(config.zoom);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initMapView = (
|
|
||||||
config: MapViewConfig,
|
|
||||||
sharedView?: View | undefined,
|
|
||||||
layers?: Collection<BaseLayer>
|
|
||||||
): Array<View | undefined> => {
|
|
||||||
let view = new View({
|
|
||||||
center: [0, 0],
|
|
||||||
zoom: 1,
|
|
||||||
showFullExtent: true, // allows zooming so the full range is visible
|
|
||||||
});
|
|
||||||
|
|
||||||
// With shared views, all panels use the same view instance
|
|
||||||
if (config.shared) {
|
|
||||||
if (!sharedView) {
|
|
||||||
sharedView = view;
|
|
||||||
} else {
|
|
||||||
view = sharedView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (layers) {
|
|
||||||
initViewExtent(view, config, layers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [sharedView, view];
|
|
||||||
};
|
|
@ -15,7 +15,7 @@ export enum MapCenterID {
|
|||||||
export const centerPointRegistry = new Registry<MapCenterItems>(() => [
|
export const centerPointRegistry = new Registry<MapCenterItems>(() => [
|
||||||
{
|
{
|
||||||
id: MapCenterID.Fit as string,
|
id: MapCenterID.Fit as string,
|
||||||
name: 'Fit data layers',
|
name: 'Fit to data',
|
||||||
zoom: 15, // max zoom
|
zoom: 15, // max zoom
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user