Geomap: Upgrade Open Layers (#107598)
Actionlint / Lint GitHub Actions files (push) Waiting to run Details
Backend Code Checks / Validate Backend Configs (push) Waiting to run Details
Backend Unit Tests / Detect whether code changed (push) Waiting to run Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (1/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (2/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (3/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (4/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (5/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (6/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (7/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana (${{ matrix.shard }}) (8/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (1/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (2/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (3/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (4/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (5/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (6/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (7/8) (push) Blocked by required conditions Details
Backend Unit Tests / Grafana Enterprise (${{ matrix.shard }}) (8/8) (push) Blocked by required conditions Details
Backend Unit Tests / All backend unit tests complete (push) Blocked by required conditions Details
CodeQL checks / Analyze (actions) (push) Waiting to run Details
CodeQL checks / Analyze (go) (push) Waiting to run Details
CodeQL checks / Analyze (javascript) (push) Waiting to run Details
Lint Frontend / Detect whether code changed (push) Waiting to run Details
Lint Frontend / Lint (push) Blocked by required conditions Details
Lint Frontend / Typecheck (push) Blocked by required conditions Details
Lint Frontend / Betterer (push) Blocked by required conditions Details
golangci-lint / lint-go (push) Waiting to run Details
Crowdin Upload Action / upload-sources-to-crowdin (push) Waiting to run Details
Verify i18n / verify-i18n (push) Waiting to run Details
Documentation / Build & Verify Docs (push) Waiting to run Details
End-to-end tests / Detect whether code changed (push) Waiting to run Details
End-to-end tests / Build & Package Grafana (push) Blocked by required conditions Details
End-to-end tests / Build E2E test runner (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/dashboards-suite, dashboards-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/panels-suite, panels-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/smoke-tests-suite, smoke-tests-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (--flags="--env dashboardScene=false", e2e/old-arch/various-suite, various-suite (old arch)) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (e2e/dashboards-suite, dashboards-suite) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (e2e/panels-suite, panels-suite) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (e2e/smoke-tests-suite, smoke-tests-suite) (push) Blocked by required conditions Details
End-to-end tests / ${{ matrix.suite }} (e2e/various-suite, various-suite) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (1, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (2, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (3, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (4, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (5, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (6, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (7, 8) (push) Blocked by required conditions Details
End-to-end tests / Playwright E2E tests (${{ matrix.shard }}/${{ matrix.shardTotal }}) (8, 8) (push) Blocked by required conditions Details
End-to-end tests / All Playwright tests complete (push) Blocked by required conditions Details
End-to-end tests / A11y test (push) Blocked by required conditions Details
End-to-end tests / All E2E tests complete (push) Blocked by required conditions Details
Frontend tests / Detect whether code changed (push) Waiting to run Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (1) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (2) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (3) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (4) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (5) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (6) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (7) (push) Blocked by required conditions Details
Frontend tests / Unit tests (${{ matrix.chunk }} / 8) (8) (push) Blocked by required conditions Details
Frontend tests / All frontend unit tests complete (push) Blocked by required conditions Details
Integration Tests / Sqlite (${{ matrix.shard }}) (1/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (2/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (3/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (4/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (5/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (6/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (7/8) (push) Waiting to run Details
Integration Tests / Sqlite (${{ matrix.shard }}) (8/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (1/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (2/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (3/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (4/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (5/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (6/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (7/8) (push) Waiting to run Details
Integration Tests / MySQL (${{ matrix.shard }}) (8/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (1/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (2/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (3/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (4/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (5/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (6/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (7/8) (push) Waiting to run Details
Integration Tests / Postgres (${{ matrix.shard }}) (8/8) (push) Waiting to run Details
Integration Tests / All backend integration tests complete (push) Blocked by required conditions Details
publish-technical-documentation-next / sync (push) Waiting to run Details
Reject GitHub secrets / reject-gh-secrets (push) Waiting to run Details
Build Release Packages / setup (push) Waiting to run Details
Build Release Packages / Dispatch grafana-enterprise build (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:darwin/amd64, darwin-amd64) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:darwin/arm64, darwin-arm64) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/amd64,deb:grafana:linux/amd64,rpm:grafana:linux/amd64,docker:grafana:linux/amd64,docker:grafana:linux/amd64:ubuntu,npm:grafana,storybook, linux-amd64) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/arm/v6,deb:grafana:linux/arm/v6, linux-armv6) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/arm/v7,deb:grafana:linux/arm/v7,docker:grafana:linux/arm/v7,docker:grafana:linux/arm/v7:ubuntu, linux-armv7) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/arm64,deb:grafana:linux/arm64,rpm:grafana:linux/arm64,docker:grafana:linux/arm64,docker:grafana:linux/arm64:ubuntu, linux-arm64) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:linux/s390x,deb:grafana:linux/s390x,rpm:grafana:linux/s390x,docker:grafana:linux/s390x,docker:grafana:linux/s390x:ubuntu, linux-s390x) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:windows/amd64,zip:grafana:windows/amd64,msi:grafana:windows/amd64, windows-amd64) (push) Blocked by required conditions Details
Build Release Packages / ${{ needs.setup.outputs.version }} / ${{ matrix.name }} (targz:grafana:windows/arm64,zip:grafana:windows/arm64, windows-arm64) (push) Blocked by required conditions Details
Build Release Packages / Upload artifacts (push) Blocked by required conditions Details
Run dashboard schema v2 e2e / dashboard-schema-v2-e2e (push) Waiting to run Details
Shellcheck / Shellcheck scripts (push) Waiting to run Details
Verify Storybook (Playwright) / Verify Storybook (Playwright) (push) Waiting to run Details
Verify Storybook / Verify Storybook (push) Waiting to run Details
Swagger generated code / Verify committed API specs match (push) Waiting to run Details
Dispatch sync to mirror / dispatch-job (push) Waiting to run Details
trigger-dashboard-search-e2e / trigger-search-e2e (push) Waiting to run Details
Trivy Scan / trivy-scan (push) Waiting to run Details

* Geomap: Upgrade Open Layers

* Fix TypeScript errors

* Fix possibly null errors

* Fix pointer events

* Simplify webgl implementation for markers

* Improve types

* Fix offset expression

* Improve layers types

* Improve types for tooltip

* Improve types for frameVectorSource

* For regular shapes convert size to radius

* Update import format for new version of ol

* Fix ESM syntax errors

* Fix more e2e stuff

* Fix tooltip test

* Simplify redundant keys
This commit is contained in:
Drew Slobodnjak 2025-07-15 17:04:41 -07:00 committed by GitHub
parent b691b3288d
commit 1143cf130e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 332 additions and 337 deletions

View File

@ -2336,8 +2336,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/geo/utils/frameVectorSource.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"public/app/features/inspector/InspectDataOptions.tsx:5381": [
[0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"],
@ -3760,9 +3759,6 @@ exports[`better eslint`] = {
"public/app/plugins/panel/geomap/types.ts:5381": [
[0, 0, 0, "Do not re-export imported variable (\`./panelcfg.gen\`)", "0"]
],
"public/app/plugins/panel/geomap/utils/layers.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/panel/geomap/utils/tooltip.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]

View File

@ -30,6 +30,11 @@ const esModules = [
'react-data-grid',
'@grafana/llm',
'pkce-challenge',
'quickselect',
'rbush',
'earcut',
'pbf',
'geotiff',
].join('|');
module.exports = {

View File

@ -371,7 +371,7 @@
"moveable": "0.53.0",
"nanoid": "^5.0.9",
"node-forge": "^1.3.1",
"ol": "7.4.0",
"ol": "10.6.0",
"ol-ext": "4.0.33",
"pluralize": "^8.0.0",
"prismjs": "1.30.0",

View File

@ -72,7 +72,7 @@
"marked-mangle": "1.1.11",
"moment": "2.30.1",
"moment-timezone": "0.5.47",
"ol": "7.4.0",
"ol": "10.6.0",
"papaparse": "5.5.3",
"react-use": "17.6.0",
"rxjs": "7.8.2",

View File

@ -1,4 +1,4 @@
import { Map as OpenLayersMap } from 'ol';
import OpenLayersMap from 'ol/Map';
import BaseLayer from 'ol/layer/Base';
import { ReactNode } from 'react';

View File

@ -98,7 +98,7 @@
"micro-memoize": "^4.1.2",
"moment": "2.30.1",
"monaco-editor": "0.34.1",
"ol": "7.4.0",
"ol": "10.6.0",
"prismjs": "1.30.0",
"rc-cascader": "3.34.0",
"rc-drawer": "7.3.0",

View File

@ -1,5 +1,5 @@
import { WKT } from 'ol/format';
import { Geometry } from 'ol/geom';
import WKT from 'ol/format/WKT';
import Geometry from 'ol/geom/Geometry';
import { FieldType } from '@grafana/data';
import { t } from '@grafana/i18n';

View File

@ -1,14 +1,24 @@
import { Feature } from 'ol';
import { Geometry, LineString, Point } from 'ol/geom';
import Feature from 'ol/Feature';
import { Geometry } from 'ol/geom';
import VectorSource from 'ol/source/Vector';
import { DataFrame, Field } from '@grafana/data';
import { DataFrame } from '@grafana/data';
import { getGeometryField, LocationFieldMatchers } from './location';
export interface FrameVectorSourceOptions {}
export class FrameVectorSource<T extends Geometry = Geometry> extends VectorSource<T> {
// Helper function to create properly typed Features
function createFeature<T extends Geometry>(properties: {
frame: DataFrame;
rowIndex: number;
geometry: T;
}): Feature<T> {
const feature = new Feature(properties);
return feature;
}
export class FrameVectorSource<T extends Geometry = Geometry> extends VectorSource<Feature<T>> {
constructor(public location: LocationFieldMatchers) {
super({});
}
@ -22,11 +32,12 @@ export class FrameVectorSource<T extends Geometry = Geometry> extends VectorSour
}
for (let i = 0; i < frame.length; i++) {
const geometry = info.field.values[i] as T;
this.addFeatureInternal(
new Feature({
createFeature({
frame,
rowIndex: i,
geometry: info.field.values[i] as T,
geometry,
})
);
}
@ -34,27 +45,4 @@ export class FrameVectorSource<T extends Geometry = Geometry> extends VectorSour
// only call this at the end
this.changed();
}
updateLineString(frame: DataFrame) {
this.clear(true);
const info = getGeometryField(frame, this.location);
if (!info.field) {
this.changed();
return;
}
//eslint-disable-next-line
const field = info.field as unknown as Field<Point>;
const geometry: Geometry = new LineString(field.values.map((p) => p.getCoordinates()));
this.addFeatureInternal(
new Feature({
frame,
rowIndex: 0,
geometry: geometry as T,
})
);
// only call this at the end
this.changed();
}
}

View File

@ -1,6 +1,8 @@
import { css } from '@emotion/css';
import { Global } from '@emotion/react';
import { Map as OpenLayersMap, MapBrowserEvent, View } from 'ol';
import OpenLayersMap from 'ol/Map';
import MapBrowserEvent from 'ol/MapBrowserEvent';
import View from 'ol/View';
import Attribution from 'ol/control/Attribution';
import ScaleLine from 'ol/control/ScaleLine';
import Zoom from 'ol/control/Zoom';
@ -273,11 +275,11 @@ export class GeomapPanel extends Component<Props, State> {
this.setState({ ttipOpen: false, ttip: undefined });
};
pointerClickListener = (evt: MapBrowserEvent<MouseEvent>) => {
pointerClickListener = (evt: MapBrowserEvent<PointerEvent>) => {
pointerClickListener(evt, this);
};
pointerMoveListener = (evt: MapBrowserEvent<MouseEvent>) => {
pointerMoveListener = (evt: MapBrowserEvent<PointerEvent>) => {
pointerMoveListener(evt, this);
};

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { Map } from 'ol';
import Map from 'ol/Map';
import { Coordinate } from 'ol/coordinate';
import { transform } from 'ol/proj';
import { PureComponent } from 'react';

View File

@ -200,24 +200,24 @@ export class MeasureVectorLayer extends VectorLayer<VectorSource> {
}
const segmentPoint = new Point(segment.getCoordinateAt(0.5));
this.segmentStyles[count].setGeometry(segmentPoint);
this.segmentStyles[count].getText().setText(label);
this.segmentStyles[count].getText()?.setText(label);
styles.push(this.segmentStyles[count]);
count++;
});
}
if (label!) {
this.labelStyle.setGeometry(point!);
this.labelStyle.getText().setText(label);
this.labelStyle.getText()?.setText(label);
styles.push(this.labelStyle);
}
if (
tip &&
type === 'Point' &&
geometry instanceof Point &&
!this.modify.getOverlay().getSource().getFeatures().length
!this.modify.getOverlay().getSource()?.getFeatures().length
) {
this.tipPoint = geometry;
this.tipStyle.getText().setText(tip);
this.tipStyle.getText()?.setText(tip);
styles.push(this.tipStyle);
}
}

View File

@ -1,4 +1,4 @@
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
@ -33,7 +33,12 @@ export const carto: MapLayerRegistryItem<CartoConfig> = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<CartoConfig>, eventBus: EventBus, theme: GrafanaTheme2) => ({
create: async (
map: OpenLayersMap,
options: MapLayerOptions<CartoConfig>,
eventBus: EventBus,
theme: GrafanaTheme2
) => ({
init: () => {
const cfg = { ...defaultCartoConfig, ...options.config };
let style: string | undefined = cfg.theme;

View File

@ -1,4 +1,4 @@
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import { MapLayerRegistryItem, MapLayerOptions, GrafanaTheme2, RegistryItem, Registry, EventBus } from '@grafana/data';
@ -60,7 +60,12 @@ export const esriXYZTiles: MapLayerRegistryItem<ESRIXYZConfig> = {
description: 'Add layer from an ESRI ArcGIS MapServer',
isBaseMap: true,
create: async (map: Map, options: MapLayerOptions<ESRIXYZConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (
map: OpenLayersMap,
options: MapLayerOptions<ESRIXYZConfig>,
eventBus: EventBus,
theme: GrafanaTheme2
) => {
const cfg = { ...options.config };
const svc = publicServiceRegistry.getIfExists(cfg.server ?? DEFAULT_SERVICE)!;
if (svc.id !== CUSTOM_SERVICE) {

View File

@ -1,4 +1,4 @@
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
@ -23,7 +23,12 @@ export const xyzTiles: MapLayerRegistryItem<XYZConfig> = {
description: 'Add map from a generic tile layer',
isBaseMap: true,
create: async (map: Map, options: MapLayerOptions<XYZConfig>, eventBus: EventBus, theme: GrafanaTheme2) => ({
create: async (
map: OpenLayersMap,
options: MapLayerOptions<XYZConfig>,
eventBus: EventBus,
theme: GrafanaTheme2
) => ({
init: () => {
const cfg = { ...options.config };
if (!cfg.url) {

View File

@ -1,4 +1,4 @@
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import TileLayer from 'ol/layer/Tile';
import OSM from 'ol/source/OSM';
@ -14,7 +14,7 @@ export const standard: MapLayerRegistryItem = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions, eventBus: EventBus) => ({
create: async (map: OpenLayersMap, options: MapLayerOptions, eventBus: EventBus) => ({
init: () => {
return new TileLayer({
source: new OSM(),

View File

@ -1,5 +1,5 @@
import Feature from 'ol/Feature';
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import Point from 'ol/geom/Point';
import { Group as LayerGroup } from 'ol/layer';
import VectorImage from 'ol/layer/VectorImage';
@ -60,7 +60,7 @@ export const dayNightLayer: MapLayerRegistryItem<DayNightConfig> = {
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<DayNightConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<DayNightConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultConfig,

View File

@ -1,5 +1,5 @@
import { FeatureLike } from 'ol/Feature';
import OlMap from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import { unByKey } from 'ol/Observable';
import GeoJSON from 'ol/format/GeoJSON';
import VectorImage from 'ol/layer/VectorImage';
@ -78,7 +78,7 @@ export const dynamicGeoJSONLayer: MapLayerRegistryItem<DynamicGeoJSONMapperConfi
* @param options
* @param theme
*/
create: async (map: OlMap, options: MapLayerOptions<DynamicGeoJSONMapperConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<DynamicGeoJSONMapperConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const config = { ...defaultOptions, ...options.config };
const source = new VectorSource({

View File

@ -1,5 +1,5 @@
import { FeatureLike } from 'ol/Feature';
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import { unByKey } from 'ol/Observable';
import GeoJSON from 'ol/format/GeoJSON';
import VectorImage from 'ol/layer/VectorImage';
@ -73,7 +73,7 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<GeoJSONMapperConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<GeoJSONMapperConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const config = { ...defaultOptions, ...options.config };
// Interpolate variables in the URL

View File

@ -1,4 +1,4 @@
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import { Point } from 'ol/geom';
import * as layer from 'ol/layer';
@ -46,9 +46,11 @@ export const heatmapLayer: MapLayerRegistryItem<HeatmapConfig> = {
/**
* Function that configures transformation and returns a transformer
* @param map
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<HeatmapConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<HeatmapConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const config = { ...defaultOptions, ...options.config };
const location = await getLocationMatchers(options.location);

View File

@ -1,5 +1,5 @@
import Feature from 'ol/Feature';
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import * as layer from 'ol/layer';
import * as source from 'ol/source';
import * as style from 'ol/style';
@ -25,9 +25,11 @@ export const lastPointTracker: MapLayerRegistryItem<LastPointConfig> = {
/**
* Function that configures transformation and returns a transformer
* @param map
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<LastPointConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<LastPointConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
const point = new Feature({});
const config = { ...defaultOptions, ...options.config };

View File

@ -1,4 +1,4 @@
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import { Point } from 'ol/geom';
import { VectorImage } from 'ol/layer';
import LayerGroup from 'ol/layer/Group';
@ -14,6 +14,7 @@ import {
GrafanaTheme2,
FrameGeometrySourceMode,
EventBus,
PanelOptionsEditorBuilder,
} from '@grafana/data';
import { t } from '@grafana/i18n';
import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource';
@ -68,7 +69,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<MarkersConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<MarkersConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultOptions,
@ -221,7 +222,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
},
// Marker overlay options
registerOptionsUI: (builder) => {
registerOptionsUI: (builder: PanelOptionsEditorBuilder<MapLayerOptions<MarkersConfig>>) => {
builder
.addCustomEditor({
id: 'config.style',

View File

@ -1,7 +1,6 @@
import { isNumber } from 'lodash';
import { Feature } from 'ol';
import { FeatureLike } from 'ol/Feature';
import Map from 'ol/Map';
import Feature, { FeatureLike } from 'ol/Feature';
import OpenLayersMap from 'ol/Map';
import { Geometry, LineString, Point, SimpleGeometry } from 'ol/geom';
import VectorImage from 'ol/layer/VectorImage';
import { Fill, Stroke, Style, Text } from 'ol/style';
@ -76,10 +75,9 @@ export const networkLayer: MapLayerRegistryItem<NetworkConfig> = {
* Function that configures transformation and returns a transformer
* @param map
* @param options
* @param eventBus
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<NetworkConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<NetworkConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultOptions,
@ -90,9 +88,8 @@ export const networkLayer: MapLayerRegistryItem<NetworkConfig> = {
const edgeStyle = await getStyleConfigState(config.edgeStyle);
const location = await getLocationMatchers(options.location);
const source = new FrameVectorSource(location);
const vectorLayer = new VectorImage({
source,
source
});
const hasArrows = config.arrow === 1 || config.arrow === -1 || config.arrow === 2;

View File

@ -1,12 +1,21 @@
import { FeatureLike } from 'ol/Feature';
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import VectorImage from 'ol/layer/VectorImage';
import { Stroke, Style } from 'ol/style';
import Photo from 'ol-ext/style/Photo';
import { MapLayerRegistryItem, PanelData, GrafanaTheme2, EventBus, PluginState, FieldType, Field } from '@grafana/data';
import {
MapLayerRegistryItem,
PanelData,
GrafanaTheme2,
EventBus,
PluginState,
FieldType,
Field,
MapLayerOptions,
} from '@grafana/data';
import { t } from '@grafana/i18n';
import { FrameGeometrySourceMode, MapLayerOptions } from '@grafana/schema';
import { FrameGeometrySourceMode } from '@grafana/schema';
import { findField } from 'app/features/dimensions/utils';
import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource';
import { getLocationMatchers } from 'app/features/geo/utils/location';
@ -70,7 +79,7 @@ export const photosLayer: MapLayerRegistryItem<PhotoConfig> = {
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<PhotoConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<PhotoConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultOptions,

View File

@ -1,6 +1,6 @@
import { isNumber } from 'lodash';
import Feature, { FeatureLike } from 'ol/Feature';
import Map from 'ol/Map';
import OpenLayersMap from 'ol/Map';
import { LineString, Point, SimpleGeometry } from 'ol/geom';
import { Group as LayerGroup } from 'ol/layer';
import VectorImage from 'ol/layer/VectorImage';
@ -20,9 +20,10 @@ import {
DataHoverClearEvent,
DataFrame,
FieldType,
colorManipulator
colorManipulator,
MapLayerOptions,
} from '@grafana/data';
import { MapLayerOptions, FrameGeometrySourceMode } from '@grafana/schema';
import { FrameGeometrySourceMode } from '@grafana/schema';
import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource';
import { getGeometryField, getLocationMatchers } from 'app/features/geo/utils/location';
@ -84,7 +85,7 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
* Function that configures transformation and returns a transformer
* @param options
*/
create: async (map: Map, options: MapLayerOptions<RouteConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
create: async (map: OpenLayersMap, options: MapLayerOptions<RouteConfig>, eventBus: EventBus, theme: GrafanaTheme2) => {
// Assert default values
const config = {
...defaultOptions,
@ -102,7 +103,7 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
const styleBase = routeStyle(style.base);
if (style.config.size && style.config.size.fixed) {
// Applies width to base style if specified
styleBase.getStroke().setWidth(style.config.size.fixed);
styleBase.getStroke()?.setWidth(style.config.size.fixed);
}
vectorLayer.setStyle(styleBase);
} else {
@ -306,7 +307,30 @@ export const routeLayer: MapLayerRegistryItem<RouteConfig> = {
style.dims = getStyleDimension(frame, style, theme);
}
source.updateLineString(frame);
source.clear(true);
const info = getGeometryField(frame, location);
if (!info.field) {
source.changed();
break;
}
const coords: number[][] = [];
for (const v of info.field.values) {
if (v instanceof Point) {
coords.push(v.getCoordinates());
}
}
if (coords.length >= 2) {
const geometry = new LineString(coords);
source['addFeatureInternal'](
new Feature({
frame,
rowIndex: 0,
geometry,
})
);
}
source.changed();
break; // Only the first frame for now!
}
},

View File

@ -1,4 +1,4 @@
import { Map as OpenLayersMap } from 'ol';
import OpenLayersMap from 'ol/Map';
import {
MapLayerRegistryItem,

View File

@ -1,6 +1,14 @@
import { getPublicOrAbsoluteUrl } from 'app/features/dimensions/resource';
import { getWebGLStyle } from './markers';
import {
getWebGLStyle,
baseCircleStyle,
baseShapeStyle,
sizeExpression,
opacityExpression,
rotationExpression,
offsetExpression,
} from './markers';
// Mock dependencies
jest.mock('app/features/dimensions/resource', () => ({
@ -14,40 +22,30 @@ describe('getWebGLStyle', () => {
it('returns default circle style when no symbol is provided', async () => {
const result = await getWebGLStyle();
expect(result).toEqual({
symbol: {
symbolType: 'circle',
size: ['get', 'size', 'number'],
color: ['color', ['get', 'red'], ['get', 'green'], ['get', 'blue']],
offset: ['array', ['get', 'offsetX'], ['get', 'offsetY']],
rotation: ['get', 'rotation', 'number'],
opacity: ['get', 'opacity', 'number'],
},
});
expect(result).toEqual(baseCircleStyle);
});
it('returns circle style for known WebGL regular shape', async () => {
const result = await getWebGLStyle('img/icons/marker/circle.svg');
if (result.symbol) {
expect(result.symbol.symbolType).toBe('circle');
expect(result.symbol).not.toHaveProperty('src');
}
expect(result).toEqual(baseCircleStyle);
});
it('returns square style for known WebGL regular shape', async () => {
it('returns shape style for square WebGL regular shape', async () => {
const result = await getWebGLStyle('img/icons/marker/square.svg');
if (result.symbol) {
expect(result.symbol.symbolType).toBe('square');
expect(result.symbol).not.toHaveProperty('src');
}
expect(result).toEqual({
...baseShapeStyle,
'shape-points': 4,
'shape-angle': Math.PI / 4,
});
});
it('returns triangle style for known WebGL regular shape', async () => {
it('returns shape style for triangle WebGL regular shape', async () => {
const result = await getWebGLStyle('img/icons/marker/triangle.svg');
if (result.symbol) {
expect(result.symbol.symbolType).toBe('triangle');
expect(result.symbol).not.toHaveProperty('src');
}
expect(result).toEqual({
...baseShapeStyle,
'shape-points': 3,
'shape-angle': 0,
});
});
it('returns image style with src for custom SVG symbol', async () => {
@ -62,10 +60,12 @@ describe('getWebGLStyle', () => {
} as Response)
);
const result = await getWebGLStyle('test.svg');
if (result.symbol) {
expect(result.symbol.symbolType).toBe('image');
expect(result.symbol.src).toContain('data:image/svg+xml');
}
expect(result['icon-src']).toContain('data:image/svg+xml');
expect(result['icon-width']).toEqual(sizeExpression);
expect(result['icon-height']).toEqual(sizeExpression);
expect(result['icon-opacity']).toEqual(opacityExpression);
expect(result['icon-rotation']).toEqual(rotationExpression);
expect(result['icon-displacement']).toEqual(offsetExpression);
});
it('includes background circle with opacity-adjusted stroke when opacity is provided', async () => {
@ -79,12 +79,10 @@ describe('getWebGLStyle', () => {
} as Response)
);
const result = await getWebGLStyle('custom.svg', 0.5);
if (result.symbol?.src) {
expect(result.symbol.symbolType).toBe('image');
expect(result.symbol.src).toContain('circle');
const decodedSrc = decodeURIComponent(result.symbol.src);
expect(decodedSrc).toContain('stroke="rgba(255,255,255,0.2)"'); // 0.1 / 0.5 = 0.2
}
const iconSrc = result['icon-src'] as string;
expect(iconSrc).toContain('circle');
const decodedSrc = decodeURIComponent(iconSrc);
expect(decodedSrc).toContain('stroke="rgba(255,255,255,0.2)"'); // 0.1 / 0.5 = 0.2
});
it('handles fetch error gracefully', async () => {
@ -94,10 +92,8 @@ describe('getWebGLStyle', () => {
(getPublicOrAbsoluteUrl as jest.Mock).mockReturnValue('error.svg');
global.fetch = jest.fn(() => Promise.reject(new Error('Fetch failed')));
const result = await getWebGLStyle('error.svg');
if (result.symbol) {
expect(result.symbol.symbolType).toBe('image');
expect(result.symbol.src).toBe(''); // Empty SVG
}
expect(result['icon-src']).toBe(''); // Empty SVG
// Verify console.error was called with the expected error
expect(consoleErrorSpy).toHaveBeenCalledWith(new Error('Fetch failed'));

View File

@ -1,5 +1,5 @@
import { Fill, RegularShape, Stroke, Circle, Style, Icon, Text } from 'ol/style';
import { LiteralStyle } from 'ol/style/literal';
import type { FlatStyle } from 'ol/style/flat';
import tinycolor from 'tinycolor2';
import { Registry, RegistryItem, textUtil } from '@grafana/data';
@ -32,12 +32,6 @@ const MarkerShapePath = {
x: 'img/icons/marker/x-mark.svg',
};
const WebGLRegularShapes: Record<string, string> = {
circle: 'img/icons/marker/circle.svg',
square: 'img/icons/marker/square.svg',
triangle: 'img/icons/marker/triangle.svg',
};
export function getFillColor(cfg: StyleConfigValues) {
const opacity = cfg.opacity == null ? 0.8 : cfg.opacity;
if (opacity === 1) {
@ -319,34 +313,75 @@ export function getMarkerAsPath(shape?: string): string | undefined {
return undefined;
}
// Returns literal style for WebGL markers
export async function getWebGLStyle(symbol?: string, opacity?: number): Promise<LiteralStyle> {
// style expressions
const symbolStyle: LiteralStyle = {
symbol: {
symbolType: 'circle',
size: ['get', 'size', 'number'],
color: ['color', ['get', 'red'], ['get', 'green'], ['get', 'blue']],
offset: ['array', ['get', 'offsetX'], ['get', 'offsetY']],
rotation: ['get', 'rotation', 'number'],
opacity: ['get', 'opacity', 'number'],
},
};
// set symbolType and src if a symbol is provided
if (symbol && symbolStyle.symbol) {
const imageString = 'image';
const symbolType = Object.keys(WebGLRegularShapes).find((key) => WebGLRegularShapes[key] === symbol) ?? imageString;
symbolStyle.symbol = { ...symbolStyle.symbol, symbolType };
if (symbolType === imageString) {
const backgroundOpacity = opacity === 0 ? 0 : 0.1 / (opacity ?? 1);
symbolStyle.symbol = {
...symbolStyle.symbol,
src: await prepareSVG(getPublicOrAbsoluteUrl(symbol), undefined, backgroundOpacity),
};
}
// Common expressions used across different style types
export const colorExpression = ['color', ['get', 'red'], ['get', 'green'], ['get', 'blue'], ['get', 'opacity']];
export const sizeExpression = ['get', 'size'];
export const opacityExpression = ['get', 'opacity'];
export const rotationExpression = ['get', 'rotation'];
export const offsetExpression = ['array', ['get', 'offsetX'], ['get', 'offsetY']];
// Base style for regular shapes
export const baseShapeStyle = {
'shape-radius': ['/', sizeExpression, 2],
'shape-fill-color': colorExpression,
'shape-stroke-color': colorExpression,
'shape-stroke-width': 1,
'shape-opacity': opacityExpression,
'shape-rotation': rotationExpression,
'shape-displacement': offsetExpression,
};
// Base style for circles
export const baseCircleStyle = {
'circle-radius': ['/', sizeExpression, 2],
'circle-fill-color': colorExpression,
'circle-stroke-color': colorExpression,
'circle-stroke-width': 1,
'circle-opacity': opacityExpression,
'circle-displacement': offsetExpression,
};
// Returns style configuration for WebGL markers
export async function getWebGLStyle(symbol?: string, opacity?: number): Promise<FlatStyle> {
// Handle circle explicitly (before generic SVG check)
if (symbol === MarkerShapePath.circle) {
return baseCircleStyle;
}
return symbolStyle;
// Handle square as WebGL regular shape
if (symbol === MarkerShapePath.square) {
return {
...baseShapeStyle,
'shape-points': 4,
'shape-angle': Math.PI / 4,
};
}
// Handle triangle as WebGL regular shape
if (symbol === MarkerShapePath.triangle) {
return {
...baseShapeStyle,
'shape-points': 3,
'shape-angle': 0,
};
}
// Handle custom SVG symbols and other shapes as icons
if (symbol && symbol.endsWith('.svg')) {
const backgroundOpacity = opacity === 0 ? 0 : 0.1 / (opacity ?? 1);
return {
'icon-src': await prepareSVG(getPublicOrAbsoluteUrl(symbol), undefined, backgroundOpacity),
'icon-width': sizeExpression,
'icon-height': sizeExpression,
'icon-opacity': opacityExpression,
'icon-rotation': rotationExpression,
'icon-displacement': offsetExpression,
'icon-color': colorExpression,
};
}
// Default to circle (also handles MarkerShapePath.circle)
return baseCircleStyle;
}
// Will prepare symbols as necessary
@ -388,7 +423,7 @@ export async function getMarkerMaker(symbol?: string, hasTextLabel?: boolean): P
image: new RegularShape({
fill: new Fill({ color: 'rgba(0,0,0,0)' }),
points: 4,
radius: cfg.size,
radius: radius,
rotation: (rotation * Math.PI) / 180 + Math.PI / 4,
}),
}),

View File

@ -1,5 +1,5 @@
import { Map as OpenLayersMap } from 'ol';
import { FeatureLike } from 'ol/Feature';
import OpenLayersMap from 'ol/Map';
import { Units } from 'ol/control/ScaleLine';
import BaseLayer from 'ol/layer/Base';
import { Subject } from 'rxjs';

View File

@ -1,4 +1,4 @@
import { Feature } from 'ol';
import Feature from 'ol/Feature';
import { ComparisonOperation } from '@grafana/schema';

View File

@ -1,4 +1,4 @@
import { Feature } from 'ol';
import Feature from 'ol/Feature';
import { Point } from 'ol/geom';
import { GeometryTypeId } from '../style/types';

View File

@ -1,5 +1,6 @@
import { Map as OpenLayersMap } from 'ol';
import { FeatureLike } from 'ol/Feature';
import OpenLayersMap from 'ol/Map';
import BaseLayer from 'ol/layer/Base';
import LayerGroup from 'ol/layer/Group';
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import { Subject } from 'rxjs';
@ -14,6 +15,8 @@ import { MapLayerState } from '../types';
import { getNextLayerName } from './utils';
const layerStateMap = new WeakMap<BaseLayer, MapLayerState>();
export const applyLayerFilter = (
handler: MapLayerHandler<unknown>,
options: MapLayerOptions<unknown>,
@ -148,18 +151,16 @@ export async function initLayer(
};
panel.byName.set(UID, state);
// eslint-disable-next-line
(state.layer as any).__state = state;
layerStateMap.set(state.layer, state);
// Pass state into WebGLPointsLayers contained in a LayerGroup
if (layer instanceof LayerGroup) {
layer
.getLayers()
.getArray()
.forEach((layer) => {
.forEach((layer: BaseLayer) => {
if (layer instanceof WebGLPointsLayer) {
// eslint-disable-next-line
(layer as any).__state = state;
layerStateMap.set(layer, state);
}
});
}
@ -169,6 +170,6 @@ export async function initLayer(
return state;
}
export const getMapLayerState = (l: any): MapLayerState => {
return l?.__state;
export const getMapLayerState = (l: BaseLayer | undefined): MapLayerState | undefined => {
return l ? layerStateMap.get(l) : undefined;
};

View File

@ -1,4 +1,5 @@
import { Feature, MapBrowserEvent } from 'ol';
import Feature from 'ol/Feature';
import MapBrowserEvent from 'ol/MapBrowserEvent';
import { Point } from 'ol/geom';
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import VectorSource from 'ol/source/Vector';
@ -55,9 +56,9 @@ jest.mock('./layers', () => {
describe('tooltip utils', () => {
let panel: GeomapPanel;
let mockEvent: MapBrowserEvent<MouseEvent>;
let mockWebGLLayer: WebGLPointsLayer<VectorSource<Point>>;
let mockVectorSource: VectorSource<Point>;
let mockEvent: MapBrowserEvent<PointerEvent>;
let mockWebGLLayer: WebGLPointsLayer<VectorSource<Feature<Point>>>;
let mockVectorSource: VectorSource<Feature<Point>>;
// Consolidated feature constants
let feature1: Feature;
@ -72,12 +73,15 @@ describe('tooltip utils', () => {
// Create mock objects
panel = new GeomapPanel({} as PanelProps<Options>);
// Create a proper MouseEvent instance to pass the instanceof check
const mouseEvent = new MouseEvent('pointermove');
Object.defineProperty(mouseEvent, 'pageX', { value: 100 });
Object.defineProperty(mouseEvent, 'pageY', { value: 100 });
mockEvent = {
originalEvent: {
pageX: 100,
pageY: 100,
},
} as MapBrowserEvent<MouseEvent>;
originalEvent: mouseEvent,
} as MapBrowserEvent<PointerEvent>;
// Create features for testing
feature1 = new Feature({
@ -111,19 +115,16 @@ describe('tooltip utils', () => {
});
// Create mock vector source
mockVectorSource = new VectorSource<Point>();
mockVectorSource = new VectorSource<Feature<Point>>();
mockVectorSource.forEachFeature = jest.fn();
// Create mock WebGL layer
mockWebGLLayer = new WebGLPointsLayer({
source: mockVectorSource,
style: {
symbol: {
symbolType: 'circle',
size: 8,
color: '#000000',
opacity: 1,
},
'circle-radius': 8,
'circle-fill-color': '#000000',
'circle-opacity': 1,
},
});
mockWebGLLayer.getSource = jest.fn().mockReturnValue(mockVectorSource);

View File

@ -1,6 +1,6 @@
import { debounce } from 'lodash';
import { MapBrowserEvent } from 'ol';
import { FeatureLike } from 'ol/Feature';
import MapBrowserEvent from 'ol/MapBrowserEvent';
import { Point } from 'ol/geom';
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
import { toLonLat } from 'ol/proj';
@ -16,14 +16,17 @@ import { getMapLayerState } from './layers';
export const setTooltipListeners = (panel: GeomapPanel) => {
// Tooltip listener
panel.map?.on('singleclick', panel.pointerClickListener);
panel.map?.on('pointermove', debounce(panel.pointerMoveListener, 200));
panel.map?.on('singleclick', (evt) => pointerClickListener(evt, panel));
panel.map?.on(
'pointermove',
debounce((evt) => pointerMoveListener(evt, panel), 200)
);
panel.map?.getViewport().addEventListener('mouseout', (evt: MouseEvent) => {
panel.props.eventBus.publish(new DataHoverClearEvent());
});
};
export const pointerClickListener = (evt: MapBrowserEvent<MouseEvent>, panel: GeomapPanel) => {
export const pointerClickListener = (evt: MapBrowserEvent, panel: GeomapPanel) => {
if (pointerMoveListener(evt, panel)) {
evt.preventDefault();
evt.stopPropagation();
@ -32,7 +35,7 @@ export const pointerClickListener = (evt: MapBrowserEvent<MouseEvent>, panel: Ge
}
};
export const pointerMoveListener = (evt: MapBrowserEvent<MouseEvent>, panel: GeomapPanel) => {
export const pointerMoveListener = (evt: MapBrowserEvent, panel: GeomapPanel) => {
// If measure menu is open, bypass tooltip logic and display measuring mouse events
if (panel.state.measureMenuActive) {
return true;
@ -43,6 +46,10 @@ export const pointerMoveListener = (evt: MapBrowserEvent<MouseEvent>, panel: Geo
return false;
}
if (!(evt.originalEvent instanceof MouseEvent)) {
return false;
}
const mouse = evt.originalEvent;
const pixel = panel.map.getEventPixel(mouse);
const hover = toLonLat(panel.map.getCoordinateFromPixel(pixel));

View File

@ -38,8 +38,8 @@ import { hasVariableDependencies, hasLayerData } from './utils';
// Test fixtures
const createTestFeature = () => new Feature(new Point([0, 0]));
const createTestVectorSource = (hasFeature = false): VectorSource<Point> => {
const source = new VectorSource<Point>();
const createTestVectorSource = (hasFeature = false): VectorSource<Feature<Point>> => {
const source = new VectorSource<Feature<Point>>();
if (hasFeature) {
source.addFeature(createTestFeature());
}
@ -47,12 +47,9 @@ const createTestVectorSource = (hasFeature = false): VectorSource<Point> => {
};
const createTestWebGLStyle = () => ({
symbol: {
symbolType: 'circle',
size: 8,
color: '#000000',
opacity: 1,
},
'circle-radius': 8,
'circle-fill-color': '#000000',
'circle-opacity': 1,
});
describe('hasVariableDependencies', () => {

View File

@ -1,4 +1,5 @@
import { Map as OpenLayersMap } from 'ol';
import Feature from 'ol/Feature';
import OpenLayersMap from 'ol/Map';
import Geometry from 'ol/geom/Geometry';
import Point from 'ol/geom/Point';
import { defaults as interactionDefaults } from 'ol/interaction';
@ -178,9 +179,9 @@ export const isUrl = (url: string) => {
export function hasLayerData(
layer:
| LayerGroup
| VectorLayer<VectorSource<Geometry>>
| VectorImage<VectorSource<Geometry>>
| WebGLPointsLayer<VectorSource<Point>>
| VectorLayer<VectorSource<Feature<Geometry>>>
| VectorImage<VectorSource<Feature<Geometry>>>
| WebGLPointsLayer<VectorSource<Feature<Point>>>
| TileLayer<TileSource>
| ImageLayer<ImageSource>
| BaseLayer

182
yarn.lock
View File

@ -3094,7 +3094,7 @@ __metadata:
marked-mangle: "npm:1.1.11"
moment: "npm:2.30.1"
moment-timezone: "npm:0.5.47"
ol: "npm:7.4.0"
ol: "npm:10.6.0"
papaparse: "npm:5.5.3"
react: "npm:18.3.1"
react-dom: "npm:18.3.1"
@ -3771,7 +3771,7 @@ __metadata:
monaco-editor: "npm:0.34.1"
msw: "npm:^2.10.2"
msw-storybook-addon: "npm:^2.0.5"
ol: "npm:7.4.0"
ol: "npm:10.6.0"
prismjs: "npm:1.30.0"
process: "npm:^0.11.10"
rc-cascader: "npm:3.34.0"
@ -5010,48 +5010,6 @@ __metadata:
languageName: node
linkType: hard
"@mapbox/jsonlint-lines-primitives@github:mapbox/jsonlint#commit=e31b7289baedf3e1000d7ae7edd42268212c9954":
version: 2.0.2
resolution: "@mapbox/jsonlint-lines-primitives@https://github.com/mapbox/jsonlint.git#commit=e31b7289baedf3e1000d7ae7edd42268212c9954"
checksum: 10/660bd93beac97f0f4bdcfbbded3282efaf2ab4debb99da55f7fb7b27250e0dfa7dd572a9040802fdc008c71d0bb5ff7e565a7426177fda6c3d17e9bf1598bbaf
languageName: node
linkType: hard
"@mapbox/mapbox-gl-style-spec@npm:^13.23.1":
version: 13.25.0
resolution: "@mapbox/mapbox-gl-style-spec@npm:13.25.0"
dependencies:
"@mapbox/jsonlint-lines-primitives": "npm:~2.0.2"
"@mapbox/point-geometry": "npm:^0.1.0"
"@mapbox/unitbezier": "npm:^0.0.0"
csscolorparser: "npm:~1.0.2"
json-stringify-pretty-compact: "npm:^2.0.0"
minimist: "npm:^1.2.5"
rw: "npm:^1.3.3"
sort-object: "npm:^0.3.2"
bin:
gl-style-composite: bin/gl-style-composite.js
gl-style-format: bin/gl-style-format.js
gl-style-migrate: bin/gl-style-migrate.js
gl-style-validate: bin/gl-style-validate.js
checksum: 10/cabd9629bde72fe961f9a9d957534f75eb74bddb10068bf7b43fa73868eeb857a31196a12dc2a4532ab732bc7d38392e9bd4582fa9021ef9a5c3465e964201d2
languageName: node
linkType: hard
"@mapbox/point-geometry@npm:^0.1.0":
version: 0.1.0
resolution: "@mapbox/point-geometry@npm:0.1.0"
checksum: 10/f6f78ac8a7f798efb19db6eb1a9e05da7ba942102f5347c1a673d94202d0c606ec3f522efa3e76d583cdca46fb96dde52c3d37234f162d21df42f9e8c4f182bd
languageName: node
linkType: hard
"@mapbox/unitbezier@npm:^0.0.0":
version: 0.0.0
resolution: "@mapbox/unitbezier@npm:0.0.0"
checksum: 10/211fc5b0a40fafa0127baf87938a6a00535b22b51bec95df2f6141cf1dd50339bca2a9729c7a9803cdee5c2b4e0e3323a882655c74f1a86e557096684196e1ff
languageName: node
linkType: hard
"@mdx-js/react@npm:^3.0.0":
version: 3.0.1
resolution: "@mdx-js/react@npm:3.0.1"
@ -9867,6 +9825,13 @@ __metadata:
languageName: node
linkType: hard
"@types/rbush@npm:4.0.0":
version: 4.0.0
resolution: "@types/rbush@npm:4.0.0"
checksum: 10/ee37c7fa322a83af4e754180022253b7e2bbbb853c9a6d8ffce1ea1e59bbb0eef3a347e2f37934bb1afa4d40ea2ea44409d7cee751cb8ac8dbded13dc9160a64
languageName: node
linkType: hard
"@types/react-color@npm:3.0.13":
version: 3.0.13
resolution: "@types/react-color@npm:3.0.13"
@ -14083,13 +14048,6 @@ __metadata:
languageName: node
linkType: hard
"csscolorparser@npm:~1.0.2":
version: 1.0.3
resolution: "csscolorparser@npm:1.0.3"
checksum: 10/b46b9032eaace69e5bb151bb64473547d8e48813b45395c1923cde1c87674bbd030c99536f44373e092de6afdca1a8134933a1503e779aaeb703b7c7897f5eca
languageName: node
linkType: hard
"cssesc@npm:^3.0.0":
version: 3.0.0
resolution: "cssesc@npm:3.0.0"
@ -15357,10 +15315,10 @@ __metadata:
languageName: node
linkType: hard
"earcut@npm:^2.2.3":
version: 2.2.4
resolution: "earcut@npm:2.2.4"
checksum: 10/ca8b24714cc2fa67f98fbca6ddcf64bb42ee8d75d0b4f1a81486b3282b0f7f1bf9ec49ad4d02149985886a0c8a03a173463f2acb1f51fa0bb7ba2e1d4aa1254d
"earcut@npm:^3.0.0":
version: 3.0.1
resolution: "earcut@npm:3.0.1"
checksum: 10/92786b5a35276a099eb2545bd7183e31a8ddd34f0d24d26eb26a280bcf7d35dcf5a01cd8712343bfbe504849229d01ff4771aa4ed0d8d900083afaa01d732b5c
languageName: node
linkType: hard
@ -17582,9 +17540,9 @@ __metadata:
languageName: node
linkType: hard
"geotiff@npm:^2.0.7":
version: 2.0.7
resolution: "geotiff@npm:2.0.7"
"geotiff@npm:^2.1.3":
version: 2.1.3
resolution: "geotiff@npm:2.1.3"
dependencies:
"@petamoriken/float16": "npm:^3.4.7"
lerc: "npm:^3.0.0"
@ -17593,7 +17551,8 @@ __metadata:
quick-lru: "npm:^6.1.1"
web-worker: "npm:^1.2.0"
xml-utils: "npm:^1.0.2"
checksum: 10/7d309ef9e7fee0aef39117ef045cf35d30be91621e0df643cd86d363519907706bdc6e0f6f4dc2ebcc0ce085eab18fd5801aca9c162606cdd293c7b616070d1a
zstddec: "npm:^0.1.0"
checksum: 10/91475680b882c11e6c28b4538c8703da03a13fa1b3f8f9d456aa3b274cb71fb01f04aadb29006c3a607c20b724b936a95899ad8ef65d26a11eed59ba9f81c61f
languageName: node
linkType: hard
@ -18376,7 +18335,7 @@ __metadata:
node-forge: "npm:^1.3.1"
node-notifier: "npm:10.0.1"
nx: "npm:20.7.1"
ol: "npm:7.4.0"
ol: "npm:10.6.0"
ol-ext: "npm:4.0.33"
openapi-types: "npm:^12.1.3"
pa11y-ci: "npm:^3.1.0"
@ -19304,7 +19263,7 @@ __metadata:
languageName: node
linkType: hard
"ieee754@npm:^1.1.12, ieee754@npm:^1.1.13, ieee754@npm:^1.2.1":
"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1":
version: 1.2.1
resolution: "ieee754@npm:1.2.1"
checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4
@ -21347,13 +21306,6 @@ __metadata:
languageName: node
linkType: hard
"json-stringify-pretty-compact@npm:^2.0.0":
version: 2.0.0
resolution: "json-stringify-pretty-compact@npm:2.0.0"
checksum: 10/08f2725eef0a37bfd554aaa1ce551bc973e13f2c3dc86dd40b10ae6c08b3851b84946db672877e994e1fd1d904a578941c176609864e4f1a48b6d8f51fc2ac5f
languageName: node
linkType: hard
"json-stringify-safe@npm:^5.0.1, json-stringify-safe@npm:~5.0.1":
version: 5.0.1
resolution: "json-stringify-safe@npm:5.0.1"
@ -22413,13 +22365,6 @@ __metadata:
languageName: node
linkType: hard
"mapbox-to-css-font@npm:^2.4.1":
version: 2.4.1
resolution: "mapbox-to-css-font@npm:2.4.1"
checksum: 10/224fc0f39a2f546bbe5b69a67a4e8c54928606cac44490650ee711723747edb393b4936c0005bcc72cb12f70b31fab50078a65e3e27e0f3e7d65f1ab8a3a253b
languageName: node
linkType: hard
"marked-mangle@npm:1.1.11":
version: 1.1.11
resolution: "marked-mangle@npm:1.1.11"
@ -24164,27 +24109,16 @@ __metadata:
languageName: node
linkType: hard
"ol-mapbox-style@npm:^10.1.0":
"ol@npm:10.6.0":
version: 10.6.0
resolution: "ol-mapbox-style@npm:10.6.0"
resolution: "ol@npm:10.6.0"
dependencies:
"@mapbox/mapbox-gl-style-spec": "npm:^13.23.1"
mapbox-to-css-font: "npm:^2.4.1"
ol: "npm:^7.3.0"
checksum: 10/069421f5260b6d7b02e6f753eef5e9e10d3a86df8fe425aab1317b5e3d75684709b4b506e4c47e9840abeb503066c64a73dd29ec4a1cf65a8cbc484e91c70bfb
languageName: node
linkType: hard
"ol@npm:7.4.0, ol@npm:^7.3.0":
version: 7.4.0
resolution: "ol@npm:7.4.0"
dependencies:
earcut: "npm:^2.2.3"
geotiff: "npm:^2.0.7"
ol-mapbox-style: "npm:^10.1.0"
pbf: "npm:3.2.1"
rbush: "npm:^3.0.1"
checksum: 10/c83698fddd02fdd497e92bffee7ca7d4059f64d28a0be7a7781981f129b6636cacbee894aed331a572f066fe1e56a4cf22861c51e1efddb7fbb58c4bf5606ece
"@types/rbush": "npm:4.0.0"
earcut: "npm:^3.0.0"
geotiff: "npm:^2.1.3"
pbf: "npm:4.0.1"
rbush: "npm:^4.0.0"
checksum: 10/942fe6c0b55b24a6b1538d78ad221962476bfcba5b1316f427235d50aa817ca5ed805e8d3b5c6129b5da245ba0b66cd8bf359403230bb939648a1a78bffb0ca3
languageName: node
linkType: hard
@ -25090,15 +25024,14 @@ __metadata:
languageName: node
linkType: hard
"pbf@npm:3.2.1":
version: 3.2.1
resolution: "pbf@npm:3.2.1"
"pbf@npm:4.0.1":
version: 4.0.1
resolution: "pbf@npm:4.0.1"
dependencies:
ieee754: "npm:^1.1.12"
resolve-protobuf-schema: "npm:^2.1.0"
bin:
pbf: bin/pbf
checksum: 10/566a64424063b07f46d3e2cb2288094a60b0a45efea48fd4030f527143b0e9c611399ebd3a6fd56db51908f2006defef5e09a1b2a027db9481a59a41156a540c
checksum: 10/9e2821fec1511cdaacf205c1279a33991119a2b8469208071d22e6204162dc8a4d216f647ce92759601e3d685fa913d4739278a86aa9ac126d04a1a3ac66bf12
languageName: node
linkType: hard
@ -26222,10 +26155,10 @@ __metadata:
languageName: node
linkType: hard
"quickselect@npm:^2.0.0":
version: 2.0.0
resolution: "quickselect@npm:2.0.0"
checksum: 10/ed2e78431050d223fb75da20ee98011aef1a03f7cb04e1a32ee893402e640be3cfb76d72e9dbe01edf3bb457ff6a62e5c2d85748424d1aa531f6ba50daef098c
"quickselect@npm:^3.0.0":
version: 3.0.0
resolution: "quickselect@npm:3.0.0"
checksum: 10/8f72bedb8bb14bce5c3767c55f567bc296fa3ca9d98ba385e3867e434463bc633feee1eddf3dfec17914b7e88feeb08c7b313cf47114a8ff11bf964f77f51cfc
languageName: node
linkType: hard
@ -26321,12 +26254,12 @@ __metadata:
languageName: node
linkType: hard
"rbush@npm:^3.0.1":
version: 3.0.1
resolution: "rbush@npm:3.0.1"
"rbush@npm:^4.0.0":
version: 4.0.1
resolution: "rbush@npm:4.0.1"
dependencies:
quickselect: "npm:^2.0.0"
checksum: 10/489e2e7d9889888ad533518f194e3ab7cc19b1f1365a38ee99fbdda542a47f41cda7dc89870180050f4d04ea402e9ff294e1d767d03c0f1694e0028b7609eec9
quickselect: "npm:^3.0.0"
checksum: 10/8db2f9b464ee31cbb13237e762140c27f4de517a470d1639f840cc606b1f917f64f6273178b2f07c93d95c44cc068cc0c25c84ad40c4a483404e60d17c054eec
languageName: node
linkType: hard
@ -28334,7 +28267,7 @@ __metadata:
languageName: node
linkType: hard
"rw@npm:1, rw@npm:^1.3.3":
"rw@npm:1":
version: 1.3.3
resolution: "rw@npm:1.3.3"
checksum: 10/e90985d64777a00f4ab5f8c0bfea2fb5645c6bda5238840afa339c8a4f86f776e8ce83731155643a7425a0b27ce89077dab27b2f57519996ba4d2fe54cac1941
@ -29335,20 +29268,6 @@ __metadata:
languageName: node
linkType: hard
"sort-asc@npm:^0.1.0":
version: 0.1.0
resolution: "sort-asc@npm:0.1.0"
checksum: 10/c130223336a9431edd4c12cf0a4893eb9327591f8c9d9ccd1b27a42f7657b4bb84807d7150d79fc52ab7d1e9691684897de7ef2a6a3516d32349af68d2705d57
languageName: node
linkType: hard
"sort-desc@npm:^0.1.1":
version: 0.1.1
resolution: "sort-desc@npm:0.1.1"
checksum: 10/f2a2568f4eaf9a7315700ae9e46e74ae7b395f24323bf807f07b20269a7c162b549b0bac6d5bb8e26f7ee15af72a74fdaf09d47257227dbe31c4ac8f13b19e1d
languageName: node
linkType: hard
"sort-keys@npm:^2.0.0":
version: 2.0.0
resolution: "sort-keys@npm:2.0.0"
@ -29367,16 +29286,6 @@ __metadata:
languageName: node
linkType: hard
"sort-object@npm:^0.3.2":
version: 0.3.2
resolution: "sort-object@npm:0.3.2"
dependencies:
sort-asc: "npm:^0.1.0"
sort-desc: "npm:^0.1.1"
checksum: 10/701fac8b49e9c720f4e5f4b70e995a795771dbecab2368b2e362c5c02cc269418a061f3303c47137d22fc3fe1ded03b4fd4215edec0960c4721274d320dae2b4
languageName: node
linkType: hard
"source-list-map@npm:^2.0.0, source-list-map@npm:^2.0.1":
version: 2.0.1
resolution: "source-list-map@npm:2.0.1"
@ -33037,3 +32946,10 @@ __metadata:
checksum: 10/0e35432dcca7f053e63f5dd491a87c78abe0d981817547252c3b6d05f0f58788695d1a69724759c6501dff3fd62929be24c9f314a3625179bee889150f7a61fa
languageName: node
linkType: hard
"zstddec@npm:^0.1.0":
version: 0.1.0
resolution: "zstddec@npm:0.1.0"
checksum: 10/818d7a7b910509bed32a5ae42352dc45e87e4ee8470c27352859824540efceb9643864afbbb93564beb76822d72a946a6ee7cfbf01627ee34a1cd9656f2857b8
languageName: node
linkType: hard