Compare commits
24 Commits
main
...
vehicle-po
Author | SHA1 | Date |
---|---|---|
dancingCycle | 20694dac86 | |
dancingCycle | 4aaad35ae5 | |
dancingCycle | eca8a07656 | |
dancingCycle | af533042ad | |
dancingCycle | d2dcf47245 | |
dancingCycle | bfb15c4cf1 | |
dancingCycle | 0b501ee48a | |
dancingCycle | 168db28cd1 | |
dancingCycle | b09004a163 | |
dancingCycle | 0b5a4eac3e | |
dancingCycle | 966243afd3 | |
dancingCycle | a828b309be | |
dancingCycle | b3025d9d79 | |
dancingCycle | 648d1afe66 | |
dancingCycle | 134dcbcd8b | |
dancingCycle | 72815f7cbd | |
dancingCycle | 9ff992b39a | |
dancingCycle | c5cd44e632 | |
dancingCycle | d67e967c7a | |
dancingCycle | 5ed587f0ce | |
dancingCycle | ac91c2b741 | |
dancingCycle | 6d94dc0732 | |
dancingCycle | 82b92eac86 | |
dancingCycle | b5e4f567a3 |
11
app/app.jsx
11
app/app.jsx
|
@ -5,10 +5,9 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
|||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
|
||||
import Contact from './pages/contact';
|
||||
import Home from './pages/home';
|
||||
import Table from './pages/table-page';
|
||||
import Map from './pages/map-page';
|
||||
import NavBar from './components/nav-bar';
|
||||
import TripUpdates from './pages/trip-updates';
|
||||
import VehiclePositions from './pages/vehicle-positions';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
@ -19,10 +18,10 @@ export default function App() {
|
|||
<BrowserRouter>
|
||||
<NavBar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Map />} />
|
||||
<Route path="/table" element={<Table />} />
|
||||
<Route path="/map" element={<Map />} />
|
||||
<Route path="/" element={<VehiclePositions />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
<Route path="/trip-updates" element={<TripUpdates />} />
|
||||
<Route path="/vehicle-positions" element={<VehiclePositions />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
/*controlled component: input form value controlled by React*/
|
||||
const InputSearch = ({id, name, onChange, placeholder, title, type, value}) => {
|
||||
return (
|
||||
<>
|
||||
<Form.Control
|
||||
aria-label={title}
|
||||
className={name}
|
||||
id={id}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
title={title}
|
||||
type={type}
|
||||
value={value}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default InputSearch;
|
||||
|
||||
InputSearch.propTypes = {
|
||||
id: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
onChange: PropTypes.func
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MapContainer,TileLayer} from 'react-leaflet';
|
||||
import MarkerClusterGroup from 'react-leaflet-cluster';
|
||||
/*JS module import (vs cdn or style link)*/
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import './map.css';
|
||||
|
@ -26,20 +27,25 @@ export default function Map({messages}) {
|
|||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
|
||||
<MarkerClusterGroup
|
||||
chunkedLoading
|
||||
>
|
||||
{
|
||||
messages.map(function(value,key) {
|
||||
//console.log(`key: ${key}, tripId: ${value.tripId}`);
|
||||
if(hasGtfs){
|
||||
return <MsgMarkerWithGtfs key={value.tripId} index={value.vehicleId} message={value}/>;
|
||||
}else{
|
||||
return <MsgMarkerWithoutGtfs key={value.tripId} index={value.vehicleId} message={value}/>;
|
||||
return <MsgMarkerWithGtfs key={key} index={value.trip_id} message={value}/>;
|
||||
} else {
|
||||
return <MsgMarkerWithoutGtfs key={key} index={value.trip_id} message={value}/>;
|
||||
}
|
||||
})
|
||||
}
|
||||
</MarkerClusterGroup>
|
||||
</MapContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Map.propTypes = {
|
||||
messages: PropTypes.array
|
||||
};
|
||||
|
|
|
@ -7,20 +7,26 @@ import getIcon from './icon';
|
|||
|
||||
const MarkerMsgPlus = ({ message }) => {
|
||||
|
||||
if(message===undefined || message===null){
|
||||
if(message === undefined || message === null){
|
||||
console.error('message undefined or null');
|
||||
return null;
|
||||
}else{
|
||||
//console.log(`MarkerMsgPlus: tripId: ${message.tripId}`);
|
||||
const markerIcon=getIcon();
|
||||
if(markerIcon===null){
|
||||
if(markerIcon === null || markerIcon === undefined){
|
||||
console.error('MarkerMsgPlus: icon null');
|
||||
return null;
|
||||
}else if(message.latitude === null || message.latitude === undefined) {
|
||||
//console.error('MarkerMsgPlus: lat unavailable');
|
||||
return null;
|
||||
}else if(message.longitude === null || message.longitude === undefined) {
|
||||
//console.error('MarkerMsgPlus: lon unavailable');
|
||||
return null;
|
||||
}else{
|
||||
return(
|
||||
<>
|
||||
<Marker
|
||||
position={[message.lat,message.lon]}
|
||||
position={[message.latitude, message.longitude]}
|
||||
icon={markerIcon}
|
||||
>
|
||||
<PopupMsg message={message} />
|
||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import MarkerMsgPlus from './marker-msg-plus';
|
||||
|
||||
const MarkerMsg = ({ message }) => {
|
||||
if(message===undefined || message===null){
|
||||
if(message === undefined || message === null){
|
||||
console.error('message undefined or null');
|
||||
return null;
|
||||
}else{
|
||||
|
|
|
@ -2,29 +2,14 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {Popup} from 'react-leaflet';
|
||||
|
||||
import seconds2dmhs from '../../utils/seconds2dhms';
|
||||
|
||||
const PopupMsg = ({message}) => {
|
||||
/*get number of ms since epoch*/
|
||||
const nowTsMs=Date.now();
|
||||
const nowTs=Math.round(nowTsMs/1000);
|
||||
|
||||
const itcsTs=message.tsMsgCreationItcs;
|
||||
const itcsTsMs=itcsTs*1000;
|
||||
const itcsAge=seconds2dmhs(Math.round(nowTs-itcsTs));
|
||||
const itcsDate=new Date(itcsTsMs);
|
||||
const itcsString=itcsDate.toString()
|
||||
return (
|
||||
<>
|
||||
<Popup>
|
||||
message id: {message.id} <br/>
|
||||
vehicle id: {message.vehicleId} <br/>
|
||||
trip id: {message.tripId} <br/>
|
||||
route id: {message.routeId} <br/>
|
||||
lat: {message.lat} <br/>
|
||||
lon: {message.lon} <br/>
|
||||
<br/>
|
||||
GTFS Realtime age: {itcsAge} <br/>
|
||||
trip id: {message.trip_id} <br/>
|
||||
route id: {message.route_id} <br/>
|
||||
lat: {message.latitude} <br/>
|
||||
lon: {message.longitude} <br/>
|
||||
</Popup>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -5,18 +5,17 @@ import { LinkContainer } from 'react-router-bootstrap';
|
|||
function NavigationBar () {
|
||||
return (
|
||||
<Navbar collapseOnSelect fixed="top" bg="dark" expand="xxl" variant="dark">
|
||||
//TODO make brand available through configuration
|
||||
<Navbar.Brand href="/">GTFS Realtime Display</Navbar.Brand>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav" />
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
<Nav className="mr-auto">
|
||||
<LinkContainer to="/table">
|
||||
<Nav.Link>Table</Nav.Link>
|
||||
<LinkContainer to="/trip-updates">
|
||||
<Nav.Link>TripUpdates</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
<Nav className="mr-auto">
|
||||
<LinkContainer to="/map">
|
||||
<Nav.Link>Map</Nav.Link>
|
||||
<LinkContainer to="/vehicle-positions">
|
||||
<Nav.Link>VehiclePositions</Nav.Link>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
<Nav className="mr-auto">
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
/*controlled component: select controlled by React*/
|
||||
const Select = ({defaultValue, id, name, onChange, options, title}) => {
|
||||
if (options) {
|
||||
return (
|
||||
<Form.Select
|
||||
aria-label="select table entries per page"
|
||||
className={name}
|
||||
defaultValue={defaultValue}
|
||||
name={name}
|
||||
id={id}
|
||||
onChange={onChange}
|
||||
title={title}
|
||||
>
|
||||
{options.map((item, index) => (
|
||||
<option key={index} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</Form.Select>
|
||||
);
|
||||
} else {
|
||||
return <p>Select options unavailable.</p>;
|
||||
}
|
||||
};
|
||||
export default Select;
|
||||
Select.propTypes = {
|
||||
id: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
defaultValue: PropTypes.number,
|
||||
onChange: PropTypes.func,
|
||||
options: PropTypes.array,
|
||||
title: PropTypes.string
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Stack from 'react-bootstrap/Stack';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Select from './select';
|
||||
import {selectOptions} from '../utils/select-options';
|
||||
import TripUpdatesTable from './trip-updates-table';
|
||||
import Input from './input';
|
||||
import config from '../config';
|
||||
|
||||
export default function TripUpdatesPage(){
|
||||
/*store and initialise data in function component state*/
|
||||
const [oset, setOset] = useState(1);
|
||||
const [limit, setLimit] = useState(parseInt(selectOptions[0],10));
|
||||
const [searchField, setSearchField] = useState('');
|
||||
const handleClickPrev = () => {
|
||||
setOset((oset) => (oset > 1 ? --oset : oset));
|
||||
};
|
||||
const handleClickNext = () => {
|
||||
setOset((oset) => ++oset);
|
||||
};
|
||||
const handleChangeLimit = (event) => {
|
||||
setLimit((limit) => parseInt(event.target.value,10));
|
||||
};
|
||||
const handleSearch = (e) => {
|
||||
setSearchField((searchField)=>e.target.value);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Stack direction="horizontal" gap={1} className="m-1">
|
||||
<Button variant="secondary" onClick={handleClickPrev}>
|
||||
prev
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleClickNext}>
|
||||
next
|
||||
</Button>
|
||||
<Select
|
||||
defaultValue={selectOptions[0]}
|
||||
id="tablePageLimit"
|
||||
name="tablePageLimit"
|
||||
onChange={handleChangeLimit}
|
||||
options={selectOptions}
|
||||
/>
|
||||
<Input
|
||||
id="tablePageSearch"
|
||||
name="tablePageSearch"
|
||||
onChange={handleSearch}
|
||||
placeholder="Search table globally"
|
||||
type="search"
|
||||
title="Enter search value"
|
||||
value={searchField}
|
||||
/>
|
||||
</Stack>
|
||||
<TripUpdatesTable
|
||||
isFetched={false}
|
||||
oset={oset}
|
||||
limit={limit}
|
||||
filter={searchField}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TripUpdatesEntry from './trip-updates-table-entry';
|
||||
|
||||
export default function TripUpdatesTableEntries ({aryData}) {
|
||||
if (aryData.length > 0) {
|
||||
//iterate over array
|
||||
return aryData.map((item, index) => {
|
||||
if (item.trip) {
|
||||
//console.log('TripUpdatesTableEntries: trip available');
|
||||
const trip = item.trip;
|
||||
|
||||
return (
|
||||
<TripUpdatesEntry
|
||||
tripId={typeof trip.trip_id !== 'undefined' ? trip.trip_id : null}
|
||||
routeId={typeof trip.route_id !== 'undefined' ? trip.route_id : null}
|
||||
directionId={typeof trip.direction_id !== 'undefined' ? trip.direction_id : null}
|
||||
startTime={typeof trip.start_time !== 'undefined' ? trip.start_time : null}
|
||||
startDate={typeof trip.start_date !== 'undefined' ? trip.start_date : null}
|
||||
timestamp={typeof item.timestamp !== 'undefined' ? item.timestamp : null}
|
||||
delay={typeof item.delay !== 'undefined' ? item.delay : null}
|
||||
key={index}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
console.log('ERROR: TripUpdatesTableEntries: REQUIRED trip NOT available');
|
||||
}
|
||||
});
|
||||
}else{
|
||||
//data is empty
|
||||
return null;
|
||||
}
|
||||
}
|
||||
TripUpdatesTableEntries.propTypes = {
|
||||
aryData: PropTypes.array
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/*destructure props object*/
|
||||
const TripUpdatesTableEntry = ({
|
||||
tripId,
|
||||
routeId,
|
||||
directionId,
|
||||
startTime,
|
||||
startDate,
|
||||
timestamp,
|
||||
delay
|
||||
}) => {
|
||||
return (
|
||||
<tr>
|
||||
<td>{tripId}</td>
|
||||
<td>{routeId}</td>
|
||||
<td>{directionId}</td>
|
||||
<td>{startTime}</td>
|
||||
<td>{startDate}</td>
|
||||
<td>{timestamp}</td>
|
||||
<td>{delay}</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
TripUpdatesTableEntry.propTypes = {
|
||||
tripId: PropTypes.string,
|
||||
routeId: PropTypes.string,
|
||||
directionId: PropTypes.number,
|
||||
startTime: PropTypes.string,
|
||||
startDate: PropTypes.string,
|
||||
timestamp: PropTypes.number,
|
||||
delay: PropTypes.number
|
||||
};
|
||||
|
||||
export default TripUpdatesTableEntry;
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
const TripUpdatesTableHead = () => {
|
||||
return (
|
||||
<tr>
|
||||
<th>Trip:trip_id</th>
|
||||
<th>Trip:route_id</th>
|
||||
<th>Trip:direction_id</th>
|
||||
<th>Trip:start_time</th>
|
||||
<th>Trip:start_date</th>
|
||||
<th>timestamp</th>
|
||||
<th>delay</th>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default TripUpdatesTableHead;
|
|
@ -0,0 +1,144 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import PropTypes from 'prop-types';
|
||||
import Table from 'react-bootstrap/Table';
|
||||
import Alert from 'react-bootstrap/Alert';
|
||||
import Badge from 'react-bootstrap/Badge';
|
||||
|
||||
import {readPbf} from '../utils/gtfs-rt-utils';
|
||||
import TripUpdatesTableHead from './trip-updates-table-head';
|
||||
import TripUpdatesTableEntries from './trip-updates-table-entries';
|
||||
import config from '../config';
|
||||
import {filterData} from '../utils/filter-data';
|
||||
|
||||
export default function TripUpdatesTable ({isFetched, oset, limit, filter}) {
|
||||
|
||||
const [ary, setAry] = useState([]);
|
||||
const [aryFiltered, setAryFiltered] = useState([]);
|
||||
const [fetchCompleted, setFetchCompleted] = useState(isFetched);
|
||||
const [entityCount, setEntityCount] = useState(0);
|
||||
const [entityTripUpdateCount, setEntityTripUpdateCount] = useState(0);
|
||||
/*fetch ary in a JavaScript function*/
|
||||
const fetch = async () => {
|
||||
|
||||
try {
|
||||
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
|
||||
//TODO Make fields available via configuration!
|
||||
const optns = { responseType: 'arraybuffer' };
|
||||
const address = `${config.API}`;
|
||||
console.log('getData() address: ' + address );
|
||||
|
||||
const res = await axios.get(address, optns);
|
||||
|
||||
if(res.data){
|
||||
//////console.log('fetch() res available');
|
||||
const rry = readPbf(res.data);
|
||||
const rryLngth= rry.length;
|
||||
console.log('fetch() rryLngh: ' + rryLngth);
|
||||
setEntityCount(rryLngth);
|
||||
|
||||
const rryETU = new Array();
|
||||
rry.forEach(entity => {
|
||||
const entityTripUpdate = entity.trip_update;
|
||||
if (entityTripUpdate) {
|
||||
rryETU.push(entityTripUpdate);
|
||||
}
|
||||
});
|
||||
const rryETULngth= rryETU.length;
|
||||
console.log('fetch() rryETULength: ' + rryETULngth);
|
||||
|
||||
setEntityTripUpdateCount(rryETULngth);
|
||||
|
||||
const rryOstLmt = new Array();
|
||||
let j = 0
|
||||
for(let i = (oset - 1) * limit; i < rryETULngth ; i++){
|
||||
if(j < limit){
|
||||
rryOstLmt.push(rryETU[i]);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
//console.log('fetch() rryOstLmt.length: ' + rryOstLmt.length);
|
||||
setAry((ary) => rryOstLmt);
|
||||
|
||||
let data=filterData(rryOstLmt,'trip_updates',filter);
|
||||
setAryFiltered((aryFiltered) => data);
|
||||
}else{
|
||||
console.error('fetch() res NOT available');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('err.message: ' + err.message);
|
||||
setAry([]);
|
||||
setAryFiltered([]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
setAryFiltered((aryFiltered)=>{
|
||||
let filtered=filterData(ary,'trip_updates',filter);
|
||||
return filtered;
|
||||
});
|
||||
},[filter]);
|
||||
|
||||
useEffect(() => {
|
||||
/*effect goes here*/
|
||||
fetch();
|
||||
setFetchCompleted((fetchCompleted)=>true);
|
||||
//console.log('entityCount: ' + entityCount);
|
||||
//console.log('entityTripUpdateCount: ' + entityTripUpdateCount);
|
||||
/*use an empty dependency array to ensure the hook is running only once*/
|
||||
/*TODO study dependency array: https://reactjs.org/docs/hooks-effect.html*/
|
||||
}, [oset,limit]);
|
||||
|
||||
if(fetchCompleted && aryFiltered.length > 0){
|
||||
/*return a React element*/
|
||||
return (
|
||||
<>
|
||||
<Badge bg="secondary">
|
||||
abs entity count: {entityCount}
|
||||
</Badge>
|
||||
|
||||
<Badge bg="secondary">
|
||||
abs TripUpdate count: {entityTripUpdateCount}
|
||||
</Badge>
|
||||
|
||||
<Badge bg="secondary">
|
||||
page trip count: {ary.length}
|
||||
</Badge>
|
||||
|
||||
<Badge bg="secondary">
|
||||
filtered trip count: {aryFiltered.length}
|
||||
</Badge>
|
||||
|
||||
<Table
|
||||
striped
|
||||
bordered
|
||||
hover
|
||||
size="sm"
|
||||
variant="dark"
|
||||
responsive
|
||||
>
|
||||
<thead>
|
||||
<TripUpdatesTableHead />
|
||||
</thead>
|
||||
<tbody>
|
||||
<TripUpdatesTableEntries aryData={aryFiltered} />
|
||||
</tbody>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
}else{
|
||||
return (
|
||||
<Alert variant={'secondary'} onClose={() => setShow(false)} dismissible>
|
||||
<Badge bg="secondary">TripUpdate</Badge> entities loading...
|
||||
</Alert>
|
||||
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
TripUpdatesTable.propTypes = {
|
||||
isFetched: PropTypes.bool,
|
||||
offset: PropTypes.number,
|
||||
limit: PropTypes.number,
|
||||
filter: PropTypes.string
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
API: 'https://vm2037.swingbe.mooo.com/'
|
||||
};
|
|
@ -4,32 +4,36 @@ const VERSION = packageInfo.version;
|
|||
const Contact = () => {
|
||||
return (
|
||||
<>
|
||||
<h2>About this website</h2>
|
||||
|
||||
<p>
|
||||
For questions about this website please do not hesitate to reach out to dialog (at) swingbe (dot) de.
|
||||
For questions about this website please do not hesitate to reach out to dialog
|
||||
(at) swingbe (dot) de.
|
||||
</p>
|
||||
<p>
|
||||
Source code has been made public on{' '}
|
||||
Source code is controlled and provided using{' '}
|
||||
<a
|
||||
href="https://github.com/Software-Ingenieur-Begerad/gtfs-rt-display"
|
||||
href="https://git.wtf-eg.de/dancesWithCycles/gtfs-rt-display"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub
|
||||
</a>.
|
||||
Git
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<h2>Imprint</h2>
|
||||
<address>
|
||||
<strong>Software Ingenieur Begerad</strong>
|
||||
<br />
|
||||
Lammer Heide 87
|
||||
<br />
|
||||
38116 Braunschweig
|
||||
<br />
|
||||
Deutschland
|
||||
<br />
|
||||
</address>
|
||||
<h2>Other</h2>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.swingbe.de/imprint"
|
||||
target="_blank"
|
||||
>
|
||||
Imprint
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.swingbe.de/privacy-policy"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
Version: {VERSION}
|
||||
</p>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<h1>Home</h1>
|
||||
<p>Home</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import React, {useEffect,useState} from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import Map from '../components/map/map';
|
||||
import parseMessages from '../utils/gtfs-rt-utils';
|
||||
export default function MapPage() {
|
||||
/*storage*/
|
||||
const [vehPos, setVehPos] = useState([]);
|
||||
const getData= async ()=>{
|
||||
//console.log('getData() start...');
|
||||
try {
|
||||
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
|
||||
|
||||
//TODO Make fields available via configuration!
|
||||
let url = 'https://api.entur.io/realtime/v1/gtfs-rt/vehicle-positions';
|
||||
|
||||
const res = await axios.get(url,
|
||||
{
|
||||
responseType: 'arraybuffer'
|
||||
//responseType: 'blob'
|
||||
});
|
||||
if(res.data){
|
||||
//TODO remove debugging
|
||||
//console.log('getData() res available');
|
||||
/*parse messages*/
|
||||
const messages = parseMessages(res.data);
|
||||
//console.log('getData() messages.length: '+messages.length);
|
||||
|
||||
/*set state*/
|
||||
setVehPos(messages);
|
||||
}else{
|
||||
console.error('getData() res NOT available');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('err.message: ' + err.message);
|
||||
}
|
||||
//console.log('getData() done.');
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
/*do not wait the interval when component loads the first time*/
|
||||
getData();
|
||||
|
||||
/*refresh data periodically*/
|
||||
const intervalCall=setInterval(()=>{
|
||||
getData();
|
||||
}, 5000);
|
||||
/*TODO adjust interval, make it available via config file*/
|
||||
return ()=>{
|
||||
/*clean up*/
|
||||
clearInterval(intervalCall);
|
||||
};
|
||||
},[]);
|
||||
return (
|
||||
<>
|
||||
<Map messages={vehPos}/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
import React, {useEffect,useState} from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import parseMessages from '../utils/gtfs-rt-utils';
|
||||
import Table from '../components/table/table';
|
||||
export default function TablePage() {
|
||||
const [vehPos, setVehPos] = useState([]);
|
||||
const getData= async ()=>{
|
||||
//console.log('getData() start...');
|
||||
try {
|
||||
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
|
||||
//TODO Make fields available via configuration!
|
||||
let url = 'https://api.entur.io/realtime/v1/gtfs-rt/vehicle-positions';
|
||||
const res = await axios.get(url,
|
||||
{
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
if(res.data){
|
||||
//console.log('getData() res available');
|
||||
const messages = parseMessages(res.data);
|
||||
//console.log('getData() messages.length: '+messages.length);
|
||||
setVehPos(messages);
|
||||
}else{
|
||||
console.error('getData() res NOT available');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('err.message: ' + err.message);
|
||||
}
|
||||
//console.log('getData() done.');
|
||||
};
|
||||
useEffect(()=>{
|
||||
getData();
|
||||
const intervalCall=setInterval(()=>{
|
||||
getData();
|
||||
}, 5000);
|
||||
/*TODO adjust interval, make it available via config file*/
|
||||
return ()=>{
|
||||
clearInterval(intervalCall);
|
||||
};
|
||||
},[]);
|
||||
return (
|
||||
<>
|
||||
<Table messages={vehPos}/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import TripUpdatesPage from '../components/trip-updates-page';
|
||||
export default function TripUpdates() {
|
||||
return (
|
||||
<>
|
||||
<TripUpdatesPage />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import Alert from 'react-bootstrap/Alert';
|
||||
import Badge from 'react-bootstrap/Badge';
|
||||
|
||||
import {readPbf} from '../utils/gtfs-rt-utils';
|
||||
import Map from '../components/map/map';
|
||||
|
||||
import config from '../config';
|
||||
|
||||
|
||||
export default function VehiclePositions() {
|
||||
|
||||
const [rry, setRry] = useState([]);
|
||||
const [entityCount, setEntityCount] = useState(0);
|
||||
const [entityVehiclePositionCount, setEntityVehiclePositionCount] = useState(0);
|
||||
|
||||
|
||||
/*get data*/
|
||||
const getData = async () => {
|
||||
|
||||
try {
|
||||
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
|
||||
//TODO Make fields available via configuration!
|
||||
const optns = { responseType: 'arraybuffer' };
|
||||
const address = `${config.API}`;
|
||||
//console.log('getData() address: ' + address );
|
||||
|
||||
const res = await axios.get(address, optns);
|
||||
|
||||
if(res.data){
|
||||
////console.log('getRry() res available');
|
||||
const rry = readPbf(res.data);
|
||||
const rryLngth= rry.length;
|
||||
//console.log('getRry() rryLngh: ' + rryLngth);
|
||||
setEntityCount(rryLngth);
|
||||
|
||||
const rryEVP = new Array();
|
||||
rry.forEach(entity => {
|
||||
const entityVehiclePosition = entity.vehicle;
|
||||
if (entityVehiclePosition !== null && entityVehiclePosition !== undefined) {
|
||||
const oEVP = {};
|
||||
const trip = entityVehiclePosition.trip;
|
||||
if (trip !== null && trip !== undefined) {
|
||||
oEVP.trip_id = trip.trip_id;
|
||||
oEVP.route_id = trip.route_id;
|
||||
const position = entityVehiclePosition.position;
|
||||
if (position !== null && position !== undefined) {
|
||||
oEVP.latitude = position.latitude;
|
||||
oEVP.longitude = position.longitude;
|
||||
rryEVP.push(oEVP);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const rryEVPLngth = rryEVP.length;
|
||||
//console.log('getRry() rryEVPLength: ' + rryEVPLngth);
|
||||
|
||||
setEntityVehiclePositionCount(rryEVPLngth);
|
||||
|
||||
setRry(rryEVP);
|
||||
|
||||
}else{
|
||||
console.error('getRry() res NOT available');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('err.message: ' + err.message);
|
||||
setRry([]);
|
||||
}
|
||||
//////console.log('getData() done.');
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
//initial call
|
||||
getData();
|
||||
|
||||
const intervalCall=setInterval(()=>{
|
||||
getData();
|
||||
}, 10000);
|
||||
return ()=>{
|
||||
clearInterval(intervalCall);
|
||||
};
|
||||
},[]);
|
||||
|
||||
if (rry.length > 0) {
|
||||
/*return a React element*/
|
||||
return (
|
||||
<>
|
||||
<Badge bg="secondary">
|
||||
abs entity count: {entityCount}
|
||||
</Badge>
|
||||
|
||||
<Badge bg="secondary">
|
||||
abs VehiclePosition count: {entityVehiclePositionCount}
|
||||
</Badge>
|
||||
|
||||
<Map messages={rry}/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Alert variant={'secondary'} onClose={() => setShow(false)} dismissible>
|
||||
<Badge bg="secondary">VehiclePostion</Badge> entities loading...
|
||||
</Alert>
|
||||
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,216 @@
|
|||
function filterData(data, name,filter){
|
||||
if(data.length>0){
|
||||
//console.log('filterData() data.length: '+data.length);
|
||||
//console.log('filterData() name: '+name);
|
||||
//console.log('filterData() filter:'+filter);
|
||||
switch(name){
|
||||
case 'fare_zones_history':
|
||||
//TODO implement
|
||||
console.log('filterData() //TODO implement fare_zones_history');
|
||||
return data;
|
||||
break;
|
||||
case 'tdb_fare_zones':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.id.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.external!==null && item.external.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.internal!==null && item.internal.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.name!==null && item.name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.short_name!==null && item.short_name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.type!==null && item.type.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_from!==null && item.valid_from.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_until!==null && item.valid_until.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'lct_msg':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.bs_id.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.vc_trip!==null && item.vc_trip.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.vc_route!==null && item.vc_route.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.vc_tenant!==null && item.vc_tenant.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.vc_date!==null && item.vc_date.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.vc_time!==null && item.vc_time.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.vc_lon!==null && item.vc_lon.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.vc_lat!==null && item.vc_lat.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'localization':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.localization_id.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.name!==null && item.name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.lang_de!==null && item.lang_de.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.lang_en!==null && item.lang_en.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.version_id!==null && item.version_id.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'relations':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
(item.dtype!==null && item.dtype.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.id!==null && item.id.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.active!=null && item.active.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.direct_purchase!==null && item.direct_purchase.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.disabled!==null && item.disabled.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.info!==null && item.info.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.last_modified!==null && item.last_modified.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.price_level!==null && item.price_level.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.start_zone!==null && item.start_zone.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.target_zone!==null && item.target_zone.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_from!==null && item.valid_from.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_until!==null && item.valid_until.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.via_name!==null && item.via_name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.via_fare_zone!==null && item.via_fare_zone.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.zones!==null && item.zones.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.matching_via_id!==null && item.matching_via_id.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.variant!==null && item.variant.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.created_user_id!==null && item.created_user_id.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.last_modified_user_id!==null && item.last_modified_user_id.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.comment!== null && item.comment.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.reverse_direction_id!== null && item.reverse_direction_id.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.via_id!==null && item.via_id.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.all_transit_zones!=null && item.all_transit_zones.toString().toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'price_levels':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.price_level_id.toString().toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.short_name!==null && item.short_name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.name!==null && item.name.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'price':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.price_id.toString().toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.product_id!==null && item.product_id.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.price_level_id!==null && item.price_level_id.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.id_ticket!==null && item.id_ticket.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.price!==null && item.price.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.duration!==null && item.duration.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.priority!==null && item.priority.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.localization_id4add_info!==null && item.localization_id4add_info.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.localization_id4ticket_descr!==null && item.localization_id4ticket_descr.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.localization_id4sale_text1!==null && item.localization_id4sale_text1.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.localization_id4sale_text2!==null && item.localization_id4sale_text2.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.localization_id4ticket_note1!==null && item.localization_id4ticket_note1.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.localization_id4ticket_note2!==null && item.localization_id4ticket_note2.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.localization_id4note_lang!==null && item.localization_id4note_lang.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.filter_code!==null && item.filter_code.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.version_id!==null && item.version_id.toString().toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'product':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.product_id.toString().toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.id_prod!==null && item.id_prod.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.ext_prod_localization_id!==null && item.ext_prod_localization_id.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.info_prod_localization_id!==null && item.info_prod_localization_id.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'sales_parameter':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.sales_parameter_id.toString().includes(filter.toLowerCase()) ||
|
||||
(item.product_id!==null && item.product_id.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.cnt_presale_days!==null && item.cnt_presale_days.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.app_prsnt_after_val!==null && item.app_prsnt_after_val.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.bday!==null && item.bday.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.num_add_names!==null && item.num_add_names.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_type!==null && item.val_type.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_days!==null && item.val_days.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_beg_m_f!==null && item.val_beg_m_f.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_beg_s_s!==null && item.val_beg_s_s.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_end!==null && item.val_end.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.version_id!==null && item.version_id.toString().toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'tdb_stops':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.id.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.active!==null && item.active.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.lon!==null && item.lon.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.location!==null && item.location.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.lat!==null && item.lat.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.stop_long_name!==null && item.stop_long_name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.stop_name!==null && item.stop_name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.stop_name_extern!==null && item.stop_name_extern.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.fare_zone_1!==null && item.fare_zone_1.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.fare_zone_2!==null && item.fare_zone_2.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.fare_zone_3!==null && item.fare_zone_3.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.fare_zone_4!==null && item.fare_zone_4.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_from!==null && item.valid_from.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_until!==null && item.valid_until.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.last_modified!==null && item.last_modified.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'ticket-selection':
|
||||
return data.filter((item,index)=>{
|
||||
return (
|
||||
item.price_id.toString().toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.ext_prod_de!==null && item.ext_prod_de.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.id_ticket!==null && item.id_ticket.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.price!==null && item.price.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.name!==null && item.name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.short_name!==null && item.short_name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.priority!==null && item.priority.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.duration!==null && item.duration.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_beg_m_f!==null && item.val_beg_m_f.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_beg_s_s!==null && item.val_beg_s_s.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.val_end!==null && item.val_end.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'trip_updates':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
(item.trip.trip_id !== null && item.trip.trip_id.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.trip.route_id !== null && item.trip.route_id.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.trip.direction_id !== null && item.trip.direction_id.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.trip.start_time !== null && item.trip.start_time.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.trip.start_date !== null && item.trip.start_date.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.timestamp !== null && item.timestamp.toString().toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.delay !== null && item.delay.toString().toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
case 'tokens':
|
||||
return data;
|
||||
break;
|
||||
case 'users':
|
||||
console.log('filterData() //TODO implement users');
|
||||
return data;
|
||||
break;
|
||||
case 'versions':
|
||||
return data.filter((item, index) => {
|
||||
return (
|
||||
item.version_id.toString().toLowerCase().includes(filter.toLowerCase()) ||
|
||||
(item.name!==null && item.name.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_from!==null && item.valid_from.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.valid_until!==null && item.valid_until.toLowerCase().includes(filter.toLowerCase())) ||
|
||||
(item.descr!==null && item.descr.toLowerCase().includes(filter.toLowerCase()))
|
||||
);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.error(`filterData() name: ${name} unknown`);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
module.exports = {
|
||||
filterData
|
||||
};
|
|
@ -1,41 +1,58 @@
|
|||
import Pbf from 'pbf';
|
||||
|
||||
import { FeedMessage } from './gtfs-rt.js';
|
||||
import charIntoString from './string';
|
||||
//import charIntoString from './string';
|
||||
|
||||
export default function parseMessages(buffer){
|
||||
//console.log('parseMessages() start...');
|
||||
/**
|
||||
* parse buffer into array of entity objects
|
||||
*/
|
||||
export function parseMessages(buffer){
|
||||
////console.log('parseMessages() start...');
|
||||
const messages = [];
|
||||
const pbf = new Pbf(buffer);
|
||||
const feed = FeedMessage.read(pbf);
|
||||
//console.log('parseMessages() feed:'+JSON.stringify(feed));
|
||||
////console.log('parseMessages() feed:'+JSON.stringify(feed));
|
||||
|
||||
let countEntityAlert = 0;
|
||||
let countEntityShape = 0;
|
||||
let countEntityTripUpdate = 0;
|
||||
let countEntityVehicle = 0;
|
||||
feed.entity.forEach(entity => {
|
||||
/*Data about the realtime position of a vehicle.*/
|
||||
const vehiclePos = entity.vehicle;
|
||||
if (vehiclePos) {
|
||||
//console.log('getVehPos() vehiclePos available');
|
||||
const entityAlert = entity.alert;
|
||||
const entityShape = entity.shape;
|
||||
const entityTripUpdate = entity.trip_update;
|
||||
const entityVehicle = entity.vehicle;
|
||||
if (entityAlert) {
|
||||
countEntityAlert++;
|
||||
} else if (entityShape) {
|
||||
countEntityShape++;
|
||||
} else if (entityTripUpdate) {
|
||||
countEntityTripUpdate++;
|
||||
}else if (entityVehicle) {
|
||||
countEntityVehicle++;
|
||||
////console.log('parseMessage() entityVehicle available');
|
||||
/*The Trip that this vehicle is serving.*/
|
||||
const trip=vehiclePos.trip;
|
||||
const trip=entityVehicle.trip;
|
||||
/*Additional information on the vehicle that is serving this trip.*/
|
||||
const vehicle=vehiclePos.vehicle;
|
||||
const vehicle=entityVehicle.vehicle;
|
||||
/*Current position of this vehicle.*/
|
||||
const position=vehiclePos.position;
|
||||
const position=entityVehicle.position;
|
||||
/*Moment at which the vehicle's position was measured. In POSIX time (i.e., number of seconds since January 1st 1970 00:00:00 UTC).*/
|
||||
const vehPosTimestamp=vehiclePos.timestamp;
|
||||
const vehPosTimestamp=entityVehicle.timestamp;
|
||||
//remove tailing dot
|
||||
//match a dot when it is followed by a whitespace or the end of the string
|
||||
/*TODO Is this precaution required?*/
|
||||
//TODO Handle error! Placing a decimal point at a fixed place does not work in general!
|
||||
//let posLat=position.latitude;
|
||||
//console.log(`getVehPos() posLat:${posLat}`);
|
||||
////console.log(`parseMessage() posLat:${posLat}`);
|
||||
//let latFormed = position.latitude === undefined ? -360 : position.latitude.toString().replace(/\.+$/, "");
|
||||
//console.log(`getVehPos() latFormed:${latFormed}`);
|
||||
////console.log(`parseMessage() latFormed:${latFormed}`);
|
||||
//latFormed=charIntoString(latFormed,latFormed.length - 7,'.');
|
||||
//console.log(`getVehPos() latFormed:${latFormed}`);
|
||||
////console.log(`parseMessage() latFormed:${latFormed}`);
|
||||
//let lonFormed = position.longitude === undefined ? -720 : position.longitude.toString().replace(/\.+$/, "");
|
||||
//lonFormed=charIntoString(lonFormed,lonFormed.length - 7,'.');
|
||||
//console.log(`getVehPos() lonFormed:${lonFormed}`);
|
||||
////console.log(`parseMessage() lonFormed:${lonFormed}`);
|
||||
const now= new Date();
|
||||
const message={
|
||||
/*Version of the feed specification. The current version is 2.0.*/
|
||||
|
@ -61,9 +78,27 @@ export default function parseMessages(buffer){
|
|||
};
|
||||
messages.push(message);
|
||||
} else {
|
||||
console.error('getVehPos() vehiclePos NOT available');
|
||||
console.error('ERROR: parseMessage() entity NOT known');
|
||||
}
|
||||
});
|
||||
//console.log('parseMessages() done.');
|
||||
//console.log('parseMessages() countEntityAlert: ' + countEntityAlert);
|
||||
//console.log('parseMessages() countEntityShape: ' + countEntityShape);
|
||||
//console.log('parseMessages() countEntityTripUpdate: ' + countEntityTripUpdate);
|
||||
//console.log('parseMessages() countEntityVehicle: ' + countEntityVehicle);
|
||||
////console.log('parseMessages() done.');
|
||||
return messages;
|
||||
};
|
||||
|
||||
/**
|
||||
* read pbf file
|
||||
* @return array of GTFS RT entities
|
||||
*/
|
||||
export function readPbf(buffer){
|
||||
//console.log('readPbfs() start...');
|
||||
const messages = [];
|
||||
const pbf = new Pbf(buffer);
|
||||
const feed = FeedMessage.read(pbf);
|
||||
//console.log('readPbfs() length: ' + feed.entity.length);
|
||||
//console.log('readPbfs() done.');
|
||||
return feed.entity;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
const selectOptions = [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000];
|
||||
module.exports = {
|
||||
selectOptions
|
||||
};
|
|
@ -38,6 +38,14 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
|
@ -2,7 +2,7 @@
|
|||
"private": true,
|
||||
"name": "gtfs-rt-display",
|
||||
"description": "display data from GTFS Realtime feeds",
|
||||
"version": "0.3.0",
|
||||
"version": "0.6.0",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"public",
|
||||
|
@ -13,40 +13,42 @@
|
|||
"display"
|
||||
],
|
||||
"author": "Software Ingenieur Begerad <dialog@SwIngBe.de>",
|
||||
"homepage": "https://github.com/Software-Ingenieur-Begerad/gtfs-rt-display/tree/main",
|
||||
"repository": "https://github.com/Software-Ingenieur-Begerad/gtfs-rt-display",
|
||||
"bugs": "https://github.com/Software-Ingenieur-Begerad/gtfs-rt-display/issues",
|
||||
"homepage": "https://www.swingbe.de/activity/gtfs-rt-display/",
|
||||
"repository": "https://git.wtf-eg.de/dancesWithCycles/gtfs-rt-display",
|
||||
"bugs": "https://git.wtf-eg.de/dancesWithCycles/gtfs-rt-display/issues",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": "<=18.17.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve --config config/webpack.dev.js",
|
||||
"build": "webpack --config config/webpack.prod.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.19.1",
|
||||
"@babel/preset-env": "7.19.1",
|
||||
"@babel/preset-react": "7.18.6",
|
||||
"babel-loader": "8.2.5",
|
||||
"css-loader": "6.7.1",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"style-loader": "3.3.1",
|
||||
"@babel/core": "7.22.10",
|
||||
"@babel/preset-env": "7.22.10",
|
||||
"@babel/preset-react": "7.22.5",
|
||||
"babel-loader": "9.1.3",
|
||||
"css-loader": "6.8.1",
|
||||
"file-loader": "6.2.0",
|
||||
"html-webpack-plugin": "5.5.3",
|
||||
"style-loader": "3.3.2",
|
||||
"svg-url-loader": "8.0.0",
|
||||
"webpack": "5.74.0",
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.11.0",
|
||||
"webpack-merge": "5.8.0"
|
||||
"webpack": "5.88.2",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"webpack-merge": "5.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.27.2",
|
||||
"bootstrap": "5.2.1",
|
||||
"leaflet": "1.8.0",
|
||||
"pbf": "^3.2.1",
|
||||
"axios": "1.4.0",
|
||||
"bootstrap": "5.3.1",
|
||||
"leaflet": "1.9.4",
|
||||
"pbf": "3.2.1",
|
||||
"react": "18.2.0",
|
||||
"react-bootstrap": "2.5.0",
|
||||
"react-bootstrap": "2.8.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-leaflet": "4.0.2",
|
||||
"react-leaflet": "4.2.1",
|
||||
"react-leaflet-cluster": "2.1.0",
|
||||
"react-router-bootstrap": "0.26.2"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue