Skip to content

Commit

Permalink
Map control dialog and layer control for scenario results (#1527)
Browse files Browse the repository at this point in the history
* add scenario result layer control and dialog
  • Loading branch information
lastminutediorama authored May 24, 2024
1 parent aa0a99b commit cd4db3b
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,4 @@ pyrightconfig.json

Session.vim

cypress/screenshots/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<section class="header">
<h4>Map Controls</h4>
<button mat-icon-button (click)="cancel()" aria-label="close">
<mat-icon class="close-btn">close</mat-icon>
</button>
</section>
<section class="radio-row">
<div class="radio-group-label">Base Map</div>

<mat-radio-group
name="dialog-select"
aria-label="Select an option"
class="layer-radio-group"
[(ngModel)]="currentLayer"
color="primary">
<div
class="layer-control-container"
*ngFor="let baseLayerType of baseLayerTypes">
<mat-radio-button
aria-label="baseLayerType"
[value]="baseLayerType"
checked="{{ baseLayerType === currentLayer }}">
{{ BaseLayerType[baseLayerType] }}
</mat-radio-button>
</div>
</mat-radio-group>
</section>
<section class="footer">
<button
class="action"
mat-flat-button
type="button"
color="primary"
(click)="confirm()">
DONE
</button>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@import 'colors';
@import 'mixins';

:host {
display: flex;
flex-direction: column;
width: 400px;
}

.header {
display: flex;
align-items: center;
padding: 16px;
padding-bottom: 10px;
padding-top: 10px;
border-bottom: 1px solid $color-soft-gray;
justify-content: space-between;
align-items: center;
height: 64px;

button {
background: none;
border: none;
padding: 0;
margin: 0;
line-height: 0;
cursor: pointer;
height: auto;
width: auto;
}

}

h4 {
@include h4();
height: 1em;
line-height: 32px;
}

.radio-row {
padding: 1em;
justify-content: space-between;
display: flex;
flex-direction: column;
}

mat-radio-group {
height: 128px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
color: $color-dark-gray;

}
.radio-group-label {
@include xs-label();
color:black;
}

.footer {
border-top: 1px solid $color-soft-gray;
padding: 16px;
height: 64px;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MapLayerSelectDialogComponent } from './map-layer-select-dialog.component';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialog as MatDialog,
MatLegacyDialogRef as MatDialogRef,
} from '@angular/material/legacy-dialog';

describe('MapLayerSelectDialogComponent', () => {
let component: MapLayerSelectDialogComponent;
let fixture: ComponentFixture<MapLayerSelectDialogComponent>;

beforeEach(async () => {
const fakeData = {
user: {
email: '[email protected]',
},
};
const fakeDialog = jasmine.createSpyObj(
'MatDialog',
{
open: undefined,
},
{}
);
const fakeDialogRef = jasmine.createSpyObj(
'MatDialogRef',
{
close: undefined,
},
{}
);
await TestBed.configureTestingModule({
// imports: [MapLayerSelectDialogComponent],
declarations: [MapLayerSelectDialogComponent],
providers: [
{
provide: MAT_DIALOG_DATA,
useValue: fakeData,
},
{
provide: MatDialog,
useValue: fakeDialog,
},
{
provide: MatDialogRef<MapLayerSelectDialogComponent>,
useValue: fakeDialogRef,
},
],
}).compileComponents();

fixture = TestBed.createComponent(MapLayerSelectDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Component, Inject } from '@angular/core';
import {
MatLegacyDialogRef as MatDialogRef,
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
} from '@angular/material/legacy-dialog';
import { BaseLayerType } from '@types';

@Component({
selector: 'app-map-layer-select-dialog',
templateUrl: './map-layer-select-dialog.component.html',
styleUrl: './map-layer-select-dialog.component.scss',
})
export class MapLayerSelectDialogComponent {
disableDeleteButton = false;
constructor(
@Inject(MatDialogRef)
private dialogRef: MatDialogRef<MapLayerSelectDialogComponent>,
@Inject(MAT_DIALOG_DATA)
public data: {
selectedLayer: number;
}
) {}
givenLayer = this.data.selectedLayer;
currentLayer = this.givenLayer;
baseLayerTypes: number[] = [
BaseLayerType.Road,
BaseLayerType.Terrain,
BaseLayerType.Satellite,
];
readonly BaseLayerType = BaseLayerType;
cancel(): void {
this.dialogRef.close(undefined);
}

confirm(): void {
this.dialogRef.close(this.currentLayer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component, EventEmitter, Output } from '@angular/core';
import * as L from 'leaflet';

@Component({
template: '<div></div>',
})
export class MapLayerControlComponent extends L.Control {
constructor() {
super({ position: 'topleft' }); // Set the position (optional)
}
@Output() layerClicked = new EventEmitter<void>();

override onAdd(map: L.Map) {
const container = L.DomUtil.create('div', 'base-layer-control');
const icon = L.DomUtil.create(
'span',
'material-symbols-outlined layer-icon'
);
icon.textContent = 'layers';
container.append(icon);
container.addEventListener('click', () => this.handleClick());
return container;
}

handleClick() {
this.layerClicked.emit();
}
}
26 changes: 26 additions & 0 deletions src/interface/src/app/plan/plan-map/plan-map.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,30 @@
padding: 0;
line-height: 30px;
}

::ng-deep .base-layer-control {
top: 6px;
left: 6px;
font-weight: bold;
text-align: center;
width: 30px;
text-overflow: ellipsis;
overflow-x: hidden;
line-height: 10px;
background: white;
white-space: nowrap;
opacity: 0.9;
padding: 1px 2px 2px 3px;
border-radius: 4px;
transition: width 0.8s;
box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.42);
}
::ng-deep .base-layer-control:hover {
background: lightgray;
}
::ng-deep .layer-icon {
color: black;
height: 32px;
line-height: 32px;
}
}
57 changes: 51 additions & 6 deletions src/interface/src/app/plan/plan-map/plan-map.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import { Feature } from 'geojson';
import { getColorForProjectPosition } from '../plan-helpers';
import polylabel from 'polylabel';
import { environment } from '../../../environments/environment';

// Needed to keep reference to legend div element to remove
import { MapLayerSelectDialogComponent } from '../map-layer-select-dialog/map-layer-select-dialog.component';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { BaseLayerType } from '@types';
import { satelliteTiles, terrainTiles } from 'src/app/map/map.tiles';
import { MapLayerControlComponent } from './map-layer-control/map-layer-control-component';
// Needed to keep references to div elements to remove
export interface MapRef {
legend?: HTMLElement | undefined;
}
Expand All @@ -34,8 +38,9 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy {
/** The amount of padding in the top left corner when the map fits the plan boundaries. */
@Input() mapPadding: L.PointTuple = [0, 0]; // [left, top]
@Input() showAttributionAndZoom: boolean = false;

@Input() showLayerSwitcher: boolean = false;
private readonly destroy$ = new Subject<void>();

map!: L.Map;
drawingLayer: L.GeoJSON | undefined;
projectAreasLayer: L.GeoJSON | undefined;
Expand All @@ -49,10 +54,12 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy {

private layer: string = '';
private shapes: any | null = null;

private layerControl = new MapLayerControlComponent();
private currentBaseLayer: BaseLayerType = BaseLayerType.Road;
constructor(
private planStateService: PlanStateService,
private http: HttpClient
private http: HttpClient,
private dialog: MatDialog
) {
this.selectedRegion$ = this.planStateService.planRegion$;
}
Expand All @@ -72,6 +79,24 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy {
});
}

openLayerDialog() {
const layerPickerDialogRef = this.dialog.open(
MapLayerSelectDialogComponent,
{
data: { selectedLayer: this.currentBaseLayer },
panelClass: 'no-padding-dialog',
}
);
layerPickerDialogRef
.afterClosed()
.pipe(take(1))
.subscribe((selected) => {
if (selected !== undefined) {
this.setCurrentBaseLayer(selected);
}
});
}

ngAfterViewInit(): void {
if (this.map != undefined) this.map.remove();

Expand All @@ -97,6 +122,11 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy {
zoomControl.addTo(this.map);
}

if (this.showLayerSwitcher) {
this.layerControl.layerClicked.subscribe(() => this.openLayerDialog());
this.map.addControl(this.layerControl);
}

if (this.plan) {
this.drawPlanningArea(this.plan!);
}
Expand Down Expand Up @@ -143,6 +173,22 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy {
);
}

setCurrentBaseLayer(selectedLayer: BaseLayerType) {
this.map.eachLayer((ly) => {
if (ly instanceof L.TileLayer) {
this.map.removeLayer(ly);
}
});
let layerTiles = terrainTiles();
if (selectedLayer === BaseLayerType.Road) {
layerTiles = this.stadiaAlidadeTiles();
} else if (selectedLayer === BaseLayerType.Satellite) {
layerTiles = satelliteTiles();
}
this.currentBaseLayer = selectedLayer;
this.map.addLayer(layerTiles);
}

ngOnDestroy(): void {
this.map.remove();
this.destroy$.next();
Expand All @@ -169,7 +215,6 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy {
opacity: 0.7,
}
);

this.map.addLayer(this.tileLayer);

// Map legend request
Expand Down
1 change: 1 addition & 0 deletions src/interface/src/app/plan/plan.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
[plan]="currentPlan$ | async"
[mapId]="'planning-map'"
[mapPadding]="[0, 0]"
[showLayerSwitcher]="true"
[showAttributionAndZoom]="true"></app-plan-map>
</div>
</ng-container>
Expand Down
Loading

0 comments on commit cd4db3b

Please sign in to comment.