Compare commits
2
Commits
db69e1bb22
...
2e9e86541a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e9e86541a | ||
|
|
aec2290b69 |
+2
-1
@@ -35,7 +35,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss"
|
"src/styles.scss",
|
||||||
|
"node_modules/leaflet/dist/leaflet.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
|
|||||||
Generated
+23
@@ -16,7 +16,9 @@
|
|||||||
"@angular/material": "^21.2.11",
|
"@angular/material": "^21.2.11",
|
||||||
"@angular/platform-browser": "^21.2.0",
|
"@angular/platform-browser": "^21.2.0",
|
||||||
"@angular/router": "^21.2.0",
|
"@angular/router": "^21.2.0",
|
||||||
|
"@types/leaflet": "^1.9.21",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"ng2-charts": "^10.0.0",
|
"ng2-charts": "^10.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
@@ -4051,6 +4053,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/geojson": {
|
||||||
|
"version": "7946.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||||
|
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/leaflet": {
|
||||||
|
"version": "1.9.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz",
|
||||||
|
"integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-basic-ssl": {
|
"node_modules/@vitejs/plugin-basic-ssl": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.4.tgz",
|
||||||
@@ -6089,6 +6106,12 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/leaflet": {
|
||||||
|
"version": "1.9.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
|
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/listr2": {
|
"node_modules/listr2": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
"@angular/material": "^21.2.11",
|
"@angular/material": "^21.2.11",
|
||||||
"@angular/platform-browser": "^21.2.0",
|
"@angular/platform-browser": "^21.2.0",
|
||||||
"@angular/router": "^21.2.0",
|
"@angular/router": "^21.2.0",
|
||||||
|
"@types/leaflet": "^1.9.21",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"ng2-charts": "^10.0.0",
|
"ng2-charts": "^10.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { Home } from './components/home/home';
|
import { Home } from './components/home/home';
|
||||||
|
import { MapaEstacions } from './components/mapa-estacions/mapa-estacions';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', component: Home },
|
{ path: '', component: Home },
|
||||||
|
{ path: 'mapa', component: MapaEstacions },
|
||||||
{ path: '**', redirectTo: '' }
|
{ path: '**', redirectTo: '' }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -67,6 +67,11 @@
|
|||||||
<button class="menu-action secondary" (click)="requestLocation(); closeMenu()">
|
<button class="menu-action secondary" (click)="requestLocation(); closeMenu()">
|
||||||
📍 Usar la meva ubicació
|
📍 Usar la meva ubicació
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Botón de mapa de estaciones -->
|
||||||
|
<button class="menu-action secondary" (click)="openMap(); closeMenu()">
|
||||||
|
🗺️ Mapa d'estacions
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, signal } from '@angular/core';
|
import { Component, OnInit, signal } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { MeteocatService } from '../../services/meteocat.service';
|
import { MeteocatService } from '../../services/meteocat.service';
|
||||||
import { Municipi } from '../../models/municipi.model';
|
import { Municipi } from '../../models/municipi.model';
|
||||||
import { PrediccioDiaria } from '../../models/prediccio-diaria.model';
|
import { PrediccioDiaria } from '../../models/prediccio-diaria.model';
|
||||||
@@ -38,9 +39,23 @@ export class Home implements OnInit {
|
|||||||
showGpsModal = signal(false);
|
showGpsModal = signal(false);
|
||||||
gpsPermissionAsked = signal(false);
|
gpsPermissionAsked = signal(false);
|
||||||
|
|
||||||
constructor(private meteocatService: MeteocatService) {}
|
constructor(
|
||||||
|
private meteocatService: MeteocatService,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// Verificar si viene un municipio desde el mapa
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if (params['municipi']) {
|
||||||
|
this.municipiCodi.set(params['municipi']);
|
||||||
|
this.gpsPermissionAsked.set(true);
|
||||||
|
this.loadWeatherData(params['municipi']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Verificar si ya se pidió permiso GPS antes
|
// Verificar si ya se pidió permiso GPS antes
|
||||||
const gpsRejected = localStorage.getItem('gpsRejected');
|
const gpsRejected = localStorage.getItem('gpsRejected');
|
||||||
const gpsAsked = localStorage.getItem('gpsAsked');
|
const gpsAsked = localStorage.getItem('gpsAsked');
|
||||||
@@ -292,4 +307,8 @@ export class Home implements OnInit {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openMap(): void {
|
||||||
|
this.router.navigate(['/mapa']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<div class="mapa-container">
|
||||||
|
<!-- Header con título y botón volver -->
|
||||||
|
<div class="header">
|
||||||
|
<button class="back-button" (click)="goBack()">
|
||||||
|
← Tornar
|
||||||
|
</button>
|
||||||
|
<h1>Mapa d'Estacions XEMA</h1>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filtros -->
|
||||||
|
<div class="filters">
|
||||||
|
<label class="filter-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[checked]="showOperatives()"
|
||||||
|
(change)="toggleOperatives()"
|
||||||
|
/>
|
||||||
|
<span class="checkbox-label">
|
||||||
|
<span class="status-dot green"></span>
|
||||||
|
Operatives ({{ estacions().filter(e => e.estat === 'Operativa').length }})
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="filter-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[checked]="showBaixaTemporal()"
|
||||||
|
(change)="toggleBaixaTemporal()"
|
||||||
|
/>
|
||||||
|
<span class="checkbox-label">
|
||||||
|
<span class="status-dot orange"></span>
|
||||||
|
Baixa temporal ({{ estacions().filter(e => e.estat === 'Baixa temporal').length }})
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="total-count">
|
||||||
|
Mostrant: {{ getFilteredCount() }} / {{ estacions().length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading -->
|
||||||
|
@if (loading()) {
|
||||||
|
<div class="loading">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Carregant estacions...</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Error -->
|
||||||
|
@if (error()) {
|
||||||
|
<div class="error">
|
||||||
|
<p>{{ error() }}</p>
|
||||||
|
<button (click)="loadEstacions()">Tornar a intentar</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Mapa -->
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
|
||||||
|
<!-- Leyenda -->
|
||||||
|
<div class="legend">
|
||||||
|
<div class="legend-item">
|
||||||
|
<span class="legend-icon">🌡️</span>
|
||||||
|
<span>Estació meteorològica</span>
|
||||||
|
</div>
|
||||||
|
<div class="legend-note">
|
||||||
|
Fes clic en un marcador per veure el temps d'aquell municipi
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
.mapa-container {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: linear-gradient(180deg, #072d59 0%, #56556a 100%);
|
||||||
|
color: white;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 15px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
width: 80px; // Para balancear el botón
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 15px;
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
padding: 15px 20px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-checkbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
margin-right: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&.green {
|
||||||
|
background: #4caf50;
|
||||||
|
box-shadow: 0 0 6px rgba(76, 175, 80, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.orange {
|
||||||
|
background: #ff9800;
|
||||||
|
box-shadow: 0 0 6px rgba(255, 152, 0, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-count {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading, .error {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.error button {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: white;
|
||||||
|
color: #4a90e2;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
padding: 10px 15px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-note {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estilos para los marcadores personalizados
|
||||||
|
:host ::ng-deep {
|
||||||
|
.custom-marker {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-pin {
|
||||||
|
width: 30px;
|
||||||
|
height: 42px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50% 50% 50% 0;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
left: 3px;
|
||||||
|
top: 0;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.green::before {
|
||||||
|
background: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.orange::before {
|
||||||
|
background: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tooltips
|
||||||
|
.leaflet-tooltip {
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-top-color: rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.station-tooltip {
|
||||||
|
strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&.operativa {
|
||||||
|
background: rgba(76, 175, 80, 0.3);
|
||||||
|
color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.baixa-temporal {
|
||||||
|
background: rgba(255, 152, 0, 0.3);
|
||||||
|
color: #ff9800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import { Component, OnInit, AfterViewInit, OnDestroy, signal } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import * as L from 'leaflet';
|
||||||
|
import { MeteocatService } from '../../services/meteocat.service';
|
||||||
|
import { EstacioXema } from '../../models/estacio-xema.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-mapa-estacions',
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './mapa-estacions.html',
|
||||||
|
styleUrl: './mapa-estacions.scss',
|
||||||
|
})
|
||||||
|
export class MapaEstacions implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
private map: L.Map | null = null;
|
||||||
|
private markers: L.Marker[] = [];
|
||||||
|
|
||||||
|
estacions = signal<EstacioXema[]>([]);
|
||||||
|
loading = signal(true);
|
||||||
|
error = signal<string | null>(null);
|
||||||
|
|
||||||
|
// Filtros
|
||||||
|
showOperatives = signal(true);
|
||||||
|
showBaixaTemporal = signal(true);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private meteocatService: MeteocatService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadEstacions();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.initMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.map) {
|
||||||
|
this.map.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEstacions(): void {
|
||||||
|
this.loading.set(true);
|
||||||
|
this.meteocatService.getAllEstacions().subscribe({
|
||||||
|
next: (estacions) => {
|
||||||
|
this.estacions.set(estacions);
|
||||||
|
this.loading.set(false);
|
||||||
|
if (this.map) {
|
||||||
|
this.addMarkers();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error loading estacions:', err);
|
||||||
|
this.error.set('Error al carregar les estacions meteorològiques');
|
||||||
|
this.loading.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initMap(): void {
|
||||||
|
// Crear mapa centrado en Cataluña
|
||||||
|
this.map = L.map('map', {
|
||||||
|
center: [41.8, 1.5],
|
||||||
|
zoom: 8,
|
||||||
|
minZoom: 7,
|
||||||
|
maxZoom: 18
|
||||||
|
});
|
||||||
|
|
||||||
|
// Añadir capa de OpenStreetMap
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
// Añadir marcadores si ya se cargaron las estaciones
|
||||||
|
if (this.estacions().length > 0) {
|
||||||
|
this.addMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addMarkers(): void {
|
||||||
|
if (!this.map) return;
|
||||||
|
|
||||||
|
// Limpiar marcadores anteriores
|
||||||
|
this.markers.forEach(marker => marker.remove());
|
||||||
|
this.markers = [];
|
||||||
|
|
||||||
|
const filteredEstacions = this.estacions().filter(estacio => {
|
||||||
|
if (estacio.estat === 'Operativa' && this.showOperatives()) return true;
|
||||||
|
if (estacio.estat === 'Baixa temporal' && this.showBaixaTemporal()) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
filteredEstacions.forEach(estacio => {
|
||||||
|
if (!estacio.coordenadesLatitud || !estacio.coordenadesLongitud) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icono personalizado según estado
|
||||||
|
const iconColor = estacio.estat === 'Operativa' ? 'green' : 'orange';
|
||||||
|
const icon = L.divIcon({
|
||||||
|
className: 'custom-marker',
|
||||||
|
html: `
|
||||||
|
<div class="marker-pin ${iconColor}">
|
||||||
|
<div class="marker-icon">🌡️</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
iconSize: [30, 42],
|
||||||
|
iconAnchor: [15, 42],
|
||||||
|
popupAnchor: [0, -42]
|
||||||
|
});
|
||||||
|
|
||||||
|
const marker = L.marker(
|
||||||
|
[estacio.coordenadesLatitud, estacio.coordenadesLongitud],
|
||||||
|
{ icon }
|
||||||
|
).addTo(this.map!);
|
||||||
|
|
||||||
|
// Tooltip (hover)
|
||||||
|
marker.bindTooltip(`
|
||||||
|
<div class="station-tooltip">
|
||||||
|
<strong>${estacio.nom}</strong><br/>
|
||||||
|
${estacio.emplacament || ''}<br/>
|
||||||
|
<span class="status ${estacio.estat.toLowerCase().replace(' ', '-')}">${estacio.estat}</span>
|
||||||
|
</div>
|
||||||
|
`, {
|
||||||
|
direction: 'top',
|
||||||
|
offset: [0, -42]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Popup (click) - navegar al municipio
|
||||||
|
marker.on('click', () => {
|
||||||
|
if (estacio.municipiCodi) {
|
||||||
|
this.navigateToMunicipi(estacio.municipiCodi);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.markers.push(marker);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateToMunicipi(municipiCodi: string): void {
|
||||||
|
// Cerrar el mapa y volver a Home con el municipio seleccionado
|
||||||
|
this.router.navigate(['/'], {
|
||||||
|
queryParams: { municipi: municipiCodi }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOperatives(): void {
|
||||||
|
this.showOperatives.set(!this.showOperatives());
|
||||||
|
this.addMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBaixaTemporal(): void {
|
||||||
|
this.showBaixaTemporal.set(!this.showBaixaTemporal());
|
||||||
|
this.addMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilteredCount(): number {
|
||||||
|
return this.estacions().filter(estacio => {
|
||||||
|
if (estacio.estat === 'Operativa' && this.showOperatives()) return true;
|
||||||
|
if (estacio.estat === 'Baixa temporal' && this.showBaixaTemporal()) return true;
|
||||||
|
return false;
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,15 @@ export interface EstacioXema {
|
|||||||
tipus: string;
|
tipus: string;
|
||||||
coordenadesLatitud: number;
|
coordenadesLatitud: number;
|
||||||
coordenadesLongitud: number;
|
coordenadesLongitud: number;
|
||||||
altitud?: number;
|
altitud: number;
|
||||||
municipi?: string;
|
emplacament: string;
|
||||||
comarca?: string;
|
municipiCodi: string;
|
||||||
provincia?: string;
|
comarcaId: number;
|
||||||
xarxa?: string;
|
provinciaCodi: string;
|
||||||
estat?: string;
|
xarxaCodi: string;
|
||||||
dataInici?: string;
|
estat: string;
|
||||||
dataFi?: string;
|
dataInici: string | null;
|
||||||
distancia?: number; // Calculado dinámicamente para estación más cercana
|
dataFi: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user