diff --git a/src/interface/src/app/app-routing.module.ts b/src/interface/src/app/app-routing.module.ts index e3d7bbdef..176de2886 100644 --- a/src/interface/src/app/app-routing.module.ts +++ b/src/interface/src/app/app-routing.module.ts @@ -118,6 +118,7 @@ const routes: Routes = [ canActivate: [AuthGuard, createFeatureGuard('treatments')], resolve: { treatmentId: numberResolver('treatmentId', ''), + scenarioId: numberResolver('scenarioId', ''), }, loadChildren: () => import('./treatments/treatments.module').then( diff --git a/src/interface/src/app/plan/plan-helpers.ts b/src/interface/src/app/plan/plan-helpers.ts index cfea2d952..b45a2aba1 100644 --- a/src/interface/src/app/plan/plan-helpers.ts +++ b/src/interface/src/app/plan/plan-helpers.ts @@ -11,8 +11,6 @@ import { } from './project-areas/project-areas.component'; import { DEFAULT_AREA_COLOR, PROJECT_AREA_COLORS } from '@shared'; -export const NOTE_SAVE_INTERVAL = 5000; - export const POLLING_INTERVAL = 3000; // sizes in hectares @@ -25,10 +23,10 @@ export const STAND_SIZES: Record = { export function parseResultsToProjectAreas( results: ScenarioResult ): ProjectAreaReport[] { - return results.result.features.map((featureCollection, i) => { + return results.result.features.map((featureCollection) => { const props = featureCollection.properties; return { - id: i + 1, + rank: props.treatment_rank, acres: props.area_acres, percentTotal: props.pct_area, estimatedCost: props.total_cost, @@ -55,15 +53,11 @@ export function parseResultsToTotals( ); } -/** - * - * @param position rank position (1 based index) of scenario projection - */ -export function getColorForProjectPosition(position: number) { - if (position < 1) { +export function getColorForProjectPosition(rank: number) { + if (rank < 1) { return DEFAULT_AREA_COLOR; } - return PROJECT_AREA_COLORS[(position - 1) % PROJECT_AREA_COLORS.length]; + return PROJECT_AREA_COLORS[(rank - 1) % PROJECT_AREA_COLORS.length]; } export function findQuestionOnTreatmentGoalsConfig( diff --git a/src/interface/src/app/plan/plan-map/plan-map.component.ts b/src/interface/src/app/plan/plan-map/plan-map.component.ts index b0fda7fb2..7c89c7f71 100644 --- a/src/interface/src/app/plan/plan-map/plan-map.component.ts +++ b/src/interface/src/app/plan/plan-map/plan-map.component.ts @@ -324,7 +324,7 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy { this.projectAreasLayer = L.geoJSON(shapes, { style: (shape) => ({ color: '#000', - fillColor: getColorForProjectPosition(shape?.properties.proj_id), + fillColor: getColorForProjectPosition(shape?.properties.treatment_rank), fillOpacity: 0.4, weight: 1.5, }), @@ -333,7 +333,7 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy { if (feature.geometry.type === 'Polygon') { center = polylabel(feature.geometry.coordinates, 0.0005); addTooltipAtCenter( - feature.properties.proj_id.toString(), + feature.properties.treatment_rank.toString(), center, this.map ); @@ -341,7 +341,7 @@ export class PlanMapComponent implements OnInit, AfterViewInit, OnDestroy { feature.geometry.coordinates.forEach((positions) => { center = polylabel(positions, 0.005); addTooltipAtCenter( - feature.properties.proj_id.toString(), + feature.properties.treatment_rank.toString(), center, this.map ); diff --git a/src/interface/src/app/plan/project-areas/project-areas.component.html b/src/interface/src/app/plan/project-areas/project-areas.component.html index af3234bbe..558c66acd 100644 --- a/src/interface/src/app/plan/project-areas/project-areas.component.html +++ b/src/interface/src/app/plan/project-areas/project-areas.component.html @@ -34,8 +34,8 @@

Project Areas

- {{ area.id }} + [style.border-left-color]="getColorByPosition(area.rank)"> + {{ area.rank }} {{ area.acres | number: '1.0-0' }} {{ area.percentTotal | percent: '1.0-2' }} diff --git a/src/interface/src/app/plan/project-areas/project-areas.component.ts b/src/interface/src/app/plan/project-areas/project-areas.component.ts index f41b0b8da..31962ea6c 100644 --- a/src/interface/src/app/plan/project-areas/project-areas.component.ts +++ b/src/interface/src/app/plan/project-areas/project-areas.component.ts @@ -6,7 +6,7 @@ import { import { PROJECT_AREA_COLORS } from '@shared'; export interface ProjectAreaReport { - id: number; + rank: number; acres: number; percentTotal: number; estimatedCost: number; diff --git a/src/interface/src/app/standalone/learn-more/learn-more.component.html b/src/interface/src/app/standalone/learn-more/learn-more.component.html index 8dd9dcdd9..f444fd04b 100644 --- a/src/interface/src/app/standalone/learn-more/learn-more.component.html +++ b/src/interface/src/app/standalone/learn-more/learn-more.component.html @@ -9,7 +9,7 @@ title="Learn more about planscape" content="Visit our FAQs to learn more" iconsrc="../../assets/svg/icons/info-circle-dark.svg" - outboundLink="https://www.planscape.org/faqs"> + outboundLink="https://www.planscape.org/documentation/faq/"> + + + + + + diff --git a/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.scss b/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.spec.ts b/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.spec.ts new file mode 100644 index 000000000..0dce65240 --- /dev/null +++ b/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.spec.ts @@ -0,0 +1,28 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MapProjectAreasComponent } from './map-project-areas.component'; +import { MockDeclarations } from 'ng-mocks'; +import { + LayerComponent, + VectorSourceComponent, +} from '@maplibre/ngx-maplibre-gl'; + +describe('MapProjectAreasComponent', () => { + let component: MapProjectAreasComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MapProjectAreasComponent], + declarations: MockDeclarations(VectorSourceComponent, LayerComponent), + }).compileComponents(); + + fixture = TestBed.createComponent(MapProjectAreasComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.ts b/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.ts new file mode 100644 index 000000000..121565cc8 --- /dev/null +++ b/src/interface/src/app/treatments/map-project-areas/map-project-areas.component.ts @@ -0,0 +1,56 @@ +import { Component, Input } from '@angular/core'; +import { + FeatureComponent, + GeoJSONSourceComponent, + LayerComponent, + VectorSourceComponent, +} from '@maplibre/ngx-maplibre-gl'; +import { getColorForProjectPosition } from '../../plan/plan-helpers'; +import { LayerSpecification, Map as MapLibreMap } from 'maplibre-gl'; +import { environment } from '../../../environments/environment'; + +@Component({ + selector: 'app-map-project-areas', + standalone: true, + imports: [ + FeatureComponent, + GeoJSONSourceComponent, + LayerComponent, + VectorSourceComponent, + ], + templateUrl: './map-project-areas.component.html', + styleUrl: './map-project-areas.component.scss', +}) +export class MapProjectAreasComponent { + @Input() scenarioId!: number; + @Input() mapLibreMap!: MapLibreMap; + + readonly layerName = 'project_areas_by_scenario'; + readonly tilesUrl = + environment.martin_server + 'project_areas_by_scenario/{z}/{x}/{y}'; + + constructor() {} + + get vectorLayerUrl() { + return this.tilesUrl + `?scenario_id=${this.scenarioId}`; + } + + paint: LayerSpecification['paint'] = { + 'fill-outline-color': '#000', + 'fill-color': this.getFillColors() as any, + 'fill-opacity': 0.5, + }; + + getFillColors() { + const defaultColor = '#00000050'; + const matchExpression: (number | string | string[])[] = [ + 'match', + ['get', 'rank'], + ]; + for (let i = 1; i < 11; i++) { + matchExpression.push(i.toString(), getColorForProjectPosition(i)); + } + matchExpression.push(defaultColor); + return matchExpression; + } +} diff --git a/src/interface/src/app/treatments/map-rectangle/map-rectangle.component.html b/src/interface/src/app/treatments/map-rectangle/map-rectangle.component.html index 391d97b28..483e8f3be 100644 --- a/src/interface/src/app/treatments/map-rectangle/map-rectangle.component.html +++ b/src/interface/src/app/treatments/map-rectangle/map-rectangle.component.html @@ -1,4 +1,4 @@ - + + sourceLayer="project_area_aggregate"> + sourceLayer="stands_by_tx_plan"> + + diff --git a/src/interface/src/app/treatments/treatment-map/treatment-map.component.ts b/src/interface/src/app/treatments/treatment-map/treatment-map.component.ts index 0f50d431a..8022d150f 100644 --- a/src/interface/src/app/treatments/treatment-map/treatment-map.component.ts +++ b/src/interface/src/app/treatments/treatment-map/treatment-map.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { JsonPipe, NgForOf } from '@angular/common'; +import { JsonPipe, NgForOf, NgIf } from '@angular/common'; import { DraggableDirective, FeatureComponent, @@ -15,6 +15,7 @@ import { MapRectangleComponent } from '../map-rectangle/map-rectangle.component' import { SelectedStandsState } from './selected-stands.state'; import { MapControlsComponent } from '../map-controls/map-controls.component'; import { environment } from '../../../environments/environment'; +import { MapProjectAreasComponent } from '../map-project-areas/map-project-areas.component'; @Component({ selector: 'app-treatment-map', @@ -31,6 +32,8 @@ import { environment } from '../../../environments/environment'; MapStandsComponent, MapRectangleComponent, MapControlsComponent, + MapProjectAreasComponent, + NgIf, ], providers: [SelectedStandsState], templateUrl: './treatment-map.component.html', @@ -43,6 +46,7 @@ export class TreatmentMapComponent { // TODO: should we keep using prop drilling here? Consider using a provider service to hold these values @Input() projectAreaId: number | null = null; @Input() treatmentPlanId = 0; + @Input() scenarioId: number | null = null; treatedStands: { id: number; assigment: string }[] = []; diff --git a/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.html b/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.html index 9ae8e1ccf..4411d819d 100644 --- a/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.html +++ b/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.html @@ -1,4 +1,6 @@ - + diff --git a/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.ts b/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.ts index d60e41816..a894534c2 100644 --- a/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.ts +++ b/src/interface/src/app/treatments/treatment-overview/treatment-overview.component.ts @@ -16,6 +16,7 @@ import { TreatmentSummaryComponent } from '../treatment-summary/treatment-summar export class TreatmentOverviewComponent implements OnInit { treatmentPlanId: number = this.route.snapshot.data['treatmentId']; treatmentPlan: TreatmentPlan | null = null; + scenarioId: number = this.route.snapshot.data['scenarioId']; constructor( private treatmentsService: TreatmentsService, diff --git a/src/interface/src/app/types/scenario.types.ts b/src/interface/src/app/types/scenario.types.ts index d13214476..30e922002 100644 --- a/src/interface/src/app/types/scenario.types.ts +++ b/src/interface/src/app/types/scenario.types.ts @@ -41,7 +41,8 @@ export interface ScenarioResult { status: ScenarioResultStatus; completed_at: string; result: { - features: FeatureCollection[]; + // this is the FeatureCollection[] + features: FeatureCollection[]; // TODO this is actually Features[] type: string; }; }