Skip to content

Commit

Permalink
Improve feed + feed details pages. Allow realtime only
Browse files Browse the repository at this point in the history
  • Loading branch information
EnessenE committed Dec 29, 2024
1 parent 2b72a7e commit 95e61b6
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 143 deletions.
91 changes: 48 additions & 43 deletions src/app/pages/feed-details/feed-details.component.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
<div class="container-fluid grid">
<div class="col-12">
<h1>Showing data for feed {{ selectedFeed }}</h1>
<p>
Stops:
@if (stops?.length) {
{{ stops?.length }}
} @else {
<app-loading></app-loading>
}
</p>
<p>
Routes:
@if (routes?.length) {
{{ routes?.length }}
} @else {
<app-loading></app-loading>
}
</p>
@if (!onlyRealTime) {
<p>
Stops:
@if (stops?.length) {
{{ stops?.length }}
} @else {
<app-loading></app-loading>
}
</p>
<p>
Routes:
@if (routes?.length) {
{{ routes?.length }}
} @else {
<app-loading></app-loading>
}
</p>
}
<p>
Realtime vehicles:
@if (vehiclePositions?.length || vehiclePositions != null) {
Expand All @@ -29,40 +31,43 @@ <h1>Showing data for feed {{ selectedFeed }}</h1>
<div class="col-12">
<div
(window:resize)="onResize($event)"
class="leaflet stop-map-view"
class="leaflet"
[ngClass]="onlyRealTime ? 'realtime-only-map-view': 'stop-map-view'"
leaflet
[leafletOptions]="options"
[leafletLayers]="layers"
[leafletLayersControl]="layersControl"
(leafletMapReady)="onMapReady($event)"
></div>
</div>
<div class="col-12">
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">Short name</th>
<th scope="col">Long name</th>
<th scope="col">Type</th>
<th scope="col">Agency</th>
<th scope="col">Description</th>
<th scope="col">Url</th>
</tr>
</thead>
<tbody>
@for (route of routes; track route; let index = $index) {
@if (!onlyRealTime) {
<div class="col-12">
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th scope="row">{{ route.shortName }}</th>
<td>{{ route.longName }}</td>
<td>{{ route.type }}</td>
<td>{{ route.agency }}</td>
<td>{{ route.description }}</td>
<td>{{ route.url }}</td>
<th scope="col">Short name</th>
<th scope="col">Long name</th>
<th scope="col">Type</th>
<th scope="col">Agency</th>
<th scope="col">Description</th>
<th scope="col">Url</th>
</tr>
} @empty {
<p>No feeds loaded to display!</p>
}
</tbody>
</table>
</div>
</thead>
<tbody>
@for (route of routes; track route; let index = $index) {
<tr>
<th scope="row">{{ route.shortName }}</th>
<td>{{ route.longName }}</td>
<td>{{ route.type }}</td>
<td>{{ route.agency }}</td>
<td>{{ route.description }}</td>
<td>{{ route.url }}</td>
</tr>
} @empty {
<p>No feeds loaded to display!</p>
}
</tbody>
</table>
</div>
}
</div>
4 changes: 4 additions & 0 deletions src/app/pages/feed-details/feed-details.component.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.stop-map-view{
height: 50VH;
}

.realtime-only-map-view{
height: 80VH;
}
159 changes: 113 additions & 46 deletions src/app/pages/feed-details/feed-details.component.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { ApiService } from '../../services/api.service';
import { GTFSRoute } from '../../models/gtfsroute';
import { GTFSSearchStop } from '../../models/gtfssearchstop';
import { VehiclePosition } from '../../models/vehicle-position';
import { LeafletMarkerClusterModule } from '@bluehalo/ngx-leaflet-markercluster';
import { LeafletModule, LeafletControlLayersConfig } from '@bluehalo/ngx-leaflet';
// Order is apparently important here. This should be imported after bluehalo-ngx-leaflet things
import { FeatureGroup, Map, LatLngBounds, latLng, Layer, tileLayer, featureGroup, circle, Popup, MarkerClusterGroup, MarkerClusterGroupOptions } from 'leaflet';
import { LoadingComponent } from "../../comps/loading/loading.component";

import {
FeatureGroup,
Map,
LatLngBounds,
latLng,
Layer,
tileLayer,
featureGroup,
circle,
Popup,
MarkerClusterGroup,
MarkerClusterGroupOptions,
} from 'leaflet';
import { LoadingComponent } from '../../comps/loading/loading.component';
import { CommonModule } from '@angular/common';
import { interval, Subscription } from 'rxjs';

@Component({
selector: 'app-feed-details',
imports: [LeafletModule, LeafletMarkerClusterModule, LoadingComponent],
imports: [LeafletModule, LeafletMarkerClusterModule, LoadingComponent, RouterModule, CommonModule],
templateUrl: './feed-details.component.html',
styleUrl: './feed-details.component.scss'
styleUrl: './feed-details.component.scss',
})
export class FeedDetailsComponent implements OnInit {
export class FeedDetailsComponent implements OnInit, OnDestroy {
private intervalSubscription: Subscription | undefined;

loading: boolean = false;
selectedFeed: string = 'Unknown';
routes: GTFSRoute[] | undefined;
stops: GTFSSearchStop[] | undefined;
vehiclePositions: VehiclePosition[] | undefined;
onlyRealTime: boolean = false;

map!: Map;
markerLayers!: FeatureGroup;
Expand All @@ -36,7 +52,16 @@ export class FeedDetailsComponent implements OnInit {
mapFitToBounds!: LatLngBounds;

markerClusterOptions: MarkerClusterGroupOptions = {};
clusterGroup: MarkerClusterGroup;

clusterGroup: MarkerClusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: true,
zoomToBoundsOnClick: true,
disableClusteringAtZoom: 13,
removeOutsideVisibleBounds: true,
animate: true,
chunkedLoading: true,
});

options = {
zoom: 13,
Expand All @@ -50,21 +75,34 @@ export class FeedDetailsComponent implements OnInit {
private route: ActivatedRoute,
private titleService: Title,
) {

this.layers = [
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
}),
];

this.clusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: true,
zoomToBoundsOnClick: true,
disableClusteringAtZoom: 15,
removeOutsideVisibleBounds: true,
animate: true,
chunkedLoading: true,
});
var realtimeData = this.route.snapshot.queryParamMap.get('realtime');
if (realtimeData != null) {
// lmao what
this.onlyRealTime = Boolean(JSON.parse(realtimeData));
if (this.onlyRealTime) {
this.clusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: true,
zoomToBoundsOnClick: true,
disableClusteringAtZoom: 5,
removeOutsideVisibleBounds: true,
animate: true,
chunkedLoading: true,
});
} else {
this.clusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: true,
zoomToBoundsOnClick: true,
disableClusteringAtZoom: 13,
removeOutsideVisibleBounds: true,
animate: true,
chunkedLoading: true,
});
}
}
console.log('Realtime only: ' + this.onlyRealTime);
this.layers = [tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {})];
}

ngOnInit(): void {
Expand All @@ -74,32 +112,48 @@ export class FeedDetailsComponent implements OnInit {
this.markerLayers.removeLayer(layer);
});

this.layers.push(this.clusterGroup)
this.layers.push(this.clusterGroup);

this.route.params.subscribe((params) => {
this.selectedFeed = params['id'];
this.titleService.setTitle(this.selectedFeed);
this.titleService.setTitle('Feed ' + this.selectedFeed);
this.apiService.GetFeedRoutes(this.selectedFeed).subscribe({
next: (data) => {
this.routes = data;
this.loading = false;
},
});
this.apiService.GetFeedPositions(this.selectedFeed).subscribe({
next: (data) => {
this.vehiclePositions = data;
this.addVehiclesToMap(data);
this.loading = false;
},
});
this.apiService.GetFeedStops(this.selectedFeed).subscribe({
next: (data) => {
this.stops = data;
this.addStopsToMap(data);
this.loading = false;
},

this.getFeedPositions();
this.intervalSubscription = interval(10000).subscribe(() => {
this.getFeedPositions();
});

if (!this.onlyRealTime) {
this.apiService.GetFeedRoutes(this.selectedFeed).subscribe({
next: (data) => {
this.routes = data;
this.loading = false;
},
});
this.apiService.GetFeedStops(this.selectedFeed).subscribe({
next: (data) => {
this.stops = data;
this.addStopsToMap(data);
this.loading = false;
},
});
}
});
}

private getFeedPositions() {
this.apiService.GetFeedPositions(this.selectedFeed).subscribe({
next: (data) => {
this.markerLayers = featureGroup();
this.vehiclePositions = data;
this.addVehiclesToMap(data);
this.loading = false;
},
error: (err) => {
console.error('Error fetching feed positions:', err);
this.loading = false;
},
});
}

Expand Down Expand Up @@ -127,10 +181,16 @@ export class FeedDetailsComponent implements OnInit {

addVehiclesToMap(vehicles: VehiclePosition[]) {
vehicles.forEach((vehicle) => {
var stopLayer = circle([vehicle.latitude, vehicle.longitude], { radius: 100, color: "green" });
var stopLayer = circle([vehicle.latitude, vehicle.longitude], { radius: 40, color: 'green' });

var popup = new Popup();
popup.setContent(vehicle.id);
var popupText = `Known as: ${vehicle.id} </br> Measured at: ${vehicle.measurementTime} </br>`;
if (vehicle.tripId != undefined) {
popupText += `Trip: <a href='/trip/${vehicle.tripId}'> ${vehicle.tripId}</a>`;
} else {
popupText += `Trip: no trip configured`;
}
popup.setContent(popupText);
stopLayer.bindPopup(popup);
this.clusterGroup.addLayer(stopLayer);
});
Expand All @@ -152,7 +212,14 @@ export class FeedDetailsComponent implements OnInit {
invalidateMap(): void {
this.map?.invalidateSize();
}

onResize(event: any) {
this.invalidateMap();
}

ngOnDestroy() {
if (this.intervalSubscription) {
this.intervalSubscription.unsubscribe();
}
}
}
Loading

0 comments on commit 95e61b6

Please sign in to comment.