diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index c4c0c19f2..583584bf9 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -28,9 +28,16 @@ import { } from '@jupytergis/schema'; import { IObservableMap, ObservableMap } from '@jupyterlab/observables'; import { User } from '@jupyterlab/services'; +import { CommandRegistry } from '@lumino/commands'; import { JSONValue, UUID } from '@lumino/coreutils'; +import { ContextMenu } from '@lumino/widgets'; import { Collection, MapBrowserEvent, Map as OlMap, View, getUid } from 'ol'; +//@ts-expect-error no types for ol-pmtiles +import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles'; +import { FeatureLike } from 'ol/Feature'; import { ScaleLine } from 'ol/control'; +import { Coordinate } from 'ol/coordinate'; +import { singleClick } from 'ol/events/condition'; import { GeoJSON, MVT } from 'ol/format'; import { DragAndDrop, Select } from 'ol/interaction'; import { @@ -47,6 +54,8 @@ import { toLonLat, transformExtent } from 'ol/proj'; +import { get as getProjection } from 'ol/proj.js'; +import { register } from 'ol/proj/proj4.js'; import Feature from 'ol/render/Feature'; import { GeoTIFF as GeoTIFFSource, @@ -55,32 +64,28 @@ import { VectorTile as VectorTileSource, XYZ as XYZSource } from 'ol/source'; +import ImageSource from 'ol/source/Image'; import Static from 'ol/source/ImageStatic'; -//@ts-expect-error no types for ol-pmtiles -import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles'; -import { register } from 'ol/proj/proj4.js'; -import { get as getProjection } from 'ol/proj.js'; +import TileSource from 'ol/source/Tile'; +import { Circle, Fill, Stroke, Style } from 'ol/style'; import { Rule } from 'ol/style/flat'; import proj4 from 'proj4'; -import * as React from 'react'; -import { isLightTheme, loadGeoTIFFWithCache, throttle } from '../tools'; -import { MainViewModel } from './mainviewmodel'; -import { Spinner } from './spinner'; -//@ts-expect-error no types for proj4-list +//@ts-expect-error no types for proj4list import proj4list from 'proj4-list'; -import { ContextMenu } from '@lumino/widgets'; -import { CommandRegistry } from '@lumino/commands'; -import { Coordinate } from 'ol/coordinate'; +import * as React from 'react'; import AnnotationFloater from '../annotations/components/AnnotationFloater'; import { CommandIDs } from '../constants'; -import { FollowIndicator } from './FollowIndicator'; +import StatusBar from '../statusbar/StatusBar'; +import { + isLightTheme, + loadFile, + loadGeoTIFFWithCache, + throttle +} from '../tools'; import CollaboratorPointers, { ClientPointer } from './CollaboratorPointers'; -import { loadFile } from '../tools'; -import { Circle, Fill, Stroke, Style } from 'ol/style'; -import { singleClick } from 'ol/events/condition'; -import TileSource from 'ol/source/Tile'; -import { FeatureLike } from 'ol/Feature'; -import ImageSource from 'ol/source/Image'; +import { FollowIndicator } from './FollowIndicator'; +import { MainViewModel } from './mainviewmodel'; +import { Spinner } from './spinner'; interface IProps { viewModel: MainViewModel; @@ -95,6 +100,9 @@ interface IStates { firstLoad: boolean; annotations: IDict; clientPointers: IDict; + viewProjection: { code: string; units: string }; + loadingLayer: boolean; + scale: number; } export class MainView extends React.Component { @@ -131,7 +139,10 @@ export class MainView extends React.Component { loading: true, firstLoad: true, annotations: {}, - clientPointers: {} + clientPointers: {}, + viewProjection: { code: '', units: '' }, + loadingLayer: false, + scale: 0 }; this._sources = []; @@ -269,6 +280,7 @@ export class MainView extends React.Component { const projection = view.getProjection(); const latLng = toLonLat(center, projection); const bearing = view.getRotation(); + const resolution = view.getResolution(); const updatedOptions: Partial = { latitude: latLng[1], @@ -284,6 +296,19 @@ export class MainView extends React.Component { ...currentOptions, ...updatedOptions }); + + // Calculate scale + if (resolution) { + // DPI and inches per meter values taken from OpenLayers + const dpi = 25.4 / 0.28; + const inchesPerMeter = 1000 / 25.4; + const scale = resolution * inchesPerMeter * dpi; + + this.setState(old => ({ + ...old, + scale + })); + } }); this._Map.on('click', this._identifyFeature.bind(this)); @@ -309,7 +334,14 @@ export class MainView extends React.Component { this._contextMenu.open(event); }); - this.setState(old => ({ ...old, loading: false })); + this.setState(old => ({ + ...old, + loading: false, + viewProjection: { + code: view.getProjection().getCode(), + units: view.getProjection().getUnits() + } + })); } } @@ -759,6 +791,9 @@ export class MainView extends React.Component { return; } + this.setState(old => ({ ...old, loadingLayer: true })); + this._loadingLayers.add(id); + if (!this._sources[sourceId]) { await this.addSource(sourceId, source, id); } @@ -1108,6 +1143,7 @@ export class MainView extends React.Component { return new Promise(resolve => { const checkReady = () => { if (this._loadingLayers.size === 0) { + this.setState(old => ({ ...old, loadingLayer: false })); resolve(); } else { setTimeout(checkReady, 50); @@ -1685,6 +1721,12 @@ export class MainView extends React.Component { }} /> + ); } diff --git a/packages/base/src/statusbar/StatusBar.tsx b/packages/base/src/statusbar/StatusBar.tsx new file mode 100644 index 000000000..7b57969fb --- /dev/null +++ b/packages/base/src/statusbar/StatusBar.tsx @@ -0,0 +1,74 @@ +import { + faGlobe, + faLocationDot, + faRuler +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Progress } from '@jupyter/react-components'; +import { IJupyterGISModel, JgisCoordinates } from '@jupytergis/schema'; +import React, { useEffect, useState } from 'react'; +import { version } from '../../package.json'; // Adjust the path as necessary + +interface IStatusBarProps { + jgisModel: IJupyterGISModel; + loading?: boolean; + projection?: { code: string; units: string }; + scale: number; +} +const StatusBar = ({ + jgisModel, + loading, + projection, + scale +}: IStatusBarProps) => { + const [coords, setCoords] = useState({ x: 0, y: 0 }); + + useEffect(() => { + const handleClientStateChanged = () => { + const pointer = jgisModel?.localState?.pointer?.value; + + if (!pointer) { + return; + } + + setCoords({ x: pointer?.coordinates.x, y: pointer?.coordinates.y }); + }; + + jgisModel.clientStateChanged.connect(handleClientStateChanged); + + return () => { + jgisModel.clientStateChanged.disconnect(handleClientStateChanged); + }; + }, [jgisModel]); + + return ( +
+ {loading && ( +
+ +
+ )} +
+ jgis: {version} +
+
+ + + {' '} + x: {Math.trunc(coords.x)} y: {Math.trunc(coords.y)} + +
+
+ {' '} + Scale: 1: {Math.trunc(scale)} +
+
+ {' '} + {projection?.code ?? null} +
+
Units: {projection?.units}
+
+ ); +}; + +export default StatusBar; diff --git a/packages/base/style/base.css b/packages/base/style/base.css index 7a7f466cd..d3d559d83 100644 --- a/packages/base/style/base.css +++ b/packages/base/style/base.css @@ -8,6 +8,7 @@ @import url('./leftPanel.css'); @import url('./filterPanel.css'); @import url('./symbologyDialog.css'); +@import url('./statusBar.css'); @import url('ol/ol.css'); .jGIS-Toolbar-GroupName { diff --git a/packages/base/style/statusBar.css b/packages/base/style/statusBar.css new file mode 100644 index 000000000..9ac1ee459 --- /dev/null +++ b/packages/base/style/statusBar.css @@ -0,0 +1,16 @@ +.jgis-status-bar { + display: flex; + justify-content: space-around; + align-items: center; + height: 16px; + background-color: var(--jp-layout-color1); + font-size: var(--jp-ui-font-size0); +} + +.jgis-status-bar-item { + flex: 0 0 content; +} + +.jgis-status-bar-coords { + min-width: 160px; +} diff --git a/python/jupytergis_lab/style/base.css b/python/jupytergis_lab/style/base.css index 08953395e..457dc85ac 100644 --- a/python/jupytergis_lab/style/base.css +++ b/python/jupytergis_lab/style/base.css @@ -214,7 +214,7 @@ div.jGIS-toolbar-widget > div.jp-Toolbar-item:last-child { .jGIS-Mainview { width: 100%; - height: 100%; + height: calc(100% - 16px); box-sizing: border-box; } diff --git a/ui-tests/tests/filters.spec.ts-snapshots/no-filter-linux.png b/ui-tests/tests/filters.spec.ts-snapshots/no-filter-linux.png index e75be76fc..b11b2078d 100644 Binary files a/ui-tests/tests/filters.spec.ts-snapshots/no-filter-linux.png and b/ui-tests/tests/filters.spec.ts-snapshots/no-filter-linux.png differ diff --git a/ui-tests/tests/filters.spec.ts-snapshots/one-filter-linux.png b/ui-tests/tests/filters.spec.ts-snapshots/one-filter-linux.png index 5d8b7f8db..78fd3d332 100644 Binary files a/ui-tests/tests/filters.spec.ts-snapshots/one-filter-linux.png and b/ui-tests/tests/filters.spec.ts-snapshots/one-filter-linux.png differ diff --git a/ui-tests/tests/filters.spec.ts-snapshots/two-filter-linux.png b/ui-tests/tests/filters.spec.ts-snapshots/two-filter-linux.png index 272012598..cc245390a 100644 Binary files a/ui-tests/tests/filters.spec.ts-snapshots/two-filter-linux.png and b/ui-tests/tests/filters.spec.ts-snapshots/two-filter-linux.png differ diff --git a/ui-tests/tests/geojson-layers.spec.ts-snapshots/geoJSON-layer-linux.png b/ui-tests/tests/geojson-layers.spec.ts-snapshots/geoJSON-layer-linux.png index d2936c024..30859617f 100644 Binary files a/ui-tests/tests/geojson-layers.spec.ts-snapshots/geoJSON-layer-linux.png and b/ui-tests/tests/geojson-layers.spec.ts-snapshots/geoJSON-layer-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-linux.png index 497995a2b..3da128996 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-0-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-linux.png index 443c54942..ff5e2f39b 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-1-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-linux.png index 16c01f01c..93048cdfe 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/dark-Notebook-ipynb-cell-2-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-linux.png index 35557b388..c3c2a58f6 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-0-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-linux.png index 49031e036..b7a1fe5b8 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-1-linux.png differ diff --git a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-linux.png b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-linux.png index 34af919bf..90523bd62 100644 Binary files a/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-linux.png and b/ui-tests/tests/notebook.spec.ts-snapshots/light-Notebook-ipynb-cell-2-linux.png differ