Skip to content

Commit

Permalink
Follow a collaborator's viewport (geojupyter#257)
Browse files Browse the repository at this point in the history
* Start implementing follow

* Follow works

* Add zoom and stuff

* Add throttle and animation

* Update annotation type

* Remove animate from center update

* Try zoom first

* Update test maps

* Update Playwright Snapshots

* Fix latitude bug

* Update Playwright Snapshots

* Rename center

* User helper method to move view

* Implement suggestions

* Check for view.animate

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
gjmooney and github-actions[bot] authored Dec 20, 2024
1 parent 3bfd970 commit 9a5ec4d
Show file tree
Hide file tree
Showing 20 changed files with 226 additions and 116 deletions.
21 changes: 21 additions & 0 deletions packages/base/src/mainview/FollowIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { User } from '@jupyterlab/services';

interface IFollowIndicatorProps {
remoteUser: User.IIdentity | null | undefined;
}

export function FollowIndicator({ remoteUser }: IFollowIndicatorProps) {
return remoteUser?.display_name ? (
<div
style={{
position: 'absolute',
top: 1,
right: 3,
background: remoteUser.color
}}
>
{`Following ${remoteUser.display_name}`}
</div>
) : null;
}
90 changes: 81 additions & 9 deletions packages/base/src/mainview/mainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import { Rule } from 'ol/style/flat';
import proj4 from 'proj4';
import * as React from 'react';
import shp from 'shpjs';
import { isLightTheme, loadGeoTIFFWithCache } from '../tools';
import { isLightTheme, loadGeoTIFFWithCache, throttle } from '../tools';
import { MainViewModel } from './mainviewmodel';
import { Spinner } from './spinner';
//@ts-expect-error no types for proj4-list
Expand All @@ -70,6 +70,7 @@ import { CommandRegistry } from '@lumino/commands';
import { Coordinate } from 'ol/coordinate';
import AnnotationFloater from '../annotations/components/AnnotationFloater';
import { CommandIDs } from '../constants';
import { FollowIndicator } from './FollowIndicator';

interface IProps {
viewModel: MainViewModel;
Expand Down Expand Up @@ -212,6 +213,29 @@ export class MainView extends React.Component<IProps, IStates> {

this._Map.addInteraction(dragAndDropInteraction);

const view = this._Map.getView();

// TODO: Note for the future, will need to update listeners if view changes
view.on(
'change:center',
throttle(() => {
// Not syncing center if following someone else
if (this._model.localState?.remoteUser) {
return;
}
const view = this._Map.getView();
const center = view.getCenter();
const zoom = view.getZoom();
if (!center || !zoom) {
return;
}
this._model.syncViewport(
{ coordinates: { x: center[0], y: center[1] }, zoom },
this._mainViewModel.id
);
})
);

this._Map.on('postrender', () => {
if (this.state.annotations) {
this._updateAnnotation();
Expand Down Expand Up @@ -278,7 +302,7 @@ export class MainView extends React.Component<IProps, IStates> {
}

this._mainViewModel.addAnnotation({
position: [this._clickCoords[0], this._clickCoords[1]],
position: { x: this._clickCoords[0], y: this._clickCoords[1] },
zoom: this._Map.getView().getZoom() ?? 0,
label: 'New annotation',
contents: [],
Expand Down Expand Up @@ -914,7 +938,37 @@ export class MainView extends React.Component<IProps, IStates> {
sender: IJupyterGISModel,
clients: Map<number, IJupyterGISClientState>
): void => {
// TODO SOMETHING
const remoteUser = this._model.localState?.remoteUser;
// If we are in following mode, we update our position and selection
if (remoteUser) {
const remoteState = clients.get(remoteUser);
if (!remoteState) {
return;
}

if (remoteState.user?.username !== this.state.remoteUser?.username) {
this.setState(old => ({ ...old, remoteUser: remoteState.user }));
}

const remoteViewport = remoteState.viewportState;

if (remoteViewport.value) {
const { x, y } = remoteViewport.value.coordinates;
const zoom = remoteViewport.value.zoom;

this._moveToPosition({ x, y }, zoom, 0);
}
} else {
// If we are unfollowing a remote user, we reset our center and zoom to their previous values
if (this.state.remoteUser !== null) {
this.setState(old => ({ ...old, remoteUser: null }));
const viewportState = this._model.localState?.viewportState?.value;

if (viewportState) {
this._moveToPosition(viewportState.coordinates, viewportState.zoom);
}
}
}
};

private _onSharedOptionsChanged(
Expand Down Expand Up @@ -963,8 +1017,8 @@ export class MainView extends React.Component<IProps, IStates> {
[longitude || 0, latitude || 0],
view.getProjection()
);
view.setCenter(centerCoord);
view.setZoom(zoom || 0);

this._moveToPosition({ x: centerCoord[0], y: centerCoord[1] }, zoom || 0);

// Save the extent if it does not exists, to allow proper export to qgis.
if (!options.extent) {
Expand Down Expand Up @@ -1111,7 +1165,9 @@ export class MainView extends React.Component<IProps, IStates> {
};

private _computeAnnotationPosition(annotation: IAnnotation) {
const pixels = this._Map.getPixelFromCoordinate(annotation.position);
const { x, y } = annotation.position;
const pixels = this._Map.getPixelFromCoordinate([x, y]);

if (pixels) {
return { x: pixels[0], y: pixels[1] };
}
Expand All @@ -1136,9 +1192,24 @@ export class MainView extends React.Component<IProps, IStates> {
private _onZoomToAnnotation(_: IJupyterGISModel, id: string) {
const annotation = this._model.annotationModel?.getAnnotation(id);
if (annotation) {
const view = this._Map.getView();
view.animate({ center: annotation.position });
view.animate({ zoom: annotation.zoom });
this._moveToPosition(annotation.position, annotation.zoom);
}
}

private _moveToPosition(
center: { x: number; y: number },
zoom: number,
duration = 1000
) {
const view = this._Map.getView();

// Zoom needs to be set before changing center
if (!view.animate === undefined) {
view.animate({ zoom, duration });
view.animate({ center: [center.x, center.y], duration });
} else {
view.setZoom(zoom);
view.setCenter([center.x, center.y]);
}
}

Expand Down Expand Up @@ -1191,6 +1262,7 @@ export class MainView extends React.Component<IProps, IStates> {
}}
>
<Spinner loading={this.state.loading} />
<FollowIndicator remoteUser={this.state.remoteUser} />

<div
ref={this.divRef}
Expand Down
10 changes: 9 additions & 1 deletion packages/schema/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ import { IRasterSource } from './_interface/rastersource';

export { IGeoJSONSource } from './_interface/geojsonsource';

export type JgisCoordinates = { x: number; y: number };

export interface IViewPortState {
coordinates: JgisCoordinates;
zoom: number;
}
export interface IDict<T = any> {
[key: string]: T;
}
Expand Down Expand Up @@ -61,6 +67,7 @@ export interface ISelection {

export interface IJupyterGISClientState {
selected: { value?: { [key: string]: ISelection }; emitter?: string | null };
viewportState: { value?: IViewPortState; emitter?: string | null };
user: User.IIdentity;
remoteUser?: number;
toolbarForm?: IDict;
Expand Down Expand Up @@ -184,6 +191,7 @@ export interface IJupyterGISModel extends DocumentRegistry.IModel {
group: IJGISLayerGroup
): void;

syncViewport(viewport?: IViewPortState, emitter?: string): void;
syncSelected(value: { [key: string]: ISelection }, emitter?: string): void;
setUserToFollow(userId?: number): void;

Expand Down Expand Up @@ -291,7 +299,7 @@ export interface IAnnotationContent {

export interface IAnnotation {
label: string;
position: [number, number];
position: { x: number; y: number };
zoom: number;
contents: IAnnotationContent[];
parent: string;
Expand Down
8 changes: 8 additions & 0 deletions packages/schema/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from './_interface/jgis';
import { JupyterGISDoc } from './doc';
import {
IViewPortState,
IAnnotationModel,
IJGISLayerDocChange,
IJGISLayerTreeDocChange,
Expand Down Expand Up @@ -385,6 +386,13 @@ export class JupyterGISModel implements IJupyterGISModel {
return this._sharedModel.options;
}

syncViewport(viewport?: IViewPortState, emitter?: string): void {
this.sharedModel.awareness.setLocalStateField('viewportState', {
value: viewport,
emitter: emitter
});
}

syncSelected(value: { [key: string]: ISelection }, emitter?: string): void {
this.sharedModel.awareness.setLocalStateField('selected', {
value,
Expand Down
Binary file modified ui-tests/tests/filters.spec.ts-snapshots/no-filter-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/tests/filters.spec.ts-snapshots/one-filter-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/tests/filters.spec.ts-snapshots/two-filter-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions ui-tests/tests/gis-files/annotation-test.jGIS
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
"options": {
"bearing": 0.0,
"extent": [
-8642178.807421211,
-8155195.45664244,
-2787722.8057503784,
4272120.4882962
-12529065.804867502,
-8100808.6250225585,
-1152549.6551005533,
4369397.107474109
],
"latitude": -17.177848249713165,
"longitude": -51.338276575472065,
"latitude": -16.525897254029502,
"longitude": -61.452021395676205,
"pitch": 0.0,
"projection": "EPSG:3857",
"zoom": 3.867853235695746
Expand Down
10 changes: 8 additions & 2 deletions ui-tests/tests/gis-files/context-test.jGIS
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@
"metadata": {},
"options": {
"bearing": 0.0,
"latitude": 45.83799886047626,
"longitude": 1.195999428191726,
"extent": [
-1909126.715198027,
-5574454.49955133,
5591554.776288193,
5221699.676931929
],
"latitude": -1.5842233438045952,
"longitude": 16.539907049739554,
"pitch": 0.0,
"projection": "EPSG:3857",
"zoom": 4.075821017792898
Expand Down
12 changes: 9 additions & 3 deletions ui-tests/tests/gis-files/empty-france.jGIS
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
"metadata": {},
"options": {
"bearing": 0.0,
"latitude": 44.75868528451835,
"longitude": 3.12113766286636,
"extent": [
-422627.9251972026,
4287342.868767221,
949302.59590026,
7214295.187086671
],
"latitude": 45.81541815112149,
"longitude": 2.365599532255862,
"projection": "EPSG:3857",
"zoom": 4.920920690595499
"zoom": 5.96382081801938
},
"sources": {}
}
12 changes: 9 additions & 3 deletions ui-tests/tests/gis-files/filter-test.jGIS
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@
"metadata": {},
"options": {
"bearing": 0.0,
"latitude": 36.721052981505736,
"longitude": -117.6511671190405,
"extent": [
21768252.45439709,
-3237351.1324603865,
29402510.7737623,
13049997.055289526
],
"latitude": 40.27731369212111,
"longitude": -130.1626064604162,
"pitch": 0.0,
"projection": "EPSG:3857",
"zoom": 3.9435402350822097
"zoom": 3.48754023508221
},
"sources": {
"5fe556bc-9938-4217-8de6-fe25aef088c2": {
Expand Down
14 changes: 7 additions & 7 deletions ui-tests/tests/gis-files/france-hiking.jGIS
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@
"options": {
"bearing": 0.0,
"extent": [
-6333521.573498526,
1963702.5461316048,
6691580.962481095,
9798045.481679924
-656751.8709646468,
2664148.7072792556,
1682704.604042496,
7655274.313007474
],
"latitude": 46.623742146769416,
"longitude": 1.6082511087276998,
"latitude": 41.9915349847239,
"longitude": 4.608145104540139,
"pitch": 0.0,
"projection": "EPSG:3857",
"zoom": 4.947275971927249
"zoom": 5.19385485076487
},
"sources": {
"52252f5d-3cb7-45a8-a724-5793bf9950ec": {
Expand Down
12 changes: 9 additions & 3 deletions ui-tests/tests/gis-files/panel-test.jGIS
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@
"metadata": {},
"options": {
"bearing": 0.0,
"latitude": 45.83799886047626,
"longitude": 1.195999428191726,
"extent": [
6623681.549062506,
-5840630.94459963,
14416544.791436221,
10785093.742512114
],
"latitude": 21.67234413331572,
"longitude": 94.50378451502075,
"pitch": 0.0,
"projection": "EPSG:3857",
"zoom": 4.075821017792898
"zoom": 3.457874740154017
},
"sources": {
"5fd42e3b-4681-4607-b15d-65c3a3e89b32": {
Expand Down
Loading

0 comments on commit 9a5ec4d

Please sign in to comment.