Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CB-4504 transform coordinates according to the chosen srid #2331

Merged
merged 5 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion webapp/packages/plugin-gis-viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-ui": "~0.1.0",
"@cloudbeaver/plugin-data-viewer": "~0.1.0",
"@types/proj4": "^2.5.5",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move types to devDependencies

"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",
Expand All @@ -38,6 +40,7 @@
"@types/react-leaflet": "~3.0.0",
"@types/wellknown": "~0.5.8",
"leaflet": "^1.9.4",
"typescript": "^5.3.2"
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
10 changes: 10 additions & 0 deletions webapp/packages/plugin-gis-viewer/src/CrsInput.m.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.root {
display: inline-flex;
align-items: center;
font-size: 12px;
}

.combobox {
width: 120px;
flex: 0 0 auto;
}
40 changes: 13 additions & 27 deletions webapp/packages/plugin-gis-viewer/src/CrsInput.tsx
Original file line number Diff line number Diff line change
@@ -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)(
<root>
<label>CRS:</label>
<Combobox items={items} value={props.value} onSelect={props.onChange} />
</root>,
return (
<div className={classes.root}>
<Combobox className={classes.combobox} items={items} value={props.value} onSelect={props.onChange} />
</div>
);
}
16 changes: 16 additions & 0 deletions webapp/packages/plugin-gis-viewer/src/GISValuePresentation.m.css
Original file line number Diff line number Diff line change
@@ -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;
}
101 changes: 62 additions & 39 deletions webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -21,42 +21,53 @@ 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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont like this alien symbols. Lets move it to consts with meaning name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-01-30 at 11 01 40 AM it's code and you can google it, i don't agree that we need to move it to const (it will be looking like `const EPSG3857 = 'EPSG:3857'`)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moreover number we get from server and it can by typed with gql enum but it seems no sense for it (again it's not a random number it's code)

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%;
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<any, IDatabaseResultSet>;
Expand All @@ -70,9 +81,15 @@ export const GISValuePresentation = observer<Props>(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) : 'EPSG:3857';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no magic numbers also

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one can be moved to const DEFAULT_CRS = ...


const parsedGISData: IGeoJSONFeature[] = [];
const [crs, setCrs] = useState<CrsKey | null>(null);

const currentCrs = crs ?? initialCrs;

for (const cell of activeElements) {
const cellValue = gis.getCellValue(cell);
Expand All @@ -81,15 +98,24 @@ export const GISValuePresentation = observer<Props>(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 ? 'EPSG:4326' : 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);
}
}
Expand Down Expand Up @@ -119,21 +145,18 @@ export const GISValuePresentation = observer<Props>(function GISValuePresentatio
[view],
);

const defaultCrsKey = getCrsKey(parsedGISData[0]);
const [crsKey, setCrsKey] = useState(defaultCrsKey);

if (!parsedGISData.length) {
return <TextPlaceholder>{translate('gis_presentation_placeholder')}</TextPlaceholder>;
}

return styled(styles)(
<root>
<map>
<LeafletMap key={crsKey} geoJSON={parsedGISData} crsKey={crsKey} getAssociatedValues={getAssociatedValues} />
</map>
<toolbar>
<CrsInput value={crsKey} onChange={setCrsKey} />
</toolbar>
</root>,
return (
<div className={classes.root}>
<div className={classes.map}>
<LeafletMap key={currentCrs} geoJSON={parsedGISData} crsKey={currentCrs} getAssociatedValues={getAssociatedValues} />
</div>
<div className={classes.toolbar}>
<CrsInput value={currentCrs} onChange={setCrs} />
</div>
</div>
);
});
23 changes: 8 additions & 15 deletions webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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[];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -175,20 +174,14 @@ export const LeafletMap: React.FC<Props> = 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,
)(
<MapContainer ref={setMapRef} crs={crs} zoom={12}>
<MapContainer ref={setMapRef} crs={leaflet.CRS.EPSG3857} zoom={12}>
<GeoJSON
// data is not optional property, see react-leaflet.d.ts
// data={[]}
Expand Down
Loading