From 6ca12138039397d433cf93ab2e7d5689e0d7f43d Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 21 Dec 2023 11:53:09 +0000 Subject: [PATCH] feat: station list and map --- apps/picsa-apps/dashboard/project.json | 2 +- .../home/climate-data-home.component.html | 34 ++++++++----- .../pages/home/climate-data-home.component.ts | 10 +++- .../pages/station/station-page.component.ts | 39 +++++++++++++++ .../app/pages/site-select/site-select.page.ts | 12 +---- libs/shared/src/features/map/map.ts | 49 +++++++++++++------ package.json | 6 +-- yarn.lock | 28 +++++------ 8 files changed, 124 insertions(+), 56 deletions(-) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.ts diff --git a/apps/picsa-apps/dashboard/project.json b/apps/picsa-apps/dashboard/project.json index 54eb634f7..28cd94435 100644 --- a/apps/picsa-apps/dashboard/project.json +++ b/apps/picsa-apps/dashboard/project.json @@ -17,7 +17,7 @@ "tsConfig": "apps/picsa-apps/dashboard/tsconfig.app.json", "inlineStyleLanguage": "scss", "assets": ["apps/picsa-apps/dashboard/src/favicon.ico", "apps/picsa-apps/dashboard/src/assets"], - "styles": ["apps/picsa-apps/dashboard/src/styles.scss"], + "styles": ["apps/picsa-apps/dashboard/src/styles.scss", "node_modules/leaflet/dist/leaflet.css"], "stylePreprocessorOptions": { "includePaths": ["libs/theme/src"] }, diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html index 1f0b0949a..db6cd8153 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html @@ -8,16 +8,26 @@

Climate Data

}

Stations

- - - - - - - - - - - -
station_id{{ station.station_id }}station_name{{ station.station_name }}
+
+
+ + + + + + + + + + + +
station_id{{ station.station_id }}station_name{{ station.station_name }}
+
+ +
diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts index 23103c0a0..182f99612 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts @@ -2,21 +2,29 @@ import { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { MatTableModule } from '@angular/material/table'; import { RouterModule } from '@angular/router'; +import { IMapMarker, PicsaMapComponent } from '@picsa/shared/features/map/map'; import { ClimateDataDashboardService, IStationRow } from '../../climate-data.service'; @Component({ selector: 'dashboard-climate-data-home', standalone: true, - imports: [CommonModule, MatTableModule, RouterModule], + imports: [CommonModule, MatTableModule, RouterModule, PicsaMapComponent], templateUrl: './climate-data-home.component.html', styleUrls: ['./climate-data-home.component.scss'], }) export class ClimateDataHomeComponent implements OnInit { public displayedColumns: (keyof IStationRow)[] = ['station_id', 'station_name']; + public mapMarkers: IMapMarker[]; + constructor(public service: ClimateDataDashboardService) {} + async ngOnInit() { await this.service.ready(); + this.mapMarkers = this.service.stations.map((m) => ({ + latlng: [m.latitude as number, m.longitude as number], + number: m.station_id, + })); } } diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.ts new file mode 100644 index 000000000..77e55d000 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.ts @@ -0,0 +1,39 @@ +import { CommonModule } from '@angular/common'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service'; + +import { ClimateDataDashboardService, IStationRow } from '../../climate-data.service'; + +@Component({ + selector: 'dashboard-station-page', + standalone: true, + imports: [CommonModule], + templateUrl: './station-page.component.html', + styleUrls: ['./station-page.component.scss'], +}) +export class StationPageComponent implements OnInit { + public station: IStationRow | undefined; + + public get stationSummary() { + return { + keys: Object.keys(this.station || {}), + values: Object.values(this.station || {}), + }; + } + + constructor( + private service: ClimateDataDashboardService, + private route: ActivatedRoute, + private notificationService: PicsaNotificationService + ) {} + + async ngOnInit() { + await this.service.ready(); + const { stationId } = this.route.snapshot.params; + this.station = this.service.stations.find((station) => station.station_id === parseInt(stationId)); + if (!this.station) { + this.notificationService.showUserNotification({ matIcon: 'error', message: `Station data not found` }); + } + } +} diff --git a/apps/picsa-tools/climate-tool/src/app/pages/site-select/site-select.page.ts b/apps/picsa-tools/climate-tool/src/app/pages/site-select/site-select.page.ts index ee1305e54..9802c43ac 100644 --- a/apps/picsa-tools/climate-tool/src/app/pages/site-select/site-select.page.ts +++ b/apps/picsa-tools/climate-tool/src/app/pages/site-select/site-select.page.ts @@ -56,7 +56,6 @@ export class SiteSelectPage implements OnInit { } populateSites() { - const iconUrl = STATION_ICON_WHITE; let stations = HARDCODED_STATIONS; const { climateTool, localisation } = this.configurationService.activeConfiguration; const filterFn = climateTool?.stationFilter; @@ -65,21 +64,14 @@ export class SiteSelectPage implements OnInit { } else { stations = stations.filter((station) => station.countryCode === localisation.country.code); } - const markers: IMapMarker[] = stations.map((s) => { + const markers: IMapMarker[] = stations.map((s, i) => { return { - iconUrl, latlng: [s.latitude, s.longitude], data: s, - numbered: true, + number: i + 1, }; }); this.mapMarkers = markers; return { stations, markers }; } } - -// svg icon hardcoded to data uri -const STATION_ICON_BLACK = - "data:image/svg+xml,%3Csvg width='100px' height='100px' enable-background='new 6.191 0 87.619 100' fill='%23000000' version='1.1' viewBox='6.191 0 87.619 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m82.447 57.766c-3.112 0-5.937 1.255-7.992 3.29l-19.102-13.687v-1.773c0-2.015-1.303-3.692-3.094-4.333v-18.671c5.521-0.768 9.769-5.509 9.769-11.236-1e-3 -6.266-5.082-11.356-11.361-11.356-6.266 0-11.348 5.091-11.348 11.355 0 5.727 4.247 10.468 9.77 11.236v18.688c-1.76 0.662-3.027 2.335-3.027 4.316v1.604l-20.156 13.865c-2.071-2.262-5.049-3.678-8.355-3.678-6.277 0-11.36 5.087-11.36 11.357s5.083 11.356 11.36 11.356c6.265 0 11.348-5.087 11.348-11.356 0-1.818-0.436-3.544-1.193-5.066l18.353-12.622v44.299c0 2.561 2.089 4.646 4.647 4.646 2.564 0 4.646-2.085 4.646-4.646v-44.084l17.183 12.314c-0.91 1.632-1.437 3.524-1.437 5.529 0 6.283 5.087 11.357 11.351 11.357 6.273 0 11.36-5.074 11.36-11.357-1e-3 -6.269-5.088-11.347-11.362-11.347z'/%3E%3C/svg%3E%0A"; -const STATION_ICON_WHITE = - "data:image/svg+xml,%3Csvg width='100px' height='100px' enable-background='new 6.191 0 87.619 100' fill='%23000000' version='1.1' viewBox='6.191 0 87.619 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m82.447 57.766c-3.112 0-5.937 1.255-7.992 3.29l-19.102-13.687v-1.773c0-2.015-1.303-3.692-3.094-4.333v-18.671c5.521-0.768 9.769-5.509 9.769-11.236-1e-3 -6.266-5.082-11.356-11.361-11.356-6.266 0-11.348 5.091-11.348 11.355 0 5.727 4.247 10.468 9.77 11.236v18.688c-1.76 0.662-3.027 2.335-3.027 4.316v1.604l-20.156 13.865c-2.071-2.262-5.049-3.678-8.355-3.678-6.277 0-11.36 5.087-11.36 11.357s5.083 11.356 11.36 11.356c6.265 0 11.348-5.087 11.348-11.356 0-1.818-0.436-3.544-1.193-5.066l18.353-12.622v44.299c0 2.561 2.089 4.646 4.647 4.646 2.564 0 4.646-2.085 4.646-4.646v-44.084l17.183 12.314c-0.91 1.632-1.437 3.524-1.437 5.529 0 6.283 5.087 11.357 11.351 11.357 6.273 0 11.36-5.074 11.36-11.357-1e-3 -6.269-5.088-11.347-11.362-11.347z' fill='%23fff'/%3E%3C/svg%3E%0A"; diff --git a/libs/shared/src/features/map/map.ts b/libs/shared/src/features/map/map.ts index 6b47545f1..f0d5be5da 100644 --- a/libs/shared/src/features/map/map.ts +++ b/libs/shared/src/features/map/map.ts @@ -21,7 +21,17 @@ export class PicsaMapComponent { @Input() mapOptions: L.MapOptions = {}; @Input() basemapOptions: Partial = {}; - @Input() markers: IMapMarker[]; + + private _markers: IMapMarker[]; + @Input() set markers(markers: IMapMarker[]) { + if (markers) { + this._markers = markers; + // add markers if map already initialised, otherwise will be added onMapReady + if (this.map) { + this.addMarkers(markers); + } + } + } // make native map element available directly public map: L.Map; @@ -31,6 +41,7 @@ export class PicsaMapComponent { private _activeMarker: L.Marker; // default options are overwritten via input setter _mapOptions: L.MapOptions = MAP_DEFAULTS; + ngOnInit() { // the user provides basemap options separate to general map options, so combine here // define the basemap layer and then bind to the view component @@ -40,43 +51,44 @@ export class PicsaMapComponent { this._mapOptions = { ...mapOptions, layers: [basemap] }; } - private addMarkers(mapMarkers: IMapMarker[], popupContent?: HTMLDivElement) { + private addMarkers(mapMarkers: IMapMarker[], fitMap = true) { mapMarkers.forEach((m, i) => { const icon = L.icon({ ...ICON_DEFAULTS, - iconUrl: m.iconUrl, + iconUrl: m.iconUrl || STATION_ICON_WHITE, }); const activeIcon = L.icon({ ...ACTIVE_ICON_DEFAULTS, - iconUrl: m.iconUrl, + iconUrl: m.iconUrl || STATION_ICON_WHITE, }); const marker = L.marker(m.latlng, { icon }); - if (m.numbered) { + if (m.number) { const toolTip = L.tooltip(NUMBER_TOOLTIP_DEFAULTS); - marker - .bindTooltip(toolTip) - .setTooltipContent(`${i + 1}`) - .openTooltip(); + marker.bindTooltip(toolTip).setTooltipContent(`${m.number}`).openTooltip(); } marker.on({ click: () => this._onMarkerClick(m, marker, activeIcon, icon), }); marker.addTo(this.map); }); + if (fitMap) { + this.fitMapToMarkers(mapMarkers); + } } // when the map is ready it emits event with map, and also binds map to // public api to be accessed by other services _onMapReady(map: L.Map) { this.map = map; - this.addMarkers(this.markers); - this.fitMapToMarkers(); + if (this._markers) { + this.addMarkers(this._markers); + } this.onMapReady.emit(map); } /** Calculate a bounding rectangle that covers all points and fit within map */ - private fitMapToMarkers() { - const latLngs = this.markers.map((m) => m.latlng); + private fitMapToMarkers(markers: IMapMarker[]) { + const latLngs = markers.map((m) => m.latlng); const bounds = new L.LatLngBounds(latLngs as any); this.map.fitBounds(bounds, { maxZoom: 8, padding: [10, 10] }); } @@ -175,12 +187,19 @@ const NUMBER_TOOLTIP_DEFAULTS: L.TooltipOptions = { type IFeaturedCountry = 'malawi' | 'kenya'; export interface IMapMarker { - iconUrl: string; + iconUrl?: string; latlng: L.LatLngTuple; - numbered?: boolean; + /** Display number with icon */ + number?: number; data?: any; } export interface IBasemapOptions extends L.TileLayerOptions { src: string; } export type IMapOptions = L.MapOptions; + +// svg icon hardcoded to data uri +const STATION_ICON_BLACK = + "data:image/svg+xml,%3Csvg width='100px' height='100px' enable-background='new 6.191 0 87.619 100' fill='%23000000' version='1.1' viewBox='6.191 0 87.619 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m82.447 57.766c-3.112 0-5.937 1.255-7.992 3.29l-19.102-13.687v-1.773c0-2.015-1.303-3.692-3.094-4.333v-18.671c5.521-0.768 9.769-5.509 9.769-11.236-1e-3 -6.266-5.082-11.356-11.361-11.356-6.266 0-11.348 5.091-11.348 11.355 0 5.727 4.247 10.468 9.77 11.236v18.688c-1.76 0.662-3.027 2.335-3.027 4.316v1.604l-20.156 13.865c-2.071-2.262-5.049-3.678-8.355-3.678-6.277 0-11.36 5.087-11.36 11.357s5.083 11.356 11.36 11.356c6.265 0 11.348-5.087 11.348-11.356 0-1.818-0.436-3.544-1.193-5.066l18.353-12.622v44.299c0 2.561 2.089 4.646 4.647 4.646 2.564 0 4.646-2.085 4.646-4.646v-44.084l17.183 12.314c-0.91 1.632-1.437 3.524-1.437 5.529 0 6.283 5.087 11.357 11.351 11.357 6.273 0 11.36-5.074 11.36-11.357-1e-3 -6.269-5.088-11.347-11.362-11.347z'/%3E%3C/svg%3E%0A"; +const STATION_ICON_WHITE = + "data:image/svg+xml,%3Csvg width='100px' height='100px' enable-background='new 6.191 0 87.619 100' fill='%23000000' version='1.1' viewBox='6.191 0 87.619 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m82.447 57.766c-3.112 0-5.937 1.255-7.992 3.29l-19.102-13.687v-1.773c0-2.015-1.303-3.692-3.094-4.333v-18.671c5.521-0.768 9.769-5.509 9.769-11.236-1e-3 -6.266-5.082-11.356-11.361-11.356-6.266 0-11.348 5.091-11.348 11.355 0 5.727 4.247 10.468 9.77 11.236v18.688c-1.76 0.662-3.027 2.335-3.027 4.316v1.604l-20.156 13.865c-2.071-2.262-5.049-3.678-8.355-3.678-6.277 0-11.36 5.087-11.36 11.357s5.083 11.356 11.36 11.356c6.265 0 11.348-5.087 11.348-11.356 0-1.818-0.436-3.544-1.193-5.066l18.353-12.622v44.299c0 2.561 2.089 4.646 4.647 4.646 2.564 0 4.646-2.085 4.646-4.646v-44.084l17.183 12.314c-0.91 1.632-1.437 3.524-1.437 5.529 0 6.283 5.087 11.357 11.351 11.357 6.273 0 11.36-5.074 11.36-11.357-1e-3 -6.269-5.088-11.347-11.362-11.347z' fill='%23fff'/%3E%3C/svg%3E%0A"; diff --git a/package.json b/package.json index 5c10fe731..f8bd12b80 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@angular/platform-browser-dynamic": "17.0.3", "@angular/router": "17.0.3", "@angular/service-worker": "17.0.3", - "@asymmetrik/ngx-leaflet": "^14.0.1", + "@asymmetrik/ngx-leaflet": "^17.0.0", "@awesome-cordova-plugins/core": "^6.4.0", "@awesome-cordova-plugins/file": "^6.4.0", "@awesome-cordova-plugins/file-opener": "^6.4.0", @@ -92,7 +92,7 @@ "hls.js": "^1.4.12", "html2canvas": "^1.4.1", "intro.js": "^7.0.1", - "leaflet": "^1.9.3", + "leaflet": "^1.9.4", "lottie-web": "^5.10.2", "mobx": "^6.7.0", "mobx-angular": "^4.7.1", @@ -140,7 +140,7 @@ "@types/hammerjs": "^2.0.41", "@types/intro.js": "^5.1.1", "@types/jest": "29.4.4", - "@types/leaflet": "^1.8.0", + "@types/leaflet": "^1.9.4", "@types/node": "^18.14.2", "@types/papaparse": "^5.3.2", "@types/parse": "^2.18.16", diff --git a/yarn.lock b/yarn.lock index 193156b5c..cb47365d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -594,16 +594,16 @@ __metadata: languageName: node linkType: hard -"@asymmetrik/ngx-leaflet@npm:^14.0.1": - version: 14.0.1 - resolution: "@asymmetrik/ngx-leaflet@npm:14.0.1" +"@asymmetrik/ngx-leaflet@npm:^17.0.0": + version: 17.0.0 + resolution: "@asymmetrik/ngx-leaflet@npm:17.0.0" dependencies: tslib: ^2.3.0 peerDependencies: - "@angular/common": 14 - "@angular/core": 14 + "@angular/common": 17 + "@angular/core": 17 leaflet: 1 - checksum: c30fbfd3821cea0e8c05b3da914cee1580eb0ce2670e53f234cd15691115127b28c180665850b856da0c4c2ea159de257daf5571171bf49033e2576c33a6df58 + checksum: 504be40bbb9d9425e133824836f84a3300bb0220f530bd7abba4a9cb7c1122db6bee4fec8fd61ffe42d40c5efa6936ab07fde13006d48b91b3606c5bfeb668bc languageName: node linkType: hard @@ -8357,12 +8357,12 @@ __metadata: languageName: node linkType: hard -"@types/leaflet@npm:^1.8.0": - version: 1.9.4 - resolution: "@types/leaflet@npm:1.9.4" +"@types/leaflet@npm:^1.9.4": + version: 1.9.8 + resolution: "@types/leaflet@npm:1.9.8" dependencies: "@types/geojson": "*" - checksum: ae5b4422b6eb912e6c6910d049f63cb96537eac46af4a897471ab98c642d059a32789f411157b873a5dafdbc9cef3399dc0673c5f64be067eebc530c43783e75 + checksum: d6a33d5db0bdf608957cd52701b17ee89e936034a8f0ab619b8e7ef70312a0da177837dd125756e83af036276a8b299cc724c9ffb3caa7d22893fea2791e1e13 languageName: node linkType: hard @@ -16467,7 +16467,7 @@ __metadata: languageName: node linkType: hard -"leaflet@npm:^1.9.3": +"leaflet@npm:^1.9.3, leaflet@npm:^1.9.4": version: 1.9.4 resolution: "leaflet@npm:1.9.4" checksum: bfc79f17a247b37b92d84b3c78702501603392d6589fde606de4a825d11f1609d90225388834f2e0709dac327e52dcd4b4b9cc9fd3d590060c5b1e53b84fa6c6 @@ -18937,7 +18937,7 @@ __metadata: "@angular/platform-browser-dynamic": 17.0.3 "@angular/router": 17.0.3 "@angular/service-worker": 17.0.3 - "@asymmetrik/ngx-leaflet": ^14.0.1 + "@asymmetrik/ngx-leaflet": ^17.0.0 "@awesome-cordova-plugins/core": ^6.4.0 "@awesome-cordova-plugins/file": ^6.4.0 "@awesome-cordova-plugins/file-opener": ^6.4.0 @@ -18978,7 +18978,7 @@ __metadata: "@types/hammerjs": ^2.0.41 "@types/intro.js": ^5.1.1 "@types/jest": 29.4.4 - "@types/leaflet": ^1.8.0 + "@types/leaflet": ^1.9.4 "@types/node": ^18.14.2 "@types/papaparse": ^5.3.2 "@types/parse": ^2.18.16 @@ -19033,7 +19033,7 @@ __metadata: jest-preset-angular: 13.1.4 jetifier: ^2.0.0 jsonc-eslint-parser: ^2.4.0 - leaflet: ^1.9.3 + leaflet: ^1.9.4 lint-staged: ^13.2.1 lottie-web: ^5.10.2 mobx: ^6.7.0