Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
dancingCycle | e2d19d0ce6 | |
dancingCycle | 5eb046c5e6 | |
dancingCycle | 2e1bb7b166 | |
dancingCycle | 142086e3d0 | |
dancingCycle | c5c23f1161 | |
dancingCycle | 7001733350 | |
dancingCycle | 19595e10e3 | |
dancingCycle | 7b5ea82f65 | |
dancingCycle | 4b4131902a | |
dancingCycle | 7cbb31bdd3 | |
dancingCycle | f0552030c5 | |
dancingCycle | 25b139ddd7 | |
dancingCycle | d13f28d79e | |
dancingCycle | 134dcbcd8b | |
dancingCycle | 72815f7cbd | |
dancingCycle | 9ff992b39a | |
dancingCycle | c5cd44e632 | |
dancingCycle | d67e967c7a | |
dancingCycle | 5ed587f0ce | |
dancingCycle | ac91c2b741 | |
dancingCycle | 6d94dc0732 | |
dancingCycle | 82b92eac86 | |
dancingCycle | b5e4f567a3 |
|
@ -5,10 +5,10 @@ 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 Table from './pages/table-page';
|
||||
import TripUpdates from './pages/trip-updates';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
|
@ -20,9 +20,9 @@ export default function App() {
|
|||
<NavBar />
|
||||
<Routes>
|
||||
<Route path="/" element={<Map />} />
|
||||
<Route path="/table" element={<Table />} />
|
||||
<Route path="/map" element={<Map />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
<Route path="/map" element={<Map />} />
|
||||
<Route path="/trip-updates" element={<TripUpdates />} />
|
||||
</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
|
||||
};
|
|
@ -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>
|
||||
<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,124 @@
|
|||
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';
|
||||
|
||||
/*the simplest way to define a component is to write a JavaScript function*/
|
||||
/*destructure props object*/
|
||||
export default function TripUpdatesTable ({isFetched, oset, limit, filter}) {
|
||||
////console.log('TripUpdatesTable: oset: ' + oset);
|
||||
//////console.log('TripUpdatesTable: limit: ' + limit);
|
||||
|
||||
const [ary, setAry] = useState([]);
|
||||
const [aryFiltered, setAryFiltered] = useState([]);
|
||||
const [fetchCompleted, setFetchCompleted] = useState(isFetched);
|
||||
/*get arry in a JavaScript function*/
|
||||
const getRry = async () => {
|
||||
try {
|
||||
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
|
||||
const address = `${config.API}gtfs-rt`;
|
||||
const res = await axios.get(address,
|
||||
{
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
|
||||
if(res.data){
|
||||
////console.log('getRry() res available');
|
||||
const rry = readPbf(res.data);
|
||||
const rryLngth= rry.length;
|
||||
////console.log('getRry() rryLngh: ' + rryLngth);
|
||||
|
||||
const rryETU = new Array();
|
||||
let countEntityTripUpdate = 0;
|
||||
rry.forEach(entity => {
|
||||
const entityTripUpdate = entity.trip_update;
|
||||
if (entityTripUpdate) {
|
||||
rryETU.push(entityTripUpdate);
|
||||
countEntityTripUpdate++;
|
||||
}
|
||||
});
|
||||
////console.log('getRry() countEntityTripUpdate: ' + countEntityTripUpdate);
|
||||
|
||||
const rryOstLmt = new Array();
|
||||
let j = 0
|
||||
for(let i = (oset - 1) * limit; i < rryLngth ; i++){
|
||||
if(j < limit){
|
||||
rryOstLmt.push(rryETU[i]);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
const rryOstLmtLngth = rryOstLmt.length;
|
||||
////console.log('getRry() rryOstLmtLngh: ' + rryOstLmtLngth);
|
||||
|
||||
setAry((ary) => rryOstLmt);
|
||||
//TODO let data=filterData(res.data,'tdb_stops',filter);
|
||||
//TODO setAryFiltered((aryFiltered) => data);
|
||||
setAryFiltered((aryFiltered) => rryOstLmt);
|
||||
}else{
|
||||
console.error('getRry() res NOT available');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('err.message: ' + err.message);
|
||||
setAry((ary) => []);
|
||||
setAryFiltered((aryFiltered) => []);
|
||||
}
|
||||
};
|
||||
useEffect(()=>{
|
||||
setAryFiltered((aryFiltered)=>{
|
||||
let filtered=filterData(ary,'tdb_stops',filter);
|
||||
return filtered;
|
||||
});
|
||||
},[filter]);
|
||||
useEffect(()=>{
|
||||
setFetchCompleted((fetchCompleted)=>true);
|
||||
}, [ary]);
|
||||
useEffect(() => {
|
||||
/*effect goes here*/
|
||||
getRry();
|
||||
/*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 (
|
||||
<>
|
||||
<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://www.v1gtfs-rt-p.api.swingbe.de/'
|
||||
};
|
|
@ -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,40 +1,53 @@
|
|||
import React, {useEffect,useState} from 'react';
|
||||
import axios from 'axios';
|
||||
import Alert from 'react-bootstrap/Alert';
|
||||
import Badge from 'react-bootstrap/Badge';
|
||||
|
||||
import Map from '../components/map/map';
|
||||
import parseMessages from '../utils/gtfs-rt-utils';
|
||||
import {parseMessages, readPbf} from '../utils/gtfs-rt-utils';
|
||||
import config from '../config';
|
||||
|
||||
export default function MapPage() {
|
||||
/*storage*/
|
||||
const [vehPos, setVehPos] = useState([]);
|
||||
const getData= async ()=>{
|
||||
//console.log('getData() start...');
|
||||
////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,
|
||||
const address = `${config.API}gtfs-rt`;
|
||||
const res = await axios.get(address,
|
||||
{
|
||||
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);
|
||||
//console.log('getRry() res available');
|
||||
const rry = readPbf(res.data);
|
||||
const rryLngth= rry.length;
|
||||
//console.log('getRry() rryLngh: ' + rryLngth);
|
||||
|
||||
const rryEVP = new Array();
|
||||
let countEntityVehiclePositions = 0;
|
||||
rry.forEach(entity => {
|
||||
const entityVehiclePosition = entity.vehicle;
|
||||
if (entityVehiclePosition) {
|
||||
rryEVP.push(entityVehiclePosition);
|
||||
countEntityVehiclePositions++;
|
||||
}
|
||||
});
|
||||
//console.log('getRry() countEntityVehiclePositions: ' + countEntityVehiclePositions);
|
||||
|
||||
/*set state*/
|
||||
setVehPos(messages);
|
||||
setVehPos((messages) => rryEVP);
|
||||
}else{
|
||||
console.error('getData() res NOT available');
|
||||
console.error('getRry() res NOT available');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('err.message: ' + err.message);
|
||||
}
|
||||
//console.log('getData() done.');
|
||||
////console.log('getData() done.');
|
||||
};
|
||||
|
||||
useEffect(()=>{
|
||||
|
@ -51,9 +64,19 @@ export default function MapPage() {
|
|||
clearInterval(intervalCall);
|
||||
};
|
||||
},[]);
|
||||
|
||||
if (vehPos.length < 0) {
|
||||
return (
|
||||
<>
|
||||
<Map messages={vehPos}/>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Alert variant={'secondary'} onClose={() => setShow(false)} dismissible>
|
||||
<Badge bg="secondary">VehiclePostion</Badge> entities loading...
|
||||
</Alert>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React, {useEffect,useState} from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import parseMessages from '../utils/gtfs-rt-utils';
|
||||
import {parseMessages, readPbf} from '../utils/gtfs-rt-utils';
|
||||
import Table from '../components/table/table';
|
||||
|
||||
export default function TablePage() {
|
||||
const [vehPos, setVehPos] = useState([]);
|
||||
const getData= async ()=>{
|
||||
|
@ -10,13 +11,18 @@ export default function TablePage() {
|
|||
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';
|
||||
//let url = 'https://api.entur.io/realtime/v1/gtfs-rt/vehicle-positions';
|
||||
//let url = 'http://localhost:65533/gtfs-rt';
|
||||
let url = 'https://www.v1gtfs-rt-p.api.swingbe.de/gtfs-rt'
|
||||
|
||||
const res = await axios.get(url,
|
||||
{
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
if(res.data){
|
||||
//console.log('getData() res available');
|
||||
/*parse messages*/
|
||||
readPbf(res.data);
|
||||
const messages = parseMessages(res.data);
|
||||
//console.log('getData() messages.length: '+messages.length);
|
||||
setVehPos(messages);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import TripUpdatesPage from '../components/trip-updates-page';
|
||||
export default function TripUpdates() {
|
||||
return (
|
||||
<>
|
||||
<TripUpdatesPage />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,205 @@
|
|||
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 'tokens':
|
||||
console.log('filterData() //TODO implement 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
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
NODE_ENV=development
|
||||
#NODE_ENV=production
|
||||
PORT=65532
|
||||
URL=http://gtfsr.vbn.de/gtfsr_connect.bin
|
|
@ -0,0 +1,4 @@
|
|||
NODE_ENV=development
|
||||
#NODE_ENV=production
|
||||
PORT=65532
|
||||
URL=https://gtfs-rt-test.vrsinfo.de:4443/buffer/tripUpdate.buf
|
|
@ -0,0 +1,174 @@
|
|||
const db = require('./src/db');
|
||||
const protoBuf = require('pbf');
|
||||
const debug = require('debug')('index');
|
||||
require('dotenv').config();
|
||||
|
||||
const gtfsRt = require('../../proto2js/js/gtfs-rt.js');
|
||||
|
||||
const gtfsrFetch = require('./src/fetch');
|
||||
const tUO = require('./src/trip-update-obj');
|
||||
|
||||
const timeoutScnds = 60;
|
||||
|
||||
async function timeoutFct()
|
||||
{
|
||||
console.log('index:setInterval start...');
|
||||
|
||||
//initialize array of trip update objects
|
||||
const rryOfTripUpdateObjcs = new Array();
|
||||
|
||||
//fetch GTFS Realtime feed and store trip updates as array of objects
|
||||
let buffer = null
|
||||
buffer = await gtfsrFetch.fetch();
|
||||
|
||||
if (buffer !== null && buffer !== undefined) {
|
||||
//TODO clean up debug('index:intervalFunc(): buffer available');
|
||||
const FeedMessage = gtfsRt.FeedMessage;
|
||||
const prtBf = new protoBuf(buffer);
|
||||
const feed = FeedMessage.read(prtBf);
|
||||
let countTrip = 0;
|
||||
let countTripId = 0;
|
||||
let countRouteId = 0;
|
||||
let countScheduleRelationship = 0;
|
||||
let countVehicle = 0;
|
||||
let countTimestamp = 0;
|
||||
let countDelay = 0;
|
||||
feed.entity.forEach(entity => {
|
||||
const entityTripUpdate = entity.trip_update;
|
||||
if (entityTripUpdate !== null && entityTripUpdate !== undefined) {
|
||||
const tuo = new tUO.TripUpdate();
|
||||
const trip = entityTripUpdate.trip;
|
||||
if (trip !== null && trip !== undefined) {
|
||||
countTrip++
|
||||
const t = new tUO.Trip();
|
||||
const tripId = trip.trip_id;
|
||||
if (tripId !== null && tripId !== undefined) {
|
||||
countTripId++;
|
||||
t.tripId = tripId;
|
||||
}
|
||||
const routeId = trip.route_id;
|
||||
if (routeId !== null && routeId !== undefined) {
|
||||
countRouteId++;
|
||||
t.routeId = routeId;
|
||||
}
|
||||
const scheduleRelationship = trip.schedule_relationship;
|
||||
if (scheduleRelationship !== null && scheduleRelationship !== undefined) {
|
||||
countScheduleRelationship++;
|
||||
t.scheduleRelationship = scheduleRelationship;
|
||||
}
|
||||
//create a copy of an object, you can use the spread operator
|
||||
tuo.trip = {...t};
|
||||
}
|
||||
const vehicle = entityTripUpdate.vehicle;
|
||||
if (vehicle !== null && vehicle !== undefined) {
|
||||
countVehicle++;
|
||||
}
|
||||
const timestamp = entityTripUpdate.timestamp;
|
||||
if (timestamp !== null && timestamp !== undefined) {
|
||||
countTimestamp++;
|
||||
tuo.timestamp = timestamp;
|
||||
}
|
||||
const delay = entityTripUpdate.delay;
|
||||
if (delay !== null && delay !== undefined) {
|
||||
countDelay++;
|
||||
tuo.delay = delay;
|
||||
}
|
||||
rryOfTripUpdateObjcs.push({...tuo});
|
||||
}
|
||||
});
|
||||
debug('index:intervalFunc(): rryOfTripUpdateObjcs.length: ' + rryOfTripUpdateObjcs.length);
|
||||
//TODO clean up
|
||||
/*
|
||||
debug('index:intervalFunc(): countTrip: ' + countTrip);
|
||||
debug('index:intervalFunc(): countTripId: ' + countTripId);
|
||||
debug('index:intervalFunc(): countRouteId: ' + countRouteId);
|
||||
debug('index:intervalFunc(): countScheduleRelationship: ' + countScheduleRelationship);
|
||||
debug('index:intervalFunc(): countVehicle: ' + countVehicle);
|
||||
debug('index:intervalFunc(): countTimeStamp: ' + countTimestamp);
|
||||
debug('index:intervalFunc(): countDelay: ' + countDelay);
|
||||
*/
|
||||
} else {
|
||||
console.error('ERROR: index:intervalFunc(): buffer NOT available');
|
||||
}
|
||||
|
||||
//select trip updates of today from db as array of objects
|
||||
const schema = process.env.DB_SCHEMA || 'schema';
|
||||
|
||||
//TODO some GTFSR feeds do not provide timestamp const query = 'SELECT * FROM ' + schema + '.trip_updates WHERE timestamp_gtfsr >= current_date';
|
||||
//NOTE: https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
||||
const query = 'SELECT * FROM ' + schema + '.trip_updates WHERE timestamp_pgsql >= current_date';
|
||||
|
||||
//TODO clean up debug('query: '+query);
|
||||
const rsp = await db.query(query);
|
||||
debug('rsp.length: '+rsp.length);
|
||||
if (rsp !== null && rsp !== undefined && rsp.length < 0) {
|
||||
//TODO clean up
|
||||
debug('rsp[0]: ' + JSON.stringify(rsp[0]));
|
||||
}
|
||||
|
||||
//TODO only continue if rsp is neither null or undefined
|
||||
|
||||
//transform array of objects into map [trip_trip_id, trip update object]
|
||||
//TODO handle rsp NOT available
|
||||
const mapDbTripUpdates = new Map();
|
||||
rsp.forEach(element => {
|
||||
mapDbTripUpdates.set(element.trip_trip_id, element);
|
||||
});
|
||||
|
||||
debug('mapDbTripUpdates.size: ' + mapDbTripUpdates.size);
|
||||
|
||||
//compare map from db with array of trip update objcs from GTFS Realtime feed
|
||||
//TODO clean up
|
||||
let countRryElem = 0;
|
||||
let countInsert = 0;
|
||||
//TODO https://stackoverflow.com/a/37576787/15078958
|
||||
//TODO according to this link forEach() has challenges for async/await
|
||||
for (const element of rryOfTripUpdateObjcs) {
|
||||
const value = mapDbTripUpdates.get(element.trip.tripId);
|
||||
let query = '';
|
||||
let rspPsql = null;
|
||||
|
||||
if (value === undefined) {
|
||||
query = `INSERT INTO `
|
||||
+ schema
|
||||
+ `.trip_updates (trip_trip_id, trip_route_id, trip_schedule_relationship, timestamp_gtfsr, delay) VALUES ('`
|
||||
+ element.trip.tripId
|
||||
+ `', '`
|
||||
+ element.trip.routeId
|
||||
+ `', '`
|
||||
+ element.trip.scheduleRelationship
|
||||
+ `', to_timestamp(`
|
||||
+ element.timestamp
|
||||
+ `), '`
|
||||
+ element.delay
|
||||
+ `');`;
|
||||
rspPsql = await db.query(query);
|
||||
countInsert++;
|
||||
|
||||
if (countRryElem === 0) {
|
||||
debug('value: ' + JSON.stringify(value));
|
||||
debug('query: ' + query);
|
||||
debug('respDb: ' + JSON.stringify(rspPsql));
|
||||
}
|
||||
countRryElem++;
|
||||
}
|
||||
}
|
||||
|
||||
debug('countInsert: ' + countInsert);
|
||||
|
||||
//other
|
||||
console.log('index:setInterval done.');
|
||||
|
||||
setTimeout(timeoutFct, timeoutScnds * 1000);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
debug('index:run(): started...');
|
||||
|
||||
timeoutFct();
|
||||
};
|
||||
|
||||
run().catch(err => {
|
||||
console.error('ERROR: ');
|
||||
console.log(err)
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "gtfsr-dp-be",
|
||||
"version": "1.0.0",
|
||||
"description": "GTFS Realtime Display Back End",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon index.js",
|
||||
"start": "node index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Software Ingenieur Begerad <dialog@SwIngBe.de>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"axios": "1.6.2",
|
||||
"debug": "4.3.4",
|
||||
"dotenv": "16.3.1",
|
||||
"pbf": "3.2.1",
|
||||
"pg": "8.11.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "3.0.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
# Overview
|
||||
|
||||
# General
|
||||
|
||||
Requirements:
|
||||
|
||||
* Node.js <= 18.13.0
|
||||
|
||||
# Preparation
|
||||
|
||||
```
|
||||
npm i
|
||||
```
|
||||
|
||||
# Development Setup
|
||||
|
||||
```
|
||||
DEBUG=config,debug,gtfsr,index npm run dev
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
const DEBUG=require('debug')('config');
|
||||
DEBUG('config start...');
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const config = {
|
||||
db: { /* do not put password or any sensitive info here, done only for demo */
|
||||
host: process.env.DB_HOST || 'host',
|
||||
port: process.env.DB_PORT || '5432',
|
||||
user: process.env.DB_USER || 'usr',
|
||||
password: process.env.DB_PASSWORD || 'key',
|
||||
database: process.env.DB_NAME || 'db',
|
||||
},
|
||||
listPerPage: process.env.LIST_PER_PAGE || 10,
|
||||
};
|
||||
|
||||
DEBUG('config host: '+config.db.host);
|
||||
DEBUG('config port: '+config.db.port);
|
||||
DEBUG('config user: '+config.db.user);
|
||||
DEBUG('config database: '+config.db.database);
|
||||
module.exports = config;
|
||||
DEBUG('config done.');
|
|
@ -0,0 +1,22 @@
|
|||
const { Pool } = require('pg');
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
const pool = new Pool(config.db);
|
||||
|
||||
/**
|
||||
* Query the database using the pool
|
||||
* @param {*} query
|
||||
* @param {*} params
|
||||
*
|
||||
* @see https://node-postgres.com/features/pooling#single-query
|
||||
*/
|
||||
async function query(query, params) {
|
||||
const {rows, fields} = await pool.query(query, params);
|
||||
|
||||
return rows;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
query
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
const axios=require('axios');
|
||||
require('dotenv').config()
|
||||
|
||||
async function fetch() {
|
||||
let rsp = null;
|
||||
try {
|
||||
|
||||
//TODO switch from Connect to Mobilithek
|
||||
const URL = process.env.URL || 'http://localhost:65535/fetch.bin';
|
||||
|
||||
const config = {
|
||||
responseType: 'arraybuffer'
|
||||
//responseType: 'blob'
|
||||
};
|
||||
|
||||
rsp = await axios.get(URL, config);
|
||||
if (rsp === null || rsp === undefined) {
|
||||
console.error('ERROR: fetch(): rsp NOT available');
|
||||
//} else {
|
||||
//NOTE: Use byteLength to check the size
|
||||
//const bytes = rsp.data.byteLength;
|
||||
//console.log('fetch:fetch() rsp length in bytes: ' + bytes);
|
||||
}
|
||||
|
||||
|
||||
} catch (err) {
|
||||
console.error('ERROR: fetch:fetch(): ', err.message);
|
||||
}
|
||||
return rsp.data;
|
||||
};
|
||||
|
||||
module.exports={
|
||||
fetch
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
//Object Constructor
|
||||
function Trip(tripId = '', routeId = '', scheduleRelationship = '') {
|
||||
this.tripId = tripId;
|
||||
this.routeId = routeId;
|
||||
this.scheduleRelationship = scheduleRelationship;
|
||||
};
|
||||
|
||||
|
||||
//Object Constructor
|
||||
function TripUpdate(trip = null, timestamp = 0, delay = 0) {
|
||||
this.trip = trip;
|
||||
this.timestamp = timestamp;
|
||||
this.delay = delay;
|
||||
};
|
||||
|
||||
module.exports={
|
||||
Trip,
|
||||
TripUpdate
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
echo "Started..."
|
||||
#
|
||||
# special variable $# is the number of arguments
|
||||
if [ $# -lt 3 ] ; then
|
||||
echo 'Call ./<script> <db name> <db user> <db schema>'
|
||||
exit 1
|
||||
fi
|
||||
#
|
||||
DB_NAME="$1"
|
||||
echo "DB_NAME: ${DB_NAME}"
|
||||
DB_USER="$2"
|
||||
echo "DB_USER: ${DB_USER}"
|
||||
DB_SCHEMA="$3"
|
||||
echo "DB_SCHEMA: ${DB_SCHEMA}"
|
||||
#
|
||||
psql -h localhost -p 5432 -U $DB_USER -f ./sql/trip-updates.sql -d $DB_NAME -v schema=$DB_SCHEMA
|
||||
#
|
||||
echo "Done."
|
|
@ -0,0 +1,20 @@
|
|||
-- colon before variable: for a prepared statement using named placeholders, this will be a parameter name of the form :name
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS :schema;
|
||||
SET search_path to :schema, public;
|
||||
|
||||
-- create table
|
||||
DROP TABLE IF EXISTS :schema.trip_updates CASCADE;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS :schema.trip_updates
|
||||
(
|
||||
trip_trip_id text NULL,
|
||||
trip_route_id text NULL,
|
||||
trip_schedule_relationship text NULL,
|
||||
vehicle_id text NULL,
|
||||
vehicle_label text NULL,
|
||||
vehicle_license_plate text NULL,
|
||||
timestamp_gtfsr timestamptz NULL,
|
||||
timestamp_pgsql timestamptz NOT NULL DEFAULT now(),
|
||||
delay int
|
||||
);
|
File diff suppressed because it is too large
Load Diff
22
package.json
22
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.4.0",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"public",
|
||||
|
@ -13,12 +13,12 @@
|
|||
"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/dancingCycle/gtfs-rt-display",
|
||||
"bugs": "https://git.wtf-eg.de/dancingCycle/gtfs-rt-display/issues",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": "<=18.13.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve --config config/webpack.dev.js",
|
||||
|
@ -39,14 +39,14 @@
|
|||
"webpack-merge": "5.8.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-router-bootstrap": "0.26.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"extends":
|
||||
[
|
||||
"config:base"
|
||||
],
|
||||
"timezone": "Europe/Berlin",
|
||||
"packageRules":
|
||||
[
|
||||
{
|
||||
"matchDepTypes": ["devDependencies"],
|
||||
"matchPackagePatterns": ["lint","prettier"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"matchUpdateTypes": ["minor","patch"],
|
||||
"matchCurrentVersion": "!/^0/",
|
||||
"automerge": true
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue