diff --git a/.gitignore b/.gitignore
index bb573bd34bc88033b8cb475be279a03854a6eec7..e916f3841a7453077ec4eea534c9e325919248e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,7 @@ config/*
 ogndevices
 
 db
+
+# virtualenvs
+/venv
+/virtualenv
diff --git a/README.md b/README.md
index 4afe9e6627449eef61b3528a2d4efcc8f8ddef2c..c9c590d95a22be624e1d05bc0613a3a7573dc88a 100644
--- a/README.md
+++ b/README.md
@@ -25,36 +25,16 @@ What things you need to install and how to install them. These instructions are
 
 Install some ubuntu packages
 ```
-sudo apt-get install libpq-dev postgresql-12 postgresql-client-common postgresql-client libevent-dev apache2 php libapache2-mod-php php-dom php-pgsql libmagickwand-dev imagemagick php-imagick inkscape php-gd
+sudo apt-get install libpq-dev postgresql-12 postgresql-client-common postgresql-client libevent-dev apache2 php libapache2-mod-php php-dom php-pgsql libmagickwand-dev imagemagick php-imagick inkscape php-gd libjpeg-dev
 ```
 **Note that php-gd was added to these instructions during the summer of 2022, it is needed for the new heatmap generator.**
 
-#### Install python
-Unfortunately, the majority of this code was written when python 2 was still common and used, this means that the installation process needs to be adapted a bit. You might see some deprication warnings when starting the collector and websocket server.
+#### Install python 3
 
-Install python 2
+Install python 3
 ```
-sudo add-apt-repository universe
 sudo apt update
-sudo apt install python2 python2-dev
-```
-
-Install pip2 (pip for python 2)
-```
-curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
-sudo python2 get-pip.py
-```
-
-Install needed python libs
-```
-pip2 install psycopg2-binary wheel setuptools autobahn[twisted] twisted pympler image_slicer jsmin psutil
-```
-
-Install the python aprs lib (aprs-python)
-```
-git clone https://github.com/rossengeorgiev/aprs-python
-cd aprs-python/
-pip2 install .
+sudo apt install python3 python3-dev python3-pip
 ```
 
 ### Set up aprsc
@@ -95,9 +75,18 @@ sudo /opt/aprsc/sbin/aprsc -u aprsc -t /opt/aprsc2 -c /etc/aprsc2.conf -r /logs2
 
 ### Installing Track Direct
 
+Note: TrackDirect have to be installed in the user home directory, it can't be in any subdirectory.
+
+
 Start by cloning the repository
 ```
 git clone https://github.com/qvarforth/trackdirect
+cd trackdirect
+```
+
+Install needed python libs
+```
+pip install -r requirements.txt
 ```
 
 #### Set up database
@@ -267,7 +256,6 @@ docker-compose -f docker-compose-rel.yml up
 
 
 ## TODO
-- Rewrite backend to use Python 3 instead of Python 2.
 - Create a REST-API and replace the current website example with a new frontend written in Angular.
 
 ## Contribution
diff --git a/docker-compose-rel.yml b/docker-compose-rel.yml
index d0aa308d1578adde068d24a38b75402eabf8c9a4..8f6ca4043a03bd20fdc44e53ee9627673f9e7502 100644
--- a/docker-compose-rel.yml
+++ b/docker-compose-rel.yml
@@ -12,7 +12,7 @@ services:
       - $PWD/config/aprsc.conf:/opt/aprsc/etc/aprsc.conf
 
   collector:
-    image: peterus/trackdirect-python2:latest
+    image: peterus/trackdirect-python:latest
     restart: always
     volumes:
       - $PWD/config/trackdirect.ini:/root/trackdirect/config/trackdirect.ini
@@ -22,7 +22,7 @@ services:
       - "aprsc"
 
   websocket:
-    image: peterus/trackdirect-python2:latest
+    image: peterus/trackdirect-python:latest
     restart: always
     volumes:
       - $PWD/config/trackdirect.ini:/root/trackdirect/config/trackdirect.ini
@@ -34,7 +34,7 @@ services:
       - "aprsc"
 
   heatmaps:
-    image: peterus/trackdirect-python2:latest
+    image: peterus/trackdirect-python:latest
     restart: always
     volumes:
       - $PWD/config/trackdirect.ini:/root/trackdirect/config/trackdirect.ini
diff --git a/docker-compose.yml b/docker-compose.yml
index 7e9bc023153c50732d62712d954477ce12e111fa..ac2d8fd60fcb3497cbfb5d1ad921af1449008625 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,7 +15,7 @@ services:
   collector:
     build:
       context: .
-      dockerfile: trackdirect-python2.dockerfile
+      dockerfile: trackdirect-python.dockerfile
     volumes:
       - $PWD/config/trackdirect.ini:/root/trackdirect/config/trackdirect.ini
     command: /root/trackdirect/server/scripts/collector.sh trackdirect.ini 0
@@ -26,7 +26,7 @@ services:
   websocket:
     build:
       context: .
-      dockerfile: trackdirect-python2.dockerfile
+      dockerfile: trackdirect-python.dockerfile
     volumes:
       - $PWD/config/trackdirect.ini:/root/trackdirect/config/trackdirect.ini
     command: /root/trackdirect/server/scripts/wsserver.sh trackdirect.ini
diff --git a/htdocs/public/symbols/svgicons/35-1.svg b/htdocs/public/symbols/svgicons/35-1.svg
old mode 100755
new mode 100644
index 7f6d4f7df8bf1e191fa2115db5b4e3323abed6f6..5cd64ee5fde37939041f15d6104a28bfc8c42797
--- a/htdocs/public/symbols/svgicons/35-1.svg
+++ b/htdocs/public/symbols/svgicons/35-1.svg
@@ -1,108 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   version="1.1"
-   width="24"
-   height="24"
-   viewBox="0 0 24 24"
-   id="svg2"
-   inkscape:version="0.91 r13725"
-   sodipodi:docname="35-1.svg">
-  <defs
-     id="defs13" />
-  <sodipodi:namedview
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1"
-     objecttolerance="10"
-     gridtolerance="10"
-     guidetolerance="10"
-     inkscape:pageopacity="0"
-     inkscape:pageshadow="2"
-     inkscape:window-width="1875"
-     inkscape:window-height="1056"
-     id="namedview11"
-     showgrid="false"
-     inkscape:zoom="9.8333333"
-     inkscape:cx="11.898305"
-     inkscape:cy="12"
-     inkscape:window-x="45"
-     inkscape:window-y="24"
-     inkscape:window-maximized="1"
-     inkscape:current-layer="svg2" />
-  <metadata
-     id="metadata4228">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <g
-     inkscape:groupmode="layer"
-     id="layer1"
-     inkscape:label="Layer 1">
-    <path
-       sodipodi:type="star"
-       id="path4232"
-       sodipodi:sides="6"
-       sodipodi:cx="12"
-       sodipodi:cy="11.960827"
-       sodipodi:r1="12.928783"
-       sodipodi:r2="7.3694062"
-       sodipodi:arg1="0.52359878"
-       sodipodi:arg2="1.0471976"
-       inkscape:flatsided="false"
-       inkscape:rounded="0"
-       inkscape:randomized="0"
-       d="M 23.196655,18.425219 15.684703,18.34292 12,24.88961 8.3152966,18.34292 0.80334509,18.425219 4.6305938,11.960827 0.80334515,5.4964351 8.3152972,5.5787337 12,-0.96795654 15.684703,5.5787341 23.196655,5.4964352 19.369406,11.960827 Z"
-       transform="matrix(1.071125,0,0,0.92715745,-0.8535,0.91043026)"
-       fill="#008000"
-       stroke-linejoin="round"
-       stroke-width="1.124" />
-  </g>
-  <g
-     inkscape:groupmode="layer"
-     id="layer2"
-     inkscape:label="Layer 2">
-    <path
-       id="path4253"
-       sodipodi:type="arc"
-       sodipodi:cx="12"
-       sodipodi:cy="12"
-       sodipodi:rx="5.1969533"
-       sodipodi:ry="5.1969533"
-       sodipodi:start="3.1415927"
-       sodipodi:end="3.1406327"
-       sodipodi:open="true"
-       d="m 6.8030467,12 a 5.1969533,5.1969533 0 0 1 5.1957063,-5.1969531 5.1969533,5.1969533 0 0 1 5.1982,5.1944591 5.1969533,5.1969533 0 0 1 -5.193211,5.199446 5.1969533,5.1969533 0 0 1 -5.2006929,-5.191963"
-       fill="#f9f9f9"
-       stroke-linejoin="round"
-       stroke-width="1.124" />
-  </g>
-  <g
-     id="text"
-     transform="translate(0.05168457,0.23376465)">
-    <text
-       x="12"
-       y="14.5"
-       id="text3339"
-       text-align="center"
-       sodipodi:linespacing="0%"
-       word-spacing="0"
-       line-height="0%"
-       font-weight="bold"
-       letter-spacing="0"
-       font-size="7.5px"
-       style="font-weight:bold;font-size:7.5px;line-height:0%;font-family:Sans;text-align:center;letter-spacing:0;word-spacing:0;text-anchor:middle;fill:#000000">D</text>
-  </g>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+  <path d="m23.993 17.993-8.046-.076L12 23.987l-3.948-6.07-8.046.076L4.106 12l-4.1-5.994 8.046.077L12 .013l3.947 6.07 8.046-.077-4.1 5.994Z" fill="green"/>
+  <path d="M6.803 12a5.197 5.197 0 0 1 5.196-5.197 5.197 5.197 0 0 1 5.198 5.195 5.197 5.197 0 0 1-5.193 5.199 5.197 5.197 0 0 1-5.201-5.192" fill="#f9f9f9"/>
+  <text x="12" y="14.5" word-spacing="0" font-weight="bold" letter-spacing="0" font-size="7.5" style="font-weight:700;font-size:7.5px;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0;word-spacing:0;text-anchor:middle;fill:#000" transform="translate(.052 .234)"><tspan x="12" y="14.5">D</tspan></text>
 </svg>
diff --git a/htdocs/public/symbols/svgicons/35-1.svg.orig b/htdocs/public/symbols/svgicons/35-1.svg.orig
new file mode 100755
index 0000000000000000000000000000000000000000..7c21b5de9a5319ae41ce4fc8091d2b6fb7d3adf3
--- /dev/null
+++ b/htdocs/public/symbols/svgicons/35-1.svg.orig
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   version="1.1"
+   width="24"
+   height="24"
+   viewBox="0 0 24 24"
+   id="svg2"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <defs
+     id="defs13" />
+  <metadata
+     id="metadata4228">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1">
+    <path
+       id="path4232"
+       d="M 23.196655,18.425219 15.684703,18.34292 12,24.88961 8.3152966,18.34292 0.80334509,18.425219 4.6305938,11.960827 0.80334515,5.4964351 8.3152972,5.5787337 12,-0.96795654 15.684703,5.5787341 23.196655,5.4964352 19.369406,11.960827 Z"
+       transform="matrix(1.071125,0,0,0.92715745,-0.8535,0.91043026)"
+       fill="#008000"
+       stroke-linejoin="round"
+       stroke-width="1.124" />
+  </g>
+  <g
+     id="layer2">
+    <path
+       id="path4253"
+       d="m 6.8030467,12 a 5.1969533,5.1969533 0 0 1 5.1957063,-5.1969531 5.1969533,5.1969533 0 0 1 5.1982,5.1944591 5.1969533,5.1969533 0 0 1 -5.193211,5.199446 5.1969533,5.1969533 0 0 1 -5.2006929,-5.191963"
+       fill="#f9f9f9"
+       stroke-linejoin="round"
+       stroke-width="1.124" />
+  </g>
+  <g
+     id="text"
+     transform="translate(0.05168457,0.23376465)">
+    <text
+       x="12"
+       y="14.5"
+       id="text3339"
+       text-align="center"
+       word-spacing="0"
+       line-height="0%"
+       font-weight="bold"
+       letter-spacing="0"
+       font-size="7.5px"
+       style="font-weight:bold;font-size:7.5px;line-height:0%;font-family:sans-serif;text-align:center;letter-spacing:0;word-spacing:0;text-anchor:middle;fill:#000000"><tspan
+         id="tspan2930"
+         x="12"
+         y="14.5">D</tspan></text>
+  </g>
+</svg>
diff --git a/htdocs/public/symbols/svgicons/35-2.svg b/htdocs/public/symbols/svgicons/35-2.svg
old mode 100755
new mode 100644
index 82d97941a5fcc9c0e81c73827db2323a9be09a13..97cae2ab92e0a33fc76ee018c8d2e6aeffb2da79
--- a/htdocs/public/symbols/svgicons/35-2.svg
+++ b/htdocs/public/symbols/svgicons/35-2.svg
@@ -1,7 +1,4 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" width="24" height="24" viewBox="0 0 24 24">
- <metadata id="metadata4228"/>
- <g inkscape:groupmode="layer" id="layer1" inkscape:label="Layer 1">
-  <path sodipodi:type="star" id="path4232" sodipodi:sides="6" sodipodi:cx="12" sodipodi:cy="11.960827" sodipodi:r1="12.928783" sodipodi:r2="7.3694062" sodipodi:arg1="0.52359878" sodipodi:arg2="1.0471976" inkscape:flatsided="false" inkscape:rounded="0" inkscape:randomized="0" d="M 23.196655,18.425219 15.684703,18.34292 12,24.88961 8.3152966,18.34292 0.80334509,18.425219 4.6305938,11.960827 0.80334515,5.4964351 8.3152972,5.5787337 12,-0.96795654 15.684703,5.5787341 23.196655,5.4964352 19.369406,11.960827 Z" transform="matrix(1.071125,0,0,0.92715745,-0.84619502,0.91977284)" fill="#008000" stroke-linejoin="round" stroke-width="1.124"/>
- </g>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+  <path d="m24.001 18.003-8.046-.076-3.947 6.07-3.948-6.07-8.046.076 4.1-5.993-4.1-5.994 8.046.077 3.948-6.07 3.947 6.07L24 6.016l-4.1 5.994Z" fill="green"/>
 </svg>
diff --git a/htdocs/public/symbols/svgicons/35-2.svg.orig b/htdocs/public/symbols/svgicons/35-2.svg.orig
new file mode 100755
index 0000000000000000000000000000000000000000..82d97941a5fcc9c0e81c73827db2323a9be09a13
--- /dev/null
+++ b/htdocs/public/symbols/svgicons/35-2.svg.orig
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" width="24" height="24" viewBox="0 0 24 24">
+ <metadata id="metadata4228"/>
+ <g inkscape:groupmode="layer" id="layer1" inkscape:label="Layer 1">
+  <path sodipodi:type="star" id="path4232" sodipodi:sides="6" sodipodi:cx="12" sodipodi:cy="11.960827" sodipodi:r1="12.928783" sodipodi:r2="7.3694062" sodipodi:arg1="0.52359878" sodipodi:arg2="1.0471976" inkscape:flatsided="false" inkscape:rounded="0" inkscape:randomized="0" d="M 23.196655,18.425219 15.684703,18.34292 12,24.88961 8.3152966,18.34292 0.80334509,18.425219 4.6305938,11.960827 0.80334515,5.4964351 8.3152972,5.5787337 12,-0.96795654 15.684703,5.5787341 23.196655,5.4964352 19.369406,11.960827 Z" transform="matrix(1.071125,0,0,0.92715745,-0.84619502,0.91977284)" fill="#008000" stroke-linejoin="round" stroke-width="1.124"/>
+ </g>
+</svg>
diff --git a/jslib/build.sh b/jslib/build.sh
index bd7b57c107a19fd5ccb9fad1cc14734d7cf7af28..335d84110687fd9f08b1e21458e913cc7f6d4130 100755
--- a/jslib/build.sh
+++ b/jslib/build.sh
@@ -13,7 +13,7 @@ do
         newFile="${file##*/}"
         newFile="tmp/${newFile//.js/}.min.js"
         echo "Processing $file -> $newFile"
-        python2 -m jsmin $file > $newFile
+        python -m jsmin $file > $newFile
         #cp $file $newFile
     else
         newFile="tmp/${file##*/}"
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2a592dacdb7807b368d831d8e8995116459fb4a6
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,10 @@
+psycopg2-binary
+wheel
+setuptools
+autobahn[twisted]
+twisted
+pympler
+image_slicer
+jsmin
+psutil
+git+https://github.com/rossengeorgiev/aprs-python
\ No newline at end of file
diff --git a/server/bin/collector.py b/server/bin/collector.py
index cc1706c6d8eb09dcd75f379ace166e2ae8590875..7025864c22d54e015219745602e6cb0e5cb73f1a 100644
--- a/server/bin/collector.py
+++ b/server/bin/collector.py
@@ -11,9 +11,11 @@ if __name__ == '__main__':
         sys.exit()
     elif (sys.argv[1].startswith("/")):
         if (not os.path.isfile(sys.argv[1])):
+            print(f"\n File {sys.argv[1]} does not exists")
             print("\n" + sys.argv[0] + ' [config.ini] [collector number]')
             sys.exit()
     elif (not os.path.isfile(os.path.expanduser('~/trackdirect/config/' + sys.argv[1]))):
+        print(f"\n File ~/trackdirect/config/{sys.argv[1]} does not exists")
         print("\n" + sys.argv[0] + ' [config.ini] [collector number]')
         sys.exit()
 
diff --git a/server/bin/remover.py b/server/bin/remover.py
index 1116319836dfe02ec0031652d57c14789217d02e..0d80f5b8791a6a554822333f75e427308f45b5c0 100644
--- a/server/bin/remover.py
+++ b/server/bin/remover.py
@@ -1,32 +1,31 @@
-import trackdirect
 import sys
 import os.path
 import logging
 import logging.handlers
-import psycopg2
-import psycopg2.extras
 import datetime
 import time
+import trackdirect
 
 from trackdirect.database.DatabaseConnection import DatabaseConnection
 from trackdirect.database.DatabaseObjectFinder import DatabaseObjectFinder
-from trackdirect.TrackDirectConfig import TrackDirectConfig
 from trackdirect.repositories.PacketRepository import PacketRepository
 
 if __name__ == '__main__':
 
     if (len(sys.argv) < 2):
-        print "\n" + sys.argv[0] + ' [config.ini]'
+        print("\n" + sys.argv[0] + ' [config.ini]')
         sys.exit()
     elif (sys.argv[1].startswith("/")):
         if (not os.path.isfile(sys.argv[1])):
-            print "\n" + sys.argv[0] + ' [config.ini]'
+            print(f"\n File {sys.argv[1]} does not exists")
+            print("\n" + sys.argv[0] + ' [config.ini]')
             sys.exit()
     elif (not os.path.isfile(os.path.expanduser('~/trackdirect/config/' + sys.argv[1]))):
-        print "\n" + sys.argv[0] + ' [config.ini]'
+        print(f"\n File ~/trackdirect/config/{sys.argv[1]} does not exists")
+        print("\n" + sys.argv[0] + ' [config.ini]')
         sys.exit()
 
-    config = TrackDirectConfig()
+    config = trackdirect.TrackDirectConfig()
     config.populate(sys.argv[1])
 
     maxDaysToSavePositionData = int(config.daysToSavePositionData)
diff --git a/server/bin/stationremover.py b/server/bin/stationremover.py
index 1a7a1101f3f6ca7cb724831b32a338fc7a5c18cd..6e13a2eb0a1336393edf6be4dfd74ce2dc7f190b 100644
--- a/server/bin/stationremover.py
+++ b/server/bin/stationremover.py
@@ -1,33 +1,32 @@
-import trackdirect
 import sys
 import os.path
 import logging
 import logging.handlers
-import psycopg2
-import psycopg2.extras
 import datetime
 import time
+import trackdirect
 
 from trackdirect.database.DatabaseConnection import DatabaseConnection
 from trackdirect.database.DatabaseObjectFinder import DatabaseObjectFinder
-from trackdirect.TrackDirectConfig import TrackDirectConfig
 
 if __name__ == '__main__':
 
     if (len(sys.argv) < 3):
-        print "\n" + sys.argv[0] + ' [config.ini] [staion id]'
+        print("\n" + sys.argv[0] + ' [config.ini] [staion id]')
         sys.exit()
     elif (sys.argv[1].startswith("/")):
         if (not os.path.isfile(sys.argv[1])):
-            print "\n" + sys.argv[0] + ' [config.ini] [staion id]'
+            print(f"\n File {sys.argv[1]} does not exists")
+            print("\n" + sys.argv[0] + ' [config.ini] [staion id]')
             sys.exit()
     elif (not os.path.isfile(os.path.expanduser('~/trackdirect/config/' + sys.argv[1]))):
-        print "\n" + sys.argv[0] + ' [config.ini] [staion id]'
+        print(f"\n File ~/trackdirect/config/{sys.argv[1]} does not exists")
+        print("\n" + sys.argv[0] + ' [config.ini] [staion id]')
         sys.exit()
 
     stationId = sys.argv[2]
 
-    config = TrackDirectConfig()
+    config = trackdirect.TrackDirectConfig()
     config.populate(sys.argv[1])
 
     try:
diff --git a/server/scripts/collector.sh b/server/scripts/collector.sh
index ded174e0ee03c8d6728886bd512c4c94098808c0..50bd14ddfd9d66e06cfe2a1a74e8bbf0450318cd 100755
--- a/server/scripts/collector.sh
+++ b/server/scripts/collector.sh
@@ -17,6 +17,6 @@ else
 
     export PYTHONPATH=$PYTHONPATH:$CURRENTDIR/../trackdirect
     cd $CURRENTDIR/..
-    python2 ./bin/collector.py $CONFIGFILE $COLLECTORNUMBER
+    python $CURRENTDIR/../bin/collector.py $CONFIGFILE $COLLECTORNUMBER
     exit 0
 fi
diff --git a/server/scripts/ogn_devices_install.sh b/server/scripts/ogn_devices_install.sh
index 6f652706c588ef80d0f3d84fdb8820699d3f6f3a..5859bc1a39ff4ccf1dbf7c14886483aba128d415 100755
--- a/server/scripts/ogn_devices_install.sh
+++ b/server/scripts/ogn_devices_install.sh
@@ -50,7 +50,7 @@ begin transaction;
 
 drop index if exists ogn_device_device_id_idx;
 truncate ogn_device;
-copy ogn_device from '$SCRIPTPATH/ogndevices/$DATABASE/ogndevices2.csv' DELIMITERS ',' CSV QUOTE '''';
+\copy ogn_device from '$SCRIPTPATH/ogndevices/$DATABASE/ogndevices2.csv' DELIMITERS ',' CSV QUOTE '''';
 create index ogn_device_device_id_idx on ogn_device(device_id);
 
 insert into ogn_device(device_type, device_id, aircraft_model, registration, cn, tracked, identified, ddb_aircraft_type) values ('F', '3FEF6F', '', '', '', 'N', 'N', 1);
diff --git a/server/scripts/remover.sh b/server/scripts/remover.sh
index ae45b6e77395c6533b06bb5df2e6ded0c15731f5..ccb0cae108ffce298477e29787a43cc9b82ba732 100755
--- a/server/scripts/remover.sh
+++ b/server/scripts/remover.sh
@@ -16,6 +16,6 @@ else
 
     export PYTHONPATH=$PYTHONPATH:$CURRENTDIR/../trackdirect
     cd $CURRENTDIR/..
-    python2 $CURRENTDIR/../bin/remover.py $CONFIGFILE
+    python $CURRENTDIR/../bin/remover.py $CONFIGFILE
     exit 0
 fi
diff --git a/server/scripts/stationremover.sh b/server/scripts/stationremover.sh
index b1164723710b25a7e92dfeafe6c28e0f6a0da612..72be13e363e1517dbb68fc5663bdb9b7b14f1b17 100755
--- a/server/scripts/stationremover.sh
+++ b/server/scripts/stationremover.sh
@@ -17,6 +17,6 @@ else
 
     export PYTHONPATH=$PYTHONPATH:$CURRENTDIR/../trackdirect
     cd $CURRENTDIR/..
-    python2 $CURRENTDIR/../bin/stationremover.py $CONFIGFILE $STATIONID
+    python $CURRENTDIR/../bin/stationremover.py $CONFIGFILE $STATIONID
     exit 0
 fi
diff --git a/server/scripts/wsserver.sh b/server/scripts/wsserver.sh
index 2d357a64f5237a4ccd9b4aff79f33ef8f719d680..999d3e7936b254ea8e55f9544715d55f63d20bd7 100755
--- a/server/scripts/wsserver.sh
+++ b/server/scripts/wsserver.sh
@@ -15,6 +15,6 @@ else
 
     export PYTHONPATH=$PYTHONPATH:$CURRENTDIR/../trackdirect
     cd $CURRENTDIR/..
-    python2 $CURRENTDIR/../bin/wsserver.py --config $CONFIGFILE
+    python $CURRENTDIR/../bin/wsserver.py --config $CONFIGFILE
     exit 0
 fi
diff --git a/server/trackdirect/TrackDirectConfig.py b/server/trackdirect/TrackDirectConfig.py
index 0ab9c78f833bd282b04cd93504e10621974400f4..3da44ad236595cf7882ae938822b218e65777708 100644
--- a/server/trackdirect/TrackDirectConfig.py
+++ b/server/trackdirect/TrackDirectConfig.py
@@ -1,8 +1,6 @@
-import sys
 import os
 import os.path
-import ConfigParser
-from ConfigParser import SafeConfigParser
+import configparser
 
 from trackdirect.common.Singleton import Singleton
 
@@ -22,7 +20,7 @@ class TrackDirectConfig(Singleton):
         Args:
             configFile (string):  Config file name
         """
-        configParser = SafeConfigParser()
+        configParser = configparser.SafeConfigParser()
         if (configFile.startswith('/')):
             configParser.read(os.path.expanduser(configFile))
         else:
@@ -35,7 +33,7 @@ class TrackDirectConfig(Singleton):
         try:
             self.dbUsername = configParser.get(
                 'database', 'username').strip('"')
-        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+        except (configparser.NoSectionError, configparser.NoOptionError):
             self.dbUsername = os.getlogin()
         self.dbPassword = configParser.get('database', 'password').strip('"')
         self.dbPort = int(configParser.get('database', 'port').strip('"'))
@@ -54,7 +52,7 @@ class TrackDirectConfig(Singleton):
                 'database', 'save_ogn_stations_with_missing_identity').strip('"')
             if (saveOgnStationsWithMissingIdentity == "1"):
                 self.saveOgnStationsWithMissingIdentity = True
-        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+        except (configparser.NoSectionError, configparser.NoOptionError):
             pass
 
         # Websocket server
@@ -67,7 +65,7 @@ class TrackDirectConfig(Singleton):
         try :
             self.websocketExternalPort = int(configParser.get(
                 'websocket_server', 'external_port').strip('"'))
-        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+        except (configparser.NoSectionError, configparser.NoOptionError):
             pass
 
         self.errorLog = configParser.get(
@@ -98,7 +96,7 @@ class TrackDirectConfig(Singleton):
                 'websocket_server', 'aprs_port1').strip('"')
             self.websocketAprsSourceId1 = int(configParser.get(
                 'websocket_server', 'aprs_source_id1').strip('"'))
-        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+        except (configparser.NoSectionError, configparser.NoOptionError):
             self.websocketAprsSourceId1 = None
             self.websocketAprsHost1 = None
             self.websocketAprsPort1 = None
@@ -110,7 +108,7 @@ class TrackDirectConfig(Singleton):
                 'websocket_server', 'aprs_port2').strip('"')
             self.websocketAprsSourceId2 = int(configParser.get(
                 'websocket_server', 'aprs_source_id2').strip('"'))
-        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+        except (configparser.NoSectionError, configparser.NoOptionError):
             self.websocketAprsSourceId2 = None
             self.websocketAprsHost2 = None
             self.websocketAprsPort2 = None
@@ -146,7 +144,7 @@ class TrackDirectConfig(Singleton):
                 try:
                     self.collector[collectorNumber]['frequency_limit'] = configParser.get(
                         'collector' + str(collectorNumber), 'frequency_limit').strip('"')
-                except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+                except (configparser.NoSectionError, configparser.NoOptionError):
                     self.collector[collectorNumber]['frequency_limit'] = "0"
 
                 try:
@@ -154,7 +152,7 @@ class TrackDirectConfig(Singleton):
                         'collector' + str(collectorNumber), 'save_fast_packets').strip('"')
                     self.collector[collectorNumber]['save_fast_packets'] = bool(
                         int(saveFastPackets))
-                except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+                except (configparser.NoSectionError, configparser.NoOptionError):
                     self.collector[collectorNumber]['save_fast_packets'] = False
 
                 try:
@@ -162,7 +160,7 @@ class TrackDirectConfig(Singleton):
                         'collector' + str(collectorNumber), 'detect_duplicates').strip('"')
                     self.collector[collectorNumber]['detect_duplicates'] = bool(
                         int(detectDuplicates))
-                except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+                except (configparser.NoSectionError, configparser.NoOptionError):
                     self.collector[collectorNumber]['detect_duplicates'] = False
 
                 self.collector[collectorNumber]['error_log'] = configParser.get(
@@ -175,7 +173,7 @@ class TrackDirectConfig(Singleton):
                     self.collector[collectorNumber]['save_fast_packets'] = False
 
 
-            except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
+            except (configparser.NoSectionError, configparser.NoOptionError):
                 self.collector[collectorNumber]['source_id'] = None
                 self.collector[collectorNumber]['host'] = None
                 self.collector[collectorNumber]['port_full'] = None
diff --git a/server/trackdirect/TrackDirectDataCollector.py b/server/trackdirect/TrackDirectDataCollector.py
index fa5d01f1bc943e24430c48bcb0fde09371e1d164..63959b197bf4dfc6724add2bfb144516423d34be 100644
--- a/server/trackdirect/TrackDirectDataCollector.py
+++ b/server/trackdirect/TrackDirectDataCollector.py
@@ -1,9 +1,6 @@
 import logging
-from twisted.python import log
 import psycopg2
 import psycopg2.extras
-from collections import deque
-import json
 import re
 import aprslib
 import datetime
@@ -17,7 +14,6 @@ from trackdirect.collector.PacketBatchInserter import PacketBatchInserter
 from trackdirect.exceptions.TrackDirectParseError import TrackDirectParseError
 from trackdirect.database.DatabaseConnection import DatabaseConnection
 from trackdirect.repositories.StationRepository import StationRepository
-from trackdirect.objects.Packet import Packet
 
 #from pympler.tracker import SummaryTracker
 
@@ -261,9 +257,10 @@ class TrackDirectDataCollector():
                 if (turnRate > 0) :
                     frequencyLimitToApply = int(frequencyLimitToApply / (1+turnRate))
 
-            if ((packet.timestamp - frequencyLimitToApply) < packet.markerPrevPacketTimestamp):
-                # This station is sending faster than config limit
-                return True
+            if packet.markerPrevPacketTimestamp:
+                if ((packet.timestamp - frequencyLimitToApply) < packet.markerPrevPacketTimestamp):
+                    # This station is sending faster than config limit
+                    return True
 
             if (packet.stationId in self.movingStationIdsWithVisiblePacket):
                 # This station is sending way to fast (we havn't even added the previous packet to database yet)
diff --git a/server/trackdirect/TrackDirectWebsocketServer.py b/server/trackdirect/TrackDirectWebsocketServer.py
index 9633f7abb95c4230a564f1bcfc91794e7b32d98d..fe9a0706c957f011ea84cdbd05a2e63dfc0aa1b0 100644
--- a/server/trackdirect/TrackDirectWebsocketServer.py
+++ b/server/trackdirect/TrackDirectWebsocketServer.py
@@ -1,21 +1,16 @@
 import logging
 
-from twisted.python import log
 from twisted.internet import threads, reactor, task
 from twisted.internet.error import AlreadyCancelled, AlreadyCalled
 
 from autobahn.twisted.websocket import WebSocketServerProtocol
 
 import json
-import datetime
 import time
 import psycopg2
 import psycopg2.extras
 import os
-import re
-
-from trackdirect.TrackDirectConfig import TrackDirectConfig
-
+import trackdirect
 from trackdirect.database.DatabaseConnection import DatabaseConnection
 
 from trackdirect.websocket.WebsocketResponseCreator import WebsocketResponseCreator
@@ -35,7 +30,7 @@ class TrackDirectWebsocketServer(WebSocketServerProtocol):
         WebSocketServerProtocol.__init__(self)
         self.logger = logging.getLogger('trackdirect')
 
-        self.config = TrackDirectConfig()
+        self.config = trackdirect.TrackDirectConfig()
         self.maxClientIdleTime = int(self.config.maxClientIdleTime) * 60
         self.maxQueuedRealtimePackets = int(
             self.config.maxQueuedRealtimePackets)
diff --git a/server/trackdirect/collector/PacketBatchInserter.py b/server/trackdirect/collector/PacketBatchInserter.py
index 788b2c420227d607c81d11474fcff27a432e70bf..a0899d440a0d3e113ce343b5e5abb7b7d28b14c9 100644
--- a/server/trackdirect/collector/PacketBatchInserter.py
+++ b/server/trackdirect/collector/PacketBatchInserter.py
@@ -1,9 +1,6 @@
 import logging
-from twisted.python import log
 import psycopg2
 import psycopg2.extras
-import datetime
-import time
 
 from trackdirect.collector.StationLatestPacketModifier import StationLatestPacketModifier
 from trackdirect.collector.PacketMapIdModifier import PacketMapIdModifier
@@ -178,11 +175,12 @@ class PacketBatchInserter():
                                      packet.comment,
                                      packet.rawPath,
                                      packet.raw))
+
         try:
             # insert into packetYYYYMMDD
-            argString = ','.join(cur.mogrify(
+            argString = b','.join(cur.mogrify(
                 "(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", x) for x in datePacketTuples)
-            sql = "insert into " + packetTable + "(station_id, sender_id, map_id, source_id, packet_type_id, latitude, longitude, posambiguity, symbol, symbol_table, map_sector, related_map_sectors, marker_id, marker_counter, speed, course, altitude, rng, phg, latest_phg_timestamp, latest_rng_timestamp, timestamp, packet_tail_timestamp, is_moving, reported_timestamp, position_timestamp, comment, raw_path, raw) values " + argString + " RETURNING id"
+            sql = "insert into " + packetTable + "(station_id, sender_id, map_id, source_id, packet_type_id, latitude, longitude, posambiguity, symbol, symbol_table, map_sector, related_map_sectors, marker_id, marker_counter, speed, course, altitude, rng, phg, latest_phg_timestamp, latest_rng_timestamp, timestamp, packet_tail_timestamp, is_moving, reported_timestamp, position_timestamp, comment, raw_path, raw) values " + argString.decode() + " RETURNING id"
             cur.execute(sql)
         except psycopg2.InterfaceError as e:
             # Connection to database is lost, better just exit
@@ -239,10 +237,10 @@ class PacketBatchInserter():
         # insert into packetYYYYMMDD_path
         if pathTuples:
             try:
-                argString = ','.join(cur.mogrify(
+                argString = b','.join(cur.mogrify(
                     "(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", x) for x in pathTuples)
                 cur.execute("insert into " + packetPathTable +
-                            "(packet_id, station_id, latitude, longitude, timestamp, distance, number, sending_station_id, sending_latitude, sending_longitude) values " + argString)
+                            "(packet_id, station_id, latitude, longitude, timestamp, distance, number, sending_station_id, sending_latitude, sending_longitude) values " + argString.decode())
             except psycopg2.InterfaceError as e:
                 # Connection to database is lost, better just exit
                 raise e
@@ -288,10 +286,10 @@ class PacketBatchInserter():
         # insert into packetYYYYMMDD_weather
         if weatherTuples:
             try:
-                argString = ','.join(cur.mogrify(
+                argString = b','.join(cur.mogrify(
                     "(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", x) for x in weatherTuples)
                 cur.execute("insert into " + packetWeatherTable +
-                            "(packet_id, station_id, timestamp, humidity, pressure, rain_1h, rain_24h, rain_since_midnight, temperature, wind_direction, wind_gust, wind_speed, luminosity, snow, wx_raw_timestamp) values " + argString)
+                            "(packet_id, station_id, timestamp, humidity, pressure, rain_1h, rain_24h, rain_since_midnight, temperature, wind_direction, wind_gust, wind_speed, luminosity, snow, wx_raw_timestamp) values " + argString.decode())
             except psycopg2.InterfaceError as e:
                 # Connection to database is lost, better just exit
                 raise e
@@ -332,9 +330,9 @@ class PacketBatchInserter():
         # insert into packetYYYYMMDD_ogn
         if ognTuples:
             try:
-                argString = ','.join(cur.mogrify(
+                argString = b','.join(cur.mogrify(
                     "(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", x) for x in ognTuples)
-                cur.execute("insert into " + packetOgnTable + "(packet_id, station_id, timestamp, ogn_sender_address, ogn_address_type_id, ogn_aircraft_type_id, ogn_climb_rate, ogn_turn_rate, ogn_signal_to_noise_ratio, ogn_bit_errors_corrected, ogn_frequency_offset) values " + argString)
+                cur.execute("insert into " + packetOgnTable + "(packet_id, station_id, timestamp, ogn_sender_address, ogn_address_type_id, ogn_aircraft_type_id, ogn_climb_rate, ogn_turn_rate, ogn_signal_to_noise_ratio, ogn_bit_errors_corrected, ogn_frequency_offset) values " + argString.decode())
             except psycopg2.InterfaceError as e:
                 # Connection to database is lost, better just exit
                 raise e
@@ -376,10 +374,10 @@ class PacketBatchInserter():
         # insert into packetYYYYMMDD_telemetry
         if telemetryTuples:
             try:
-                argString = ','.join(cur.mogrify(
+                argString = b','.join(cur.mogrify(
                     "(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", x) for x in telemetryTuples)
                 cur.execute("insert into " + packetTelemetryTable +
-                            "(packet_id, station_id, timestamp, val1, val2, val3, val4, val5, bits, seq) values " + argString + " returning id")
+                            "(packet_id, station_id, timestamp, val1, val2, val3, val4, val5, bits, seq) values " + argString.decode() + " returning id")
             except psycopg2.InterfaceError as e:
                 # Connection to database is lost, better just exit
                 raise e
diff --git a/server/trackdirect/common/Model.py b/server/trackdirect/common/Model.py
index 3a5b97b4258cd68545605909d8f6c3f9ef2795d4..5661308d479137c40ee20ded7a55c0dd9605c936 100644
--- a/server/trackdirect/common/Model.py
+++ b/server/trackdirect/common/Model.py
@@ -20,7 +20,7 @@ class Model():
         Returns:
             true if the object exists in database otherwise false
         """
-        if ((type(self.id) == int or type(self.id) == long) and self.id is not None and self.id > 0):
+        if ((type(self.id) == int) and self.id is not None and self.id > 0):
             return True
         else:
             return False
diff --git a/server/trackdirect/database/DatabaseConnection.py b/server/trackdirect/database/DatabaseConnection.py
index 22495ccdeff6963c6a5ed9a653244d761ef1cc5d..42c2ec95b760b626454fc520d58951f53790328b 100644
--- a/server/trackdirect/database/DatabaseConnection.py
+++ b/server/trackdirect/database/DatabaseConnection.py
@@ -1,9 +1,7 @@
-import logging
-from twisted.python import log
 import psycopg2
 import psycopg2.extras
 
-from trackdirect.TrackDirectConfig import TrackDirectConfig
+import trackdirect
 
 
 class DatabaseConnection():
@@ -16,7 +14,7 @@ class DatabaseConnection():
     def __init__(self):
         """The __init__ method.
         """
-        config = TrackDirectConfig()
+        config = trackdirect.TrackDirectConfig()
         self.host = config.dbHostname
         self.database = config.dbName
         self.username = config.dbUsername
diff --git a/server/trackdirect/parser/AprsISConnection.py b/server/trackdirect/parser/AprsISConnection.py
index 4f097cabba96eb84c1f53918b58fc6d5a57de3c6..b5afe62c4be9a66c83ad2512bb402b014a49a74d 100644
--- a/server/trackdirect/parser/AprsISConnection.py
+++ b/server/trackdirect/parser/AprsISConnection.py
@@ -1,9 +1,6 @@
 import logging
-from twisted.python import log
 import aprslib
 import collections
-import psycopg2
-import datetime
 import time
 import re
 
@@ -62,9 +59,10 @@ class AprsISConnection(aprslib.IS):
         """
         def filterCallback(line):
             try:
+                # decode first then replace
+                line = line.decode()
                 line = line.replace('\x00', '')
-                line.decode('utf-8', 'replace')
-            except UnicodeError:
+            except UnicodeError as e:
                 # string is not UTF-8
                 return
 
diff --git a/server/trackdirect/parser/policies/PacketCommentPolicy.py b/server/trackdirect/parser/policies/PacketCommentPolicy.py
index e2a4c4939250f870a899f8f79f3c697fcc95e135..e2fde323f9ecdcfd9a69a0de5f004582ff6bf543 100644
--- a/server/trackdirect/parser/policies/PacketCommentPolicy.py
+++ b/server/trackdirect/parser/policies/PacketCommentPolicy.py
@@ -29,7 +29,7 @@ class PacketCommentPolicy():
         elif ("comment" in data):
             comment = data["comment"]
 
-        if isinstance(comment, unicode):
+        if isinstance(comment, bytes):
             comment = comment.encode('ascii', 'ignore')
             comment = comment.replace('\x00', '')
 
diff --git a/server/trackdirect/parser/policies/PacketRelatedMapSectorsPolicy.py b/server/trackdirect/parser/policies/PacketRelatedMapSectorsPolicy.py
index a083000fc40fdfb5f7567ebad900036150e6b977..a0595ef225fc96df404aa4c14e2b93bfa3051007 100644
--- a/server/trackdirect/parser/policies/PacketRelatedMapSectorsPolicy.py
+++ b/server/trackdirect/parser/policies/PacketRelatedMapSectorsPolicy.py
@@ -1,5 +1,3 @@
-import json
-import datetime
 import time
 
 from trackdirect.parser.policies.MapSectorPolicy import MapSectorPolicy
@@ -65,7 +63,7 @@ class PacketRelatedMapSectorsPolicy():
             # We only add related map-sectors to moving stations (that has a marker)
             if (packet.mapId == 1):
                 # If new packet is not confirmed (mapId 7) we connect it with related map-sectors later
-                if (previousPacket.markerCounter > 1
+                if (previousPacket.markerCounter is not None and previousPacket.markerCounter > 1
                         or packet.markerId == previousPacket.markerId):
                     # We only add related map-sectors if previous packet has a marker with several connected packet
                     # A packet with a marker that is not shared with anyone will be converted to a ghost-marker in client
@@ -127,8 +125,8 @@ class PacketRelatedMapSectorsPolicy():
             # lat interval: 0 - 18000000
             # lng interval: 0 - 00003600
             # Maybe we can do this smarter? Currently we are adding many map-sectors that is not relevant
-            for lat in xrange(minLat, maxLat, 20000):
-                for lng in xrange(minLng, maxLng, 5):
+            for lat in range(minLat, maxLat, 20000):
+                for lng in range(minLng, maxLng, 5):
                     mapSectorAreaCode = lat+lng
                     if (mapSectorAreaCode != prevPacketAreaCode and mapSectorAreaCode != newPacketAreaCode):
                         relatedMapSectors.append(mapSectorAreaCode)
diff --git a/server/trackdirect/repositories/PacketRepository.py b/server/trackdirect/repositories/PacketRepository.py
index 946fe4e762cdba0d35b58415f56103e83fa220b7..e9bca1a178389c7f41c3cdd5e55a403bbb8fa450 100644
--- a/server/trackdirect/repositories/PacketRepository.py
+++ b/server/trackdirect/repositories/PacketRepository.py
@@ -1,7 +1,3 @@
-import datetime
-import time
-import calendar
-import collections
 
 from trackdirect.common.Repository import Repository
 from trackdirect.objects.Packet import Packet
@@ -407,7 +403,7 @@ class PacketRepository(Repository):
         record = selectCursor.fetchone()
         selectCursor.close()
 
-        if (record is not None and record["latest_confirmed_packet_timestamp"] >= minTimestamp):
+        if (record is not None and record["latest_confirmed_packet_timestamp"] is not None and record["latest_confirmed_packet_timestamp"] >= minTimestamp):
             return self.getObjectByIdAndTimestamp(record["latest_confirmed_packet_id"], record["latest_confirmed_packet_timestamp"])
         else:
             return self.create()
@@ -431,7 +427,7 @@ class PacketRepository(Repository):
         record = selectCursor.fetchone()
         selectCursor.close()
 
-        if (record is not None and record["latest_location_packet_timestamp"] >= minTimestamp):
+        if (record is not None and record["latest_location_packet_timestamp"] is not None and record["latest_location_packet_timestamp"] >= minTimestamp):
             return self.getObjectByIdAndTimestamp(record["latest_location_packet_id"], record["latest_location_packet_timestamp"])
         else:
             return self.create()
diff --git a/server/trackdirect/repositories/PacketTelemetryRepository.py b/server/trackdirect/repositories/PacketTelemetryRepository.py
index 3c9508820c39d8e9f698ebe66230209a3237daaf..c816f6a87d86ca1bad6097359a66b95720429616 100644
--- a/server/trackdirect/repositories/PacketTelemetryRepository.py
+++ b/server/trackdirect/repositories/PacketTelemetryRepository.py
@@ -1,4 +1,3 @@
-import datetime
 import time
 
 from trackdirect.common.Repository import Repository
@@ -110,7 +109,7 @@ class PacketTelemetryRepository(Repository):
                 newObject.bits = data["telemetry"]["bits"]
 
             if ("seq" in data["telemetry"]):
-                if isinstance(data["telemetry"]["seq"], str) or isinstance(data["telemetry"]["seq"], unicode):
+                if isinstance(data["telemetry"]["seq"], str):
                     try:
                         newObject.seq = int(data["telemetry"]["seq"], 10)
                     except ValueError:
diff --git a/server/trackdirect/websocket/WebsocketConnectionState.py b/server/trackdirect/websocket/WebsocketConnectionState.py
index d5325713ffa56524952a65fe106d4e05a9a164b0..2b7837208b0336d405b2b5ba1c26a77d0055a484 100644
--- a/server/trackdirect/websocket/WebsocketConnectionState.py
+++ b/server/trackdirect/websocket/WebsocketConnectionState.py
@@ -1,7 +1,7 @@
-import datetime, time
-from math import floor, ceil
+import time
+import trackdirect
+from math import ceil
 from trackdirect.parser.policies.MapSectorPolicy import MapSectorPolicy
-from trackdirect.TrackDirectConfig import TrackDirectConfig
 
 class WebsocketConnectionState():
     """An WebsocketConnectionState instance contains information about the current state of a websocket connection
@@ -15,7 +15,7 @@ class WebsocketConnectionState():
         self.latestRequestTimestamp = 0
         self.latestRequestId = 0
         self.latestHandledRequestId = 0
-        self.config = TrackDirectConfig()
+        self.config = trackdirect.TrackDirectConfig()
         self.noRealTime = False
         self.disconnected = False
 
diff --git a/server/trackdirect/websocket/aprsis/AprsISPayloadCreator.py b/server/trackdirect/websocket/aprsis/AprsISPayloadCreator.py
index e38c573acae1606ee2a6b9a74c87ffb27a245984..6f1158eb0d9ce4ef5b00bd8cacbb3c30cf17218f 100644
--- a/server/trackdirect/websocket/aprsis/AprsISPayloadCreator.py
+++ b/server/trackdirect/websocket/aprsis/AprsISPayloadCreator.py
@@ -1,21 +1,13 @@
 import logging
-from twisted.python import log
-import psycopg2, psycopg2.extras
 
-import json
-from math import floor, ceil
-import datetime, time
+import time
 
 import aprslib
 
 from trackdirect.parser.AprsPacketParser import AprsPacketParser
-from trackdirect.parser.policies.StationNameFormatPolicy import StationNameFormatPolicy
 
-from trackdirect.objects.Packet import Packet
-from trackdirect.objects.Station import Station
-from trackdirect.objects.Sender import Sender
 
-from trackdirect.TrackDirectConfig import TrackDirectConfig
+import trackdirect
 
 from trackdirect.exceptions.TrackDirectParseError import TrackDirectParseError
 
@@ -39,7 +31,7 @@ class AprsISPayloadCreator():
         self.db = db
         self.responseDataConverter = ResponseDataConverter(state, db)
         self.historyResponseCreator = HistoryResponseCreator(state, db)
-        self.config = TrackDirectConfig()
+        self.config = trackdirect.TrackDirectConfig()
         self.stationHashTimestamps = {}
 
         self.saveOgnStationsWithMissingIdentity = False
diff --git a/server/trackdirect/websocket/aprsis/AprsISReader.py b/server/trackdirect/websocket/aprsis/AprsISReader.py
index e22323cf6c8b0ea7409956cabfe9bc1d48bb3513..a5be779c2b23bd221f376f3d6808b5401747c912 100644
--- a/server/trackdirect/websocket/aprsis/AprsISReader.py
+++ b/server/trackdirect/websocket/aprsis/AprsISReader.py
@@ -1,14 +1,8 @@
 import logging
-from twisted.python import log
+import trackdirect
 
-import re
-import datetime, time
-
-import aprslib
-from trackdirect.TrackDirectConfig import TrackDirectConfig
 from trackdirect.parser.AprsISConnection import AprsISConnection
 from trackdirect.repositories.SenderRepository import SenderRepository
-from trackdirect.exceptions.TrackDirectParseError import TrackDirectParseError
 
 class AprsISReader():
     """The AprsISReader class will connect to a APRS-IS server and listen for APRS-packets
@@ -31,7 +25,7 @@ class AprsISReader():
         self.aprsISConnection2 = None
 
         self.logger = logging.getLogger('trackdirect')
-        self.config = TrackDirectConfig()
+        self.config = trackdirect.TrackDirectConfig()
 
 
     def start(self):
diff --git a/server/trackdirect/websocket/responses/FilterResponseCreator.py b/server/trackdirect/websocket/responses/FilterResponseCreator.py
index a26740dbc667ab4191855e3017c131ae5c8e19db..e4a5508dd55f1e33f7aaba5d410b91d5e5c086a4 100644
--- a/server/trackdirect/websocket/responses/FilterResponseCreator.py
+++ b/server/trackdirect/websocket/responses/FilterResponseCreator.py
@@ -1,10 +1,9 @@
 import logging
-from twisted.python import log
 
-from math import floor, ceil
-import datetime, time
+import time
 
-import psycopg2, psycopg2.extras
+
+import trackdirect
 
 from trackdirect.repositories.PacketRepository import PacketRepository
 from trackdirect.repositories.StationRepository import StationRepository
@@ -12,7 +11,6 @@ from trackdirect.repositories.StationRepository import StationRepository
 from trackdirect.websocket.queries.MostRecentPacketsQuery import MostRecentPacketsQuery
 from trackdirect.websocket.responses.ResponseDataConverter import ResponseDataConverter
 
-from trackdirect.TrackDirectConfig import TrackDirectConfig
 
 class FilterResponseCreator():
     """The FilterResponseCreator is used to create filter responses, a response sent to client when client wants to filter on a station
@@ -32,7 +30,7 @@ class FilterResponseCreator():
         self.responseDataConverter = ResponseDataConverter(state, db)
         self.packetRepository = PacketRepository(db)
         self.stationRepository = StationRepository(db)
-        self.config = TrackDirectConfig()
+        self.config = trackdirect.TrackDirectConfig()
 
 
     def getResponses(self, request) :
diff --git a/trackdirect-python.dockerfile b/trackdirect-python.dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..1144bf5028fcc5e160b81106d3723536c5330f76
--- /dev/null
+++ b/trackdirect-python.dockerfile
@@ -0,0 +1,15 @@
+
+FROM ubuntu:20.04
+RUN apt-get update && apt-get install -y \
+  python3 \
+  python3-dev \
+  python3-pip \
+  git \
+  curl \
+  wget \
+  gcc \
+  && rm -rf /var/lib/apt/lists/*
+
+COPY . /root/trackdirect
+
+RUN pip install -r /root/trackdirect/requirements.txt
diff --git a/trackdirect-python2.dockerfile b/trackdirect-python2.dockerfile
deleted file mode 100644
index 889d9cbc50d866f650bd46875224bd1040b4a046..0000000000000000000000000000000000000000
--- a/trackdirect-python2.dockerfile
+++ /dev/null
@@ -1,18 +0,0 @@
-FROM ubuntu:20.04
-
-RUN apt-get update && apt-get install -y \
-  python2 \
-  python2-dev \
-  git \
-  curl \
-  wget \
-  gcc \
-  && rm -rf /var/lib/apt/lists/*
-
-RUN curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py && python2 get-pip.py
-RUN pip2 install psycopg2-binary autobahn[twisted] twisted pympler image_slicer jsmin psutil
-
-RUN git clone https://github.com/rossengeorgiev/aprs-python && cd aprs-python && pip2 install .
-
-COPY . /root/trackdirect
-