diff --git a/ogn/backend/__init__.py b/ogn/backend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ogn/backend/liveglidernet.py b/ogn/backend/liveglidernet.py
new file mode 100644
index 0000000..e4d4e11
--- /dev/null
+++ b/ogn/backend/liveglidernet.py
@@ -0,0 +1,93 @@
+from datetime import datetime, timedelta, date
+import os
+
+from sqlalchemy import func, and_, between, case, null
+
+from ogn.model import AircraftBeacon, DeviceInfo, Device, Receiver
+
+
+def rec(session):
+ last_10_minutes = datetime.utcnow() - timedelta(minutes=10)
+ receiver_query = session.query(Receiver,
+ case([(Receiver.lastseen > last_10_minutes, True)],
+ else_=False).label('is_online')) \
+ .order_by(Receiver.name)
+
+ lines = list()
+ lines.append('')
+ lines.append('')
+ lines.append('')
+ for [receiver, is_online] in receiver_query.all():
+ lines.append(''
+ .format(receiver.name, receiver.location.latitude, receiver.location.longitude, is_online))
+
+ lines.append('')
+ xml = '\n'.join(lines)
+
+ return xml
+
+
+def lxml(session, show_offline=False, lat_max=90, lat_min=-90, lon_max=180, lon_min=-180):
+
+ if show_offline:
+ observation_start = date.today()
+ else:
+ observation_start = datetime.utcnow() - timedelta(minutes=5)
+
+ position_query = session.query(Device, AircraftBeacon, DeviceInfo) \
+ .filter(and_(between(func.ST_Y(AircraftBeacon.location_wkt), lat_min, lat_max),
+ between(func.ST_X(AircraftBeacon.location_wkt), lon_min, lon_max))) \
+ .filter(Device.lastseen > observation_start) \
+ .filter(Device.last_position_beacon_id == AircraftBeacon.id) \
+ .outerjoin(DeviceInfo, DeviceInfo.device_id == Device.id)
+
+ lines = list()
+ lines.append('')
+ lines.append('')
+
+ for [aircraft_beacon, device_info] in position_query.all():
+ if device_info and (not device_info.tracked or not device_info.identified):
+ continue
+
+ code = encode(aircraft_beacon.address)
+
+ if device_info is None:
+ competition = ('_' + code[-2:]).lower()
+ registration = code
+ address = 0
+ else:
+ if not device_info.competition:
+ competition = device_info.registration[-2:]
+ else:
+ competition = device_info.competition
+
+ if not device_info.registration:
+ registration = '???'
+ else:
+ registration = device_info.registration
+
+ address = device_info.address
+
+ elapsed_time = datetime.utcnow() - aircraft_beacon.timestamp
+ elapsed_seconds = int(elapsed_time.total_seconds())
+
+ lines.append(''
+ .format(aircraft_beacon.location.latitude,
+ aircraft_beacon.location.longitude,
+ competition,
+ registration,
+ aircraft_beacon.altitude,
+ utc_to_local(aircraft_beacon.timestamp).strftime("%H:%M:%S"),
+ elapsed_seconds,
+ int(aircraft_beacon.track),
+ int(aircraft_beacon.ground_speed),
+ int(aircraft_beacon.climb_rate*10)/10,
+ aircraft_beacon.aircraft_type,
+ aircraft_beacon.receiver_name,
+ address,
+ code))
+
+ lines.append('')
+ xml = '\n'.join(lines)
+
+ return xml
diff --git a/ogn/backend/ognrange.py b/ogn/backend/ognrange.py
new file mode 100644
index 0000000..48c86ed
--- /dev/null
+++ b/ogn/backend/ognrange.py
@@ -0,0 +1,35 @@
+import json
+from datetime import datetime, timedelta
+
+from sqlalchemy import func, case
+from sqlalchemy.sql.expression import label
+from ogn.model import Receiver
+
+
+def alchemyencoder(obj):
+ import decimal
+ from datetime import datetime
+ """JSON encoder function for SQLAlchemy special classes."""
+ if isinstance(obj, datetime):
+ return obj.strftime('%Y-%m-%d %H:%M')
+ elif isinstance(obj, decimal.Decimal):
+ return float(obj)
+
+
+def stations2_filtered_pl(session):
+ last_10_minutes = datetime.utcnow() - timedelta(minutes=10)
+
+ query = session.query(
+ Receiver.name.label('s'),
+ label('lt', func.round(func.ST_Y(Receiver.location_wkt)*10000)/10000),
+ label('lg', func.round(func.ST_X(Receiver.location_wkt)*10000)/10000),
+ case([(Receiver.lastseen > last_10_minutes, "U")],
+ else_="D").label('u'),
+ Receiver.lastseen.label('ut'),
+ label('v', Receiver.version + '.' + Receiver.platform)) \
+ .order_by(Receiver.lastseen)
+
+ res = session.execute(query)
+ stations = json.dumps({"stations": [dict(r) for r in res]}, default=alchemyencoder)
+
+ return stations
diff --git a/setup.py b/setup.py
index 1c6ad32..79be068 100644
--- a/setup.py
+++ b/setup.py
@@ -47,7 +47,8 @@ setup(
'dev': [
'nose==1.3.7',
'coveralls==1.2',
- 'flake8==3.5.0'
+ 'flake8==3.5.0',
+ 'xmlunittest==0.4.0'
]
},
zip_safe=False
diff --git a/tests/backend/test_backends.py b/tests/backend/test_backends.py
new file mode 100644
index 0000000..d3ed434
--- /dev/null
+++ b/tests/backend/test_backends.py
@@ -0,0 +1,131 @@
+import unittest
+from unittest import mock
+import os
+from datetime import datetime
+
+from xmlunittest import XmlTestMixin
+
+from ogn.model import Receiver, Device
+
+from ogn.backend.liveglidernet import rec, lxml
+from ogn.backend.ognrange import stations2_filtered_pl
+
+
+class TestDB(unittest.TestCase, XmlTestMixin):
+ session = None
+ engine = None
+ app = None
+
+ def setUp(self):
+ os.environ['OGN_CONFIG_MODULE'] = 'config.test'
+ from ogn.commands.dbutils import engine, session
+ self.session = session
+ self.engine = engine
+
+ from ogn.commands.database import init
+ init()
+
+ # Prepare Beacons
+ self.r01 = Receiver(name='Koenigsdf', location_wkt='0101000020E610000061E8FED7A6EE26407F20661C10EA4740', lastseen='2017-12-20 10:00:00', altitude=601, version='0.2.5', platform='ARM')
+ self.r02 = Receiver(name='Bene', location_wkt='0101000020E6100000D5E76A2BF6C72640D4063A6DA0DB4740', lastseen='2017-12-20 09:45:00', altitude=609, version='0.2.7', platform='x64')
+ self.r03 = Receiver(name='Ohlstadt', location_wkt='0101000020E6100000057E678EBF772640A142883E32D44740', lastseen='2017-12-20 10:05:00', altitude=655, version='0.2.6', platform='ARM')
+
+ self.d01 = Device(address='DD4711')
+ self.d02 = Device(address='DD0815')
+
+ session.add(self.r01)
+ session.add(self.r02)
+ session.add(self.r03)
+
+ session.add(self.d01)
+ session.add(self.d02)
+ session.commit()
+
+ def tearDown(self):
+ session = self.session
+ session.execute("DELETE FROM receiver")
+ session.commit()
+
+ @mock.patch('ogn.backend.liveglidernet.datetime')
+ def test_rec(self, datetime_mock):
+ session = self.session
+
+ datetime_mock.utcnow.return_value = datetime(2017, 12, 20, 10, 0)
+
+ data = rec(session).encode(encoding='utf-8')
+
+ # Check the document
+ root = self.assertXmlDocument(data)
+ self.assertXmlNode(root, tag='markers')
+ self.assertXpathsOnlyOne(root, ('./m[@a="Koenigsdf"]', './m[@a="Bene"]', './m[@a="Ohlstadt"]'))
+
+ expected = """
+
+
+
+
+
+
+ """.encode(encoding='utf-8')
+
+ # Check the complete document
+ self.assertXmlEquivalentOutputs(data, expected)
+
+ print(data)
+
+ @unittest.skip("not finished yet")
+ @mock.patch('ogn.backend.liveglidernet.datetime')
+ def test_lxml(self, datetime_mock):
+ session = self.session
+
+ datetime_mock.utcnow.return_value = datetime(2017, 12, 20, 10, 0)
+
+ data = lxml(session).encode(encoding='utf-8')
+
+ # Check the document
+ root = self.assertXmlDocument(data)
+ self.assertXmlNode(root, tag='markers')
+ self.assertXpathsOnlyOne(root, ('./m[@a="Koenigsdf"]', './m[@a="Bene"]', './m[@a="Ohlstadt"]'))
+
+ print(data)
+
+ @mock.patch('ogn.backend.ognrange.datetime')
+ def test_stations2_filtered_pl(self, datetime_mock):
+ session = self.session
+
+ datetime_mock.utcnow.return_value = datetime(2017, 12, 20, 10, 0)
+
+ import json
+
+ result = stations2_filtered_pl(session)
+ print(result)
+
+ data = json.loads(result)
+
+ stations = data["stations"]
+ self.assertEqual(len(stations), 3)
+ s1 = stations[0]
+ s2 = stations[1]
+ s3 = stations[2]
+
+ self.assertEqual(s1["s"], 'Bene')
+ self.assertEqual(s1["lt"], 47.7158)
+ self.assertEqual(s1["lg"], 11.3906)
+ self.assertEqual(s1["u"], "D") # Down, because last beacon > 10min. ago
+ self.assertEqual(s1["ut"], "2017-12-20 09:45")
+ # self.assertEqual(s1["b"], 0)
+ self.assertEqual(s1["v"], "0.2.7.x64")
+
+ self.assertEqual(s2["s"], 'Koenigsdf')
+ self.assertEqual(s2["lt"], 47.8286)
+ self.assertEqual(s2["lg"], 11.4661)
+ self.assertEqual(s2["u"], "U")
+ self.assertEqual(s2["ut"], "2017-12-20 10:00")
+ # self.assertEqual(s2["b"], 0)
+ self.assertEqual(s2["v"], "0.2.5.ARM")
+
+ self.assertEqual(s3["s"], 'Ohlstadt')
+
+
+if __name__ == '__main__':
+ unittest.main()