-
Notifications
You must be signed in to change notification settings - Fork 399
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
Changes from 2 commits
4d960b0
ebf7388
5ec535b
9f0784b
4841391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} |
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> | ||
); | ||
} |
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>; | ||
|
@@ -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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no magic numbers also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this one can be moved to |
||
|
||
const parsedGISData: IGeoJSONFeature[] = []; | ||
const [crs, setCrs] = useState<CrsKey | null>(null); | ||
|
||
const currentCrs = crs ?? initialCrs; | ||
|
||
for (const cell of activeElements) { | ||
const cellValue = gis.getCellValue(cell); | ||
|
@@ -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); | ||
} | ||
} | ||
|
@@ -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> | ||
); | ||
}); |
There was a problem hiding this comment.
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