From cd7ffe6c2ebdd1201d00cec5d58632ce12cf2e29 Mon Sep 17 00:00:00 2001
From: alex <48489896+devnaumov@users.noreply.github.com>
Date: Tue, 30 Jan 2024 17:22:52 +0100
Subject: [PATCH] CB-4504 transform coordinates according to the chosen srid
(#2331)
* CB-4504 transform coordinates according to the chosen srid
* CB-4504 add licence
* CB-4504 move types to dev deps
---------
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
---
.../packages/plugin-gis-viewer/package.json | 5 +-
.../plugin-gis-viewer/src/CrsInput.m.css | 10 ++
.../plugin-gis-viewer/src/CrsInput.tsx | 40 +++----
.../src/GISValuePresentation.m.css | 16 +++
.../src/GISValuePresentation.tsx | 104 +++++++++++-------
.../plugin-gis-viewer/src/LeafletMap.tsx | 23 ++--
webapp/yarn.lock | 23 ++++
7 files changed, 139 insertions(+), 82 deletions(-)
create mode 100644 webapp/packages/plugin-gis-viewer/src/CrsInput.m.css
create mode 100644 webapp/packages/plugin-gis-viewer/src/GISValuePresentation.m.css
diff --git a/webapp/packages/plugin-gis-viewer/package.json b/webapp/packages/plugin-gis-viewer/package.json
index 5a7fe7bf78..f302d6d720 100644
--- a/webapp/packages/plugin-gis-viewer/package.json
+++ b/webapp/packages/plugin-gis-viewer/package.json
@@ -26,6 +26,7 @@
"geojson": "^0.5.0",
"leaflet": "^1.9.4",
"mobx-react-lite": "^4.0.5",
+ "proj4": "^2.10.0",
"react": "^18.2.0",
"react-leaflet": "^4.2.1",
"reshadow": "^0.0.1",
@@ -37,7 +38,9 @@
"@types/react": "^18.2.42",
"@types/react-leaflet": "~3.0.0",
"@types/wellknown": "~0.5.8",
+ "@types/proj4": "^2.5.5",
"leaflet": "^1.9.4",
- "typescript": "^5.3.2"
+ "typescript": "^5.3.2",
+ "typescript-plugin-css-modules": "^5.0.2"
}
}
diff --git a/webapp/packages/plugin-gis-viewer/src/CrsInput.m.css b/webapp/packages/plugin-gis-viewer/src/CrsInput.m.css
new file mode 100644
index 0000000000..be2fb54a9d
--- /dev/null
+++ b/webapp/packages/plugin-gis-viewer/src/CrsInput.m.css
@@ -0,0 +1,10 @@
+.root {
+ display: inline-flex;
+ align-items: center;
+ font-size: 12px;
+}
+
+.combobox {
+ width: 120px;
+ flex: 0 0 auto;
+}
diff --git a/webapp/packages/plugin-gis-viewer/src/CrsInput.tsx b/webapp/packages/plugin-gis-viewer/src/CrsInput.tsx
index 9ea822cff5..999f23b088 100644
--- a/webapp/packages/plugin-gis-viewer/src/CrsInput.tsx
+++ b/webapp/packages/plugin-gis-viewer/src/CrsInput.tsx
@@ -1,40 +1,26 @@
-import styled, { css } from 'reshadow';
-
+/*
+ * CloudBeaver - Cloud Database Manager
+ * Copyright (C) 2020-2024 DBeaver Corp and others
+ *
+ * Licensed under the Apache License, Version 2.0.
+ * you may not use this file except in compliance with the License.
+ */
import { Combobox } from '@cloudbeaver/core-blocks';
+import classes from './CrsInput.m.css';
import type { CrsKey } from './LeafletMap';
-const styles = css`
- root {
- display: inline-flex;
- align-items: center;
- font-size: 12px;
- }
-
- label {
- margin-right: 4px;
- flex-grow: 0;
- flex-shrink: 1;
- }
-
- Combobox {
- width: 120px;
- flex: 0 0 auto;
- }
-`;
-
interface Props {
value: CrsKey;
onChange: (value: CrsKey) => void;
}
-const items: CrsKey[] = ['Simple', 'EPSG3395', 'EPSG3857', 'EPSG4326', 'EPSG900913'];
+const items: CrsKey[] = ['Simple', 'EPSG:3395', 'EPSG:3857', 'EPSG:4326', 'EPSG:900913'];
export function CrsInput(props: Props) {
- return styled(styles)(
-
-
-
- ,
+ return (
+
+
+
);
}
diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.m.css b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.m.css
new file mode 100644
index 0000000000..383c3893fa
--- /dev/null
+++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.m.css
@@ -0,0 +1,16 @@
+.root {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.map {
+ flex: 1 1 auto;
+ border-radius: var(--theme-group-element-radius);
+ overflow: hidden;
+}
+
+.toolbar {
+ margin-top: 8px;
+ flex: 0 0 auto;
+}
diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx
index 6095e47457..09968cf155 100644
--- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx
+++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx
@@ -6,9 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
-import { useCallback, useMemo, useState } from 'react';
-import styled, { css } from 'reshadow';
-import wellknown from 'wellknown';
+import proj4 from 'proj4';
+import { useCallback, useState } from 'react';
+import wellknown, { GeoJSONGeometry } from 'wellknown';
import { TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
import {
@@ -21,42 +21,56 @@ import {
} from '@cloudbeaver/plugin-data-viewer';
import { CrsInput } from './CrsInput';
+import classes from './GISValuePresentation.m.css';
import { CrsKey, IAssociatedValue, IGeoJSONFeature, LeafletMap } from './LeafletMap';
import { ResultSetGISAction } from './ResultSetGISAction';
-function getCrsKey(feature?: IGeoJSONFeature): CrsKey {
- switch (feature?.properties.srid) {
+proj4.defs('EPSG:3395', '+title=World Mercator +proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
+
+function getCrsKey(srid: number): CrsKey {
+ switch (srid) {
case 3857:
- return 'EPSG3857';
+ return 'EPSG:3857';
case 4326:
- return 'EPSG4326';
+ return 'EPSG:4326';
case 3395:
- return 'EPSG3395';
+ return 'EPSG:3395';
case 900913:
- return 'EPSG900913';
+ return 'EPSG:900913';
default:
- return 'EPSG3857';
+ return 'EPSG:4326';
}
}
-const styles = css`
- root {
- display: flex;
- flex-direction: column;
- width: 100%;
+const DEFAULT_CRS = 'EPSG:3857';
+const DEFAULT_TRANSFORM_CRS = 'EPSG:4326';
+
+function getTransformedGeometry(from: CrsKey, to: CrsKey, geometry: GeoJSONGeometry): GeoJSONGeometry {
+ if (geometry.type === 'Point') {
+ return { ...geometry, coordinates: proj4(from, to, geometry.coordinates) };
+ }
+
+ if (geometry.type === 'MultiPoint' || geometry.type === 'LineString') {
+ return { ...geometry, coordinates: geometry.coordinates.map(point => proj4(from, to, point)) };
}
- map {
- flex: 1 1 auto;
- border-radius: var(--theme-group-element-radius);
- overflow: hidden;
+ if (geometry.type === 'MultiLineString' || geometry.type === 'Polygon') {
+ return { ...geometry, coordinates: geometry.coordinates.map(line => line.map(point => proj4(from, to, point))) };
}
- toolbar {
- margin-top: 8px;
- flex: 0 0 auto;
+ if (geometry.type === 'MultiPolygon') {
+ return {
+ ...geometry,
+ coordinates: geometry.coordinates.map(polygon => polygon.map(line => line.map(point => proj4(from, to, point)))),
+ };
}
-`;
+
+ if (geometry.type === 'GeometryCollection') {
+ return { ...geometry, geometries: geometry.geometries.map(geometry => getTransformedGeometry(from, to, geometry)) };
+ }
+
+ return geometry;
+}
interface Props {
model: IDatabaseDataModel;
@@ -70,9 +84,15 @@ export const GISValuePresentation = observer(function GISValuePresentatio
const gis = model.source.getAction(resultIndex, ResultSetGISAction);
const view = model.source.getAction(resultIndex, ResultSetViewAction);
+ const parsedGISData: IGeoJSONFeature[] = [];
const activeElements = selection.getActiveElements();
+ const firstActiveElement = activeElements[0];
+ const firstActiveCell = firstActiveElement ? gis.getCellValue(firstActiveElement) : null;
+ const initialCrs: CrsKey = firstActiveCell?.srid ? getCrsKey(firstActiveCell.srid) : DEFAULT_CRS;
- const parsedGISData: IGeoJSONFeature[] = [];
+ const [crs, setCrs] = useState(null);
+
+ const currentCrs = crs ?? initialCrs;
for (const cell of activeElements) {
const cellValue = gis.getCellValue(cell);
@@ -81,15 +101,24 @@ export const GISValuePresentation = observer(function GISValuePresentatio
continue;
}
+ const text = cellValue.mapText || cellValue.text;
+
try {
- const parsedCellValue = wellknown.parse(cellValue.mapText || cellValue.text);
+ const parsedCellValue = wellknown.parse(text);
+
if (!parsedCellValue) {
continue;
}
- parsedGISData.push({ type: 'Feature', geometry: parsedCellValue, properties: { associatedCell: cell, srid: cellValue.srid } });
+ const from = cellValue.srid === 0 ? DEFAULT_TRANSFORM_CRS : getCrsKey(cellValue.srid);
+
+ parsedGISData.push({
+ type: 'Feature',
+ geometry: currentCrs === 'Simple' ? parsedCellValue : getTransformedGeometry(from, currentCrs, parsedCellValue),
+ properties: { associatedCell: cell, srid: cellValue.srid },
+ });
} catch (exception: any) {
- console.error(`Failed to parse "${cellValue.mapText || cellValue.text}" value.`);
+ console.error(`Failed to parse "${text}" value.`);
console.error(exception);
}
}
@@ -119,21 +148,18 @@ export const GISValuePresentation = observer(function GISValuePresentatio
[view],
);
- const defaultCrsKey = getCrsKey(parsedGISData[0]);
- const [crsKey, setCrsKey] = useState(defaultCrsKey);
-
if (!parsedGISData.length) {
return {translate('gis_presentation_placeholder')};
}
- return styled(styles)(
-
-
-
-
-
- ,
+ return (
+
);
});
diff --git a/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx b/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx
index 8afb863afd..ea33764470 100644
--- a/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx
+++ b/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx
@@ -10,8 +10,7 @@
import type geojson from 'geojson';
import leaflet from 'leaflet';
import { useCallback, useEffect, useState } from 'react';
-import { GeoJSON, LayersControl, MapContainer, TileLayer } from 'react-leaflet';
-import type { TileLayerProps } from 'react-leaflet';
+import { GeoJSON, LayersControl, MapContainer, TileLayer, type TileLayerProps } from 'react-leaflet';
import styled, { css } from 'reshadow';
import { useSplit, useTranslate } from '@cloudbeaver/core-blocks';
@@ -39,7 +38,7 @@ interface IBaseTile extends TileLayerProps {
checked?: boolean;
}
-export type CrsKey = 'Simple' | 'EPSG3857' | 'EPSG4326' | 'EPSG3395' | 'EPSG900913';
+export type CrsKey = 'Simple' | 'EPSG:3857' | 'EPSG:4326' | 'EPSG:3395' | 'EPSG:900913';
interface Props {
geoJSON: IGeoJSONFeature[];
@@ -92,13 +91,13 @@ function getCRS(crsKey: CrsKey): leaflet.CRS {
switch (crsKey) {
case 'Simple':
return leaflet.CRS.Simple;
- case 'EPSG3857':
+ case 'EPSG:3857':
return leaflet.CRS.EPSG3857;
- case 'EPSG4326':
+ case 'EPSG:4326':
return leaflet.CRS.EPSG4326;
- case 'EPSG3395':
+ case 'EPSG:3395':
return leaflet.CRS.EPSG3395;
- case 'EPSG900913':
+ case 'EPSG:900913':
return leaflet.CRS.EPSG900913;
default:
return leaflet.CRS.EPSG3857;
@@ -175,20 +174,14 @@ export const LeafletMap: React.FC = function LeafletMap({ geoJSON, crsKey
useEffect(() => {
if (mapRef) {
mapRef.invalidateSize();
-
- if (mapRef.options.crs?.code !== crs.code) {
- const center = mapRef.getCenter();
- mapRef.options.crs = crs;
- mapRef.setView(center);
- }
}
- }, [split.state.isResizing, split.state.mode, crs, mapRef]);
+ }, [split.state.isResizing, split.state.mode, mapRef]);
return styled(
styles,
baseStyles,
)(
-
+