diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..4f06b0c
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,6 @@
+{
+ "presets": [
+ "@babel/preset-env",
+ "@babel/preset-react"
+ ]
+}
diff --git a/app/app.jsx b/app/app.jsx
new file mode 100644
index 0000000..0e9e291
--- /dev/null
+++ b/app/app.jsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { BrowserRouter, Route, Routes } from 'react-router-dom';
+
+/*some stylesheet is required to use react-bootstrip components*/
+import 'bootstrap/dist/css/bootstrap.min.css';
+
+import Contact from './pages/contact';
+import Home from './pages/home';
+import Map from './pages/map-page';
+import NavBar from './components/nav-bar';
+
+export default function App() {
+ return (
+ //BrowserRouter is the router implementation for HTML5 browsers
+ //Link enables Routes on an anchor tag
+ //Switch returns only the first matching route rather than all
+ //Route is the conditionally shown component //based on matching a path to a URL
+
+
+
+ } />
+ } />
+ } />
+
+
+ );
+}
diff --git a/app/assets/Logo_SIB_electricindigo.svg b/app/assets/Logo_SIB_electricindigo.svg
new file mode 100644
index 0000000..458e73e
--- /dev/null
+++ b/app/assets/Logo_SIB_electricindigo.svg
@@ -0,0 +1,35 @@
+
+
+
diff --git a/app/components/map/icon-gtfs.js b/app/components/map/icon-gtfs.js
new file mode 100644
index 0000000..d36d809
--- /dev/null
+++ b/app/components/map/icon-gtfs.js
@@ -0,0 +1,35 @@
+import getIconUrl from './icon-url';
+
+/*return icon object*/
+export default function getIcon(ptByIfleet){
+ if(ptByIfleet===undefined || ptByIfleet===null){
+ //console.log('getIcon(): ptByIfleet NOT available')
+ return null;
+ }
+ else{
+ //console.log('getIcon(): ptByIfleet available')
+ const icon = new L.Icon({
+ /*path to icon graphic*/
+ iconUrl: getIconUrl(ptByIfleet.route_type),
+ /*path to graphic used for high resolution monitors*/
+ iconRetinaUrl: getIconUrl(ptByIfleet.route_type),
+ popupAnchor: [-0, -0],
+ /*size of the icon in width and hight*/
+ iconSize: [32,32],
+ /*determine how the popup is positions relative to the actual point on the map*/
+ popupAnchor:[0,-10],
+ /*determine how the image is positions relative to the actual point on the map*/
+ iconAnchor: null,
+ /*path to shadow graphic*/
+ shadowUrl: null,
+ /*size of the shadow in width and hight*/
+ shadowSize: null,
+ /*determine how the mage is positions relative to the actual point on the map*/
+ shadowAnchor: null,
+ className: 'marker-msg'
+ });
+ //console.log('getIcon(): icon available')
+ return icon;
+ }
+
+}
diff --git a/app/components/map/icon-url.js b/app/components/map/icon-url.js
new file mode 100644
index 0000000..30ca685
--- /dev/null
+++ b/app/components/map/icon-url.js
@@ -0,0 +1,14 @@
+import bfly from '../../assets/Logo_SIB_electricindigo.svg';
+import bus from '../../assets/bus.svg';
+import train from '../../assets/train.svg';
+import tram from '../../assets/tram.svg';
+
+/* return URL that fits GTFS route_type*/
+export default function getIconUrl(routeTypeEnum){
+ //console.log('getIconUrl() routeTypeEnum: '+routeTypeEnum);
+ return routeTypeEnum===0?tram:
+ routeTypeEnum===1?bfly:
+ routeTypeEnum===2?train:
+ routeTypeEnum===3?bus:
+ bfly;
+}
diff --git a/app/components/map/icon.js b/app/components/map/icon.js
new file mode 100644
index 0000000..3b24a5e
--- /dev/null
+++ b/app/components/map/icon.js
@@ -0,0 +1,28 @@
+import bfly from '../../assets/Logo_SIB_electricindigo.svg';
+
+/*return icon object*/
+export default function getIcon(){
+ //console.log('getIcon(): ptByIfleet available')
+ const icon = new L.Icon({
+ /*path to icon graphic*/
+ iconUrl: bfly,
+ /*path to graphic used for high resolution monitors*/
+ iconRetinaUrl: bfly,
+ popupAnchor: [-0, -0],
+ /*size of the icon in width and hight*/
+ iconSize: [32,32],
+ /*determine how the popup is positions relative to the actual point on the map*/
+ popupAnchor:[0,-10],
+ /*determine how the image is positions relative to the actual point on the map*/
+ iconAnchor: null,
+ /*path to shadow graphic*/
+ shadowUrl: null,
+ /*size of the shadow in width and hight*/
+ shadowSize: null,
+ /*determine how the mage is positions relative to the actual point on the map*/
+ shadowAnchor: null,
+ className: 'marker-msg'
+ });
+ //console.log('getIcon(): icon available')
+ return icon;
+}
diff --git a/app/components/map/map.css b/app/components/map/map.css
new file mode 100644
index 0000000..747a5bb
--- /dev/null
+++ b/app/components/map/map.css
@@ -0,0 +1,4 @@
+/*set up the height of*/
+.leaflet-container {
+ height: 85vh;
+}
diff --git a/app/components/map/map.jsx b/app/components/map/map.jsx
new file mode 100644
index 0000000..b79a271
--- /dev/null
+++ b/app/components/map/map.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {MapContainer,TileLayer} from 'react-leaflet';
+/*JS module import (vs cdn or style link)*/
+import 'leaflet/dist/leaflet.css'
+import './map.css';
+
+import MarkerElem from './marker-elem';
+
+export default function Map({elements}) {
+ /*lat and lon of Braunschweig,DE*/
+ const position = [52.26594, 10.52673]
+ //TODO make this switch available via configuration!
+ const hasGtfs = false;
+ return (
+ <>
+
+
+ {
+ elements.map(function(value,key) {
+ //console.log(`key: ${key}, tripId: ${value.tripId}`);
+ return ;
+ })
+ }
+
+ >
+ );
+}
+Map.propTypes = {
+ elements: PropTypes.array
+};
diff --git a/app/components/map/marker-elem-plus.jsx b/app/components/map/marker-elem-plus.jsx
new file mode 100644
index 0000000..140cd71
--- /dev/null
+++ b/app/components/map/marker-elem-plus.jsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {Marker} from 'react-leaflet';
+
+import PopupElem from './popup-elem';
+import getIcon from './icon';
+
+export default function MarkerElemPlus({element}){
+ if(element===undefined || element===null){
+ console.error('element undefined or null');
+ return null;
+ }else{
+ const markerIcon=getIcon();
+ if(markerIcon===null){
+ //TODO Handle issue!
+ console.error('ERROR: icon null');
+ return null;
+ }else if(element.lat===undefined||element.lat===null){
+ //TODO Handle issue!
+ console.error('ERROR: element lat undefined or null');
+ return null;
+ }else if(element.lon===undefined||element.lon===null){
+ //TODO Handle issue!
+ console.error('ERROR: element lon undefined or null');
+ return null;
+ }else{
+ return(
+ <>
+
+
+
+ >
+ );
+ }
+ }
+};
+MarkerElemPlus.propTypes = {
+ element: PropTypes.object
+};
diff --git a/app/components/map/marker-elem.jsx b/app/components/map/marker-elem.jsx
new file mode 100644
index 0000000..e1dd191
--- /dev/null
+++ b/app/components/map/marker-elem.jsx
@@ -0,0 +1,22 @@
+import React, {useEffect,useState} from 'react';
+import PropTypes from 'prop-types';
+
+import MarkerElemPlus from './marker-elem-plus';
+
+export default function MarkerElem({ element }){
+ if(element===undefined || element===null){
+ console.error('element undefined or null');
+ return null;
+ }else{
+ return(
+ <>
+
+ >
+ );
+ }
+};
+MarkerElem.propTypes = {
+ element: PropTypes.object
+};
diff --git a/app/components/map/popup-elem.jsx b/app/components/map/popup-elem.jsx
new file mode 100644
index 0000000..d595c80
--- /dev/null
+++ b/app/components/map/popup-elem.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {Popup} from 'react-leaflet';
+
+export default function PopupElem({element}){
+ return (
+ <>
+
+ id: {element.id}
+ name: {element.name}
+ lat: {element.lat}
+ lon: {element.lon}
+
+ >
+ );
+};
+PopupElem.propTypes = {
+ element: PropTypes.object
+};
diff --git a/app/components/nav-bar.js b/app/components/nav-bar.js
new file mode 100644
index 0000000..6f519b7
--- /dev/null
+++ b/app/components/nav-bar.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import { Navbar, Nav } from 'react-bootstrap';
+import { LinkContainer } from 'react-router-bootstrap';
+
+function NavigationBar () {
+ return (
+
+ //TODO make brand available through configuration
+ GOMPASS
+
+
+
+
+
+
+ );
+}
+
+export default NavigationBar;
diff --git a/app/index.jsx b/app/index.jsx
new file mode 100644
index 0000000..6a8e2cd
--- /dev/null
+++ b/app/index.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './app';
+//TODO remove debugging
+if (process.env.NODE_ENV !== 'production') {
+ console.log('development mode');
+}
+const container = document.getElementById('root');
+const root = createRoot(container);
+root.render(
+
+
+
+);
diff --git a/app/pages/contact.js b/app/pages/contact.js
new file mode 100644
index 0000000..e5fab5b
--- /dev/null
+++ b/app/pages/contact.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import packageInfo from '../../package.json'
+const VERSION = packageInfo.version;
+const Contact = () => {
+ return (
+ <>
+
About this website
+
+
+ For questions about this website please do not hesitate to reach out to dialog (at) swingbe (dot) de.
+
+ Imprint
+
+
+ Stefan Begerad, Software Ingenieur Begerad
+
+
+ c/o WTF Kooperative eG, Hinterhaus, 3. OG
+
+ Forsmannstr. 14b
+
+ 22303 Hamburg
+
+ Deutschland
+
+
+ Other
+
+ Version: {VERSION}
+
+ >
+ );
+};
+export default Contact;
diff --git a/app/pages/home.jsx b/app/pages/home.jsx
new file mode 100644
index 0000000..4537be5
--- /dev/null
+++ b/app/pages/home.jsx
@@ -0,0 +1,8 @@
+import React from 'react';
+export default function Home() {
+ return (
+ <>
+ Home
+ >
+ );
+}
diff --git a/app/pages/map-page.jsx b/app/pages/map-page.jsx
new file mode 100644
index 0000000..0044782
--- /dev/null
+++ b/app/pages/map-page.jsx
@@ -0,0 +1,96 @@
+import React, {useEffect,useState} from 'react';
+import axios from 'axios';
+import qs from 'qs';
+
+import Map from '../components/map/map';
+
+const data = qs.stringify(
+ {
+ 'data': '[bbox:51.990800124058914,10.045623779296875,52.552976197007524,11.01104736328125][out:json];(node["shop"="second_hand"];way["shop"="second_hand"];relation["shop"="second_hand"];);out center;'
+ }
+);
+
+//TODO Make fields available via configuration!
+const interval=3600000;
+const opInstance='https://overpass-api.de/api/interpreter';
+const opInstanceZ='https://z.overpass-api.de/api/interpreter';
+const config = {
+ method: 'post',
+ maxBodyLength: Infinity,
+ url: opInstanceZ,
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ data : data
+};
+
+function parseOsmElem(response){
+ let elemAry=[];
+ if(response.data){
+ let elements=response.data.elements;
+ let no=elements.length;
+ //console.log('no: '+no);
+ for(let i = 0; i < no; i++) {
+ let elemObj={};
+ elemObj.id=elements[i].id;
+ elemObj.name=elements[i].tags.name;
+ if(elements[i].tags.name===undefined){
+ //TODO Handle issue!
+ console.error('ERROR: OSM element does NOT include name tag');
+ }else if(elements[i].type==='node'){
+ elemObj.lat=elements[i].lat;
+ elemObj.lon=elements[i].lon;
+ }else if(elements[i].type==='way'){
+ elemObj.lat=elements[i].center.lat;
+ elemObj.lon=elements[i].center.lon;
+ }else{
+ console.error('ERROR: OSM element NOT supported');
+ }
+ //console.log('elem name: '+elemObj.name);
+ //console.log('elem lat: '+elemObj.lat);
+ //console.log('elem lon: '+elemObj.lon);
+ elemAry.push(elemObj);
+ //console.error('elemAry no: '+elemAry.length);
+ }
+ }else{
+ console.error('ERROR: response NOT available');
+ }
+ return elemAry;
+};
+
+export default function MapPage() {
+ /*storage*/
+ const [ary, setAry] = useState([]);
+ const getData= async ()=>{
+ //console.log('getData() start...');
+ try {
+ /*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
+ const res = await axios(config);
+ let elemAry=parseOsmElem(res);
+ setAry((ary) => elemAry);
+ } catch (err) {
+ console.error('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();
+ }, interval);
+ /*TODO adjust interval, make it available via config file*/
+ return ()=>{
+ /*clean up*/
+ clearInterval(intervalCall);
+ };
+ },[]);
+ return (
+ <>
+