Speedcams: Convert iGo (txt) to Sygic (dat)

Sygic (https://www.sygic.com) is my favorite GPS navigator. I use it often on my Android to protect myself against the oppression of speed cameras! The speedcam feature will show you notifications about known locations where you could be getting a speeding ticket.

For static speed cameras, i.e. those which are permanently installed next to roads, their database is included with the app and these will be shown even in offline mode, which is nice!

The problem is that the offline camera database is not often updated, especially for regions other than the North America and Europe.

To solve this problem we can download more up to date speedcam updates from 3rd party sites, such as https://www.maparadar.com (Brazil), or https://www.speedcams.eu (Europe), or http://www.speedcamupdates.com (Worldwide) and update that offlinespeedcams.dat file ourselves!

It is worth mentioning that the "Sygic way" of updating these cameras is to create a .rupi file and use this file as your own speedcam/poi database. But I believe it is best to modify/rebuild the build-in .dat file, as it offers some "improvements" over the .rupi update method.

Requirements:

  • Python 2.7
  • The .py script

The script below will convert iGO's .txt speedcam format to Sygic's .dat. So in order to create a Sygic offline build-in database you'll have to grab an iGO database as source, which is pretty common.

Convert:

python offlinespeedcams.py thefile.txt

The command above will create (or update if a file called offlinespeedcams.dat is present) a speedcam offline database file that you can upload to your Sygic GPS.

Update Sygic App

On Android the path is:

INTERNAL_MEMORY/Android/data/com.sygic.aura/files/Maps/speedcams

Once you've created/updated your offlinespeedcams.py file, upload it to that directory and restart the Sygic app, and you'll be ready to go!

The script will also create a .html that will display a map with all cameras loaded into the .dat file.

Speedcams

Download the python script offlinespeedcams.py

The Script


# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, unicode_literals

import csv
import sqlite3
import os
import re
import datetime


def igo2sygic(files, igo_types, debug):

    speedcams = []  # [[latitude, longitude, speed, type], [...]]

    for filename in files:
        if not os.path.exists(filename):
            continue

        if debug:
            print('\n' + filename)

        with open(filename, 'rb') as csvfile:
            speedcam_csv = csv.reader(csvfile, delimiter=b',', quotechar=b'"')

            for row in speedcam_csv:
                #omit header
                if speedcam_csv.line_num == 1 and row[0] == 'X':
                    continue

                #omit bad lines
                if len(row) != 6:
                    if debug:
                        print(speedcam_csv.line_num, 'BAD LINE !!!', sep='; ')
                    continue

                longitude, latitude, kind, speed, dirtype, angle = row[0], row[1], row[2], row[3], row[4], row[5]

                #is latitude a float? if not, omit record
                try:
                    float(latitude)
                except ValueError:
                    if debug:
                        print(speedcam_csv.line_num, 'Y', latitude, sep='; ')
                    continue

                #is longitude a float? if not, omit record
                try:
                    float(longitude)
                except ValueError:
                    if debug:
                        print(speedcam_csv.line_num, 'X', longitude, sep='; ')
                    continue

                #is speed a integer? if not, try to find or set zero
                if not speed.isdigit():
                    #try to find first number in string
                    speed = re.search('\d+|$', speed).group()
                    if not speed.isdigit():
                        speed = 0
                    if debug:
                        print(speedcam_csv.line_num, 'SPEED', row[3], speed, sep='; ')

                if igo_types:
                    #is type a integer? if not, set as normal speed camera - 1
                    if not kind.isdigit():
                        kind = '1'
                        if debug:
                            print(speedcam_csv.line_num, 'TYPE', row[2], kind, sep='; ')

                    if kind in igo_types:
                        kind = igo_types[kind]
                    else:
                        if debug and igo_types:
                            print(speedcam_csv.line_num, 'TYPE', row[2], 'not defined in igotypes', sep='; ')
                        continue
                else:
                    kind = '1'

                #igo dirtype equals 0 or 2: both ways; dirtype equals 1: single direction
                both_ways = 0 if int(dirtype) == 1 else 1

                #convert to sygic format
                speedcams.append([int(float(latitude) * 100000), int(float(longitude) * 100000), int(speed), int(kind), int(angle), int(both_ways)])

    #sort by latitude, longitude, speed
    speedcams.sort(key=lambda x: (x[0], x[1], x[2]))

    print('\nSpeedCameras all: {:,}'.format(len(speedcams)))

    #eliminate duplicates:
    #append duplicated speed cameras to radars dict group by location
    radars = {}  #radars[location: latitude,longitude] = [[index, latitude, longitude, speed, mark_to_del: 0 - ok; 1 - del], [...], [...]]

    INDEX = 0
    SPEED = 3
    MARK = 4

    speedcams2 = speedcams[:]
    index1 = len(speedcams2)
    while speedcams2:
        index1 -= 1

        latitude1, longitude1, speed1, kind1, angle1, both_ways1 = speedcams2.pop()

        location = str(latitude1) + ',' + str(longitude1)

        if location in radars:
            continue

        index2 = index1
        for speedcam2 in reversed(speedcams2):
            index2 -= 1
            latitude2, longitude2, speed2, kind2, angle2, both_ways2 = speedcam2

            if latitude1 == latitude2 and longitude1 == longitude2:
                if location not in radars:
                    radars[location] = [[index1, latitude1, longitude1, speed1, kind1, angle1, both_ways1]]
                radars[location].append([index2, latitude2, longitude2, speed2, kind2, angle2, both_ways2])
            else:
                break

    #if exactly the same: leave first, mark rest to delete
    for duplicates in radars.values():
        for idx1 in range(len(duplicates)):
            for idx2, radar in enumerate(duplicates):
                if idx2 > idx1 and radar[SPEED] == duplicates[idx1][SPEED]:
                    radar[MARK] = 1

    def count2leave(duplicates):
        return sum(1 for radar in duplicates if radar[MARK] == 0)

    #one of speed is zero
    for duplicates in radars.values():
        if count2leave(duplicates) > 1:
            for radar in duplicates:
                if radar[SPEED] == 0:
                    radar[MARK] = 1

    #select lowest speed limit
    for duplicates in radars.values():
        if count2leave(duplicates) > 1:
            minspeed = min([radar[SPEED] for radar in duplicates if radar[MARK] == 0])
            for radar in duplicates:
                if radar[MARK] == 0 and radar[SPEED] != minspeed:
                    radar[MARK] = 1

    if debug:
        for location, duplicates in radars.items():
            print('\n', location, count2leave(duplicates))
            for radar in duplicates:
                print(radar)

    #delete marked
    del_list = [radar[INDEX] for duplicates in radars.values() for radar in duplicates if radar[MARK] == 1]
    del_list.sort(key=int, reverse=True)
    [speedcams.pop(d) for d in del_list]

    return speedcams


def dat2points(dat_filename):
    if not os.path.exists(dat_filename):
        return

    conn = sqlite3.connect(dat_filename, isolation_level=None)

    cursor = conn.cursor()

    cursor.execute('SELECT Latitude, Longitude, SpeedLimit, Type, Angle, BothWays FROM OfflineSpeedcam ORDER BY Latitude, Longitude')

    return cursor.fetchall()


def points2map(speedcams):
    html_filename = 'offlinespeedcams.dat_' + datetime.datetime.now().strftime('%Y%m%d_%H%M%S')

    html = []
    html.append('<!DOCTYPE html>')
    html.append('<html>')
    html.append('<head>')
    html.append('    <title>' + html_filename + '</title>')
    html.append('    <meta charset="utf-8">')
    html.append('    <style>')
    html.append('        #map {')
    html.append('            height: 100%;')
    html.append('        }')
    html.append('')
    html.append('        html, body {')
    html.append('            height: 100%;')
    html.append('            margin: 0;')
    html.append('            padding: 0;')
    html.append('        }')
    html.append('    </style>')
    html.append('</head>')
    html.append('<body>')
    html.append('<div id="map"></div>')
    html.append('<script>')
    html.append('')
    html.append('    var sygicTypes = {')
    html.append('        0: {name: "RADAR_SYMBOL", symbol: "UE37A"},')
    html.append('        1: {name: "RADAR_STATIC_SPEED", symbol: "UE37B"},')
    html.append('        2: {name: "RADAR_STATIC_RED_LIGHT", symbol: "UE37E"},')
    html.append('        3: {name: "RADAR_SEMIMOBILE_SPEED", symbol: "UE37B"},')
    html.append('        4: {name: "RADAR_STATIC_AVERAGE_SPEED", symbol: "UE37C"},')
    html.append('        5: {name: "RADAR_MOBILE_SPEED", symbol: "UE37B"},')
    html.append('        6: {name: "RADAR_STATIC_RED_LIGHT_SPEED", symbol: "UE37E"},')
    html.append('        7: {name: "RADAR_MOBILE_RED_LIGHT", symbol: "UE37E"},')
    html.append('        8: {name: "RADAR_MOBILE_AVERAGE_SPEED", symbol: "UE37C"},')
    html.append('        9: {name: "RADAR_FAV_COPS_PLACE", symbol: "UE37D"},')
    html.append('        10: {name: "RADAR_INFO_CAMERA", symbol: "UE37A"},')
    html.append('        11: {name: "RADAR_DANGEROUS_PLACE", symbol: "UE37A"},')
    html.append('        12: {name: "RADAR_CONGESTION", symbol: "UE37F"},')
    html.append('        13: {name: "RADAR_WEIGHT_CHECK", symbol: "UE37A"},')
    html.append('        14: {name: "RADAR_DISTANCE_CHECK", symbol: "UE37A"},')
    html.append('        15: {name: "RADAR_CLOSURE", symbol: "UE030"},')
    html.append('        16: {name: "RADAR_SCHOOLZONE", symbol: "UE02E"}')
    html.append('    };')
    html.append('')
    html.append('')
    html.append(
        '    var UE37A = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAACc0lEQVRo3u1Z0XXCMAyUuwErZAVWYAVWYIV0BBghHSGMACOwQjoCjHD9wPQ58jmxHYLTV+49fkC2JVk6SUbkjTeWBwBbAC36qAGsSusWo3yDMK4ANqV1zFXexbq0rkz5HVG0CxjVLSqcAKxseLhonN8rq3QvJ0rr7RpQK+UuRGZNbqIqrfvDuxqbgOxeybWl9RdCl+2ALAu1cqwEYJMaFiTZu5IGXHISk6zblVDe82QsNZKbu76UVm0sd1O8SOrD/pUGjNJmxB4VSej5aTWGNkmM02Qljji9wgB99Q2RaYkBp8B+OhTno9VA8lVEjvU/bWDP7dhNDeEj0QZNkwdjzDeRY9/d2IbGmKOInJ2vqlg6TgISChCJbQwpRfIqmlajbsBu5nl/YMlNEmBv0d1vJSLPo9VUtgBvMXYja1ifNH3wCfD1emQNa51H2YWE6XRajaHNgDeRYrSzVteQ7RTltSejk4sYEFVlyZn53SqAUyyTkLW9ApV4bpN7rrvJpL5dhcI1ca1OaFowkzyYGovq9pLDAD7zjebe0OJkNsC9RajtJ2tgIU4cJwJwPi7yegC/now7Ek8aNOzhjzzI7m3gd7a7IeFs2lT7sJkhN4z0Xr3RVfdC2tsHY0xSX2PBQi6rLbB90qfa23cGibfsAhLIo+yKCn8G9yODxNqkycg65GQPm/xsAr8u1VrA9VjykD434PdXrYjNAUuT7pWccw6ZEzYX3Ulv/WuA+API4v6AsE52yeGmBbz3/RwKnUn5mlTlhgn9JWxE+nXgS/hrwhJxMMb4eYp71btMdc3M6BVbE4o5uSdy/jj3XDyi4xh4h3rjjX+LH/cFw6xmLw4KAAAAAElFTkSuQmCC";')
    html.append(
        '    var UE37B = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAACXklEQVRo3u2Y23niMBSEZ/ZLA26BFtgSSAlJCaQEXIIpAUqAEuwScAlQAi5h9sGSI/T5im3sZP2/cJPFGV3OGQlYmCmS1pJ2ku6alpOkbVWcrAh+AyCeehA9EpLvjQIkBQCuAAIAKYDzxIGvANgZCEnua1ubZSNJVyNmcpyYLv5vbzXPJSQz08EKwMeL485IHs371Lyuuwiw6gMAF+RL6qVIgiOilD8t+gmmCN7571raCJg1P15A4x7oQeh9/kDJJpybgBTAnmRZ7dibhBDhO6/3ZsgltCf5tyJ4AADJjOQXgHcA2ZwEhCTDto1JJkOJGELA2S/vkraSYseQ3U01LdIiyRTA1xwEPIy8pBjAAcDG+dqu/aukYiOb5ZZMKeBM8uYEf/IC9wkAxMaaWGor7dgCrEeB8ext/FKAfIYsvWagLI3aLNKm49R5v2nRvmgrKTBZKZN0Q26bSyGZSApRsunfShrfANR77nK6Fql1y0GycZXGNBcr8bRZ7CvAHfW047MpUNj1yQS4677LZiwOSxjQVpQiadVwa7Bx2sZq5u6mUXN0rWI39gwAwMGpsJ+oX0oZgE9bOyRFqMk+r5oBmerrPrOTdPHaRK6VMHajicYZGEqATMCtUqmkgy/OfL/W40XaSwVYDnL2hdNPoIqbPq9d1EXAGCeyLYCtF1fjIDmeqlNKHfNI2YVY0hGPt3CDCRjk5NTACrnd7gzbNFLuNCOMcz9Ud5I7OgVv4ffhpbxnufeJoa+V6HUcHLCP5zHV8/TEyF8HqbQjCvMZxTbP5US2CPixLAKmZhGw8L/zD2iHYmmrdwfBAAAAAElFTkSuQmCC";')
    html.append(
        '    var UE37C = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAACpElEQVRo3u1Z7ZHiMAyVbq4BKIErgRZyJbAlpAWuBLYEKAFKCCWwJSQlQAlvfyCBosuHw9ohd8ubYZixZVlPkWXZJnrhHwCADECB8bAFsIhl/EKUngDMRnDWEsAZQBlL4UoI5KmNN3NuZM7er/AjQF/N6/J5U2Ej01xCyYYQ8MgSOn85dMAjBCaFFAQuRPSHiOYsIKJf0jZ5AgdmnjPzOzPf4piZK2Z+J6I5ER2nSmDHzG9dAsx8YebfMUnEIlCRCREAOYDSZJcCgF38bzQg04xBYKchA2BLRFsisjk8I6JC9xKR3U2JwFGMz4moa8OzJUKUMAohcKTr5/5oE2Bm7QvZI1byX3XIVDJfb5j97BNg5oqu2SMEIRtRb3nAzAciOoRMGCWEAKjhHwHi6vko1WasNaChE+K1gxszLlx6VJRaavcUe7nIzKRk9iieRQCSPlWmcx8AsG/R8VQCNRId4/cd459OQMNp7cbMAKxd2JS4nvZmRuckCIRib3TquhlMoHcfSIiVLOoL3Te39MD9vJoCm69b+B3x4LrozVghiFqNDkRI2TEOJBWeHslAkwXqV5FRwqUJr2uVZyPlRnag++KuvqLohf8KLl3aGj+TtnPDmLX0laZtYdotboejFMYvu3ZTQ27l2jWlro0eLa1zpz/dUdN4bCMGnK23TL89nc0M4UUTodFgap4F7jW892AtjAypUwOh5E9W1vjMGaJPT4WTq4VRQ/ioHpgxNQyxa8hGdotrMUbvdTLnSb02WUnIaExHuQt9GGi+BoH1rsjpq+bZhE/R0O+zmG2PG1omXNQo/ek6ODl5DSMNn7ylv4Tc6jkCcd6IzYR6FeLTpl2QS9Pu87t/6fTp2CMegbZP3uDNTcuYfYveJf6+IyqF/HjZ6YXvjk9KkDD5QQnRFAAAAABJRU5ErkJggg==";')
    html.append(
        '    var UE37D = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAADIUlEQVRo3uVZ23XiMBC9k5MGSAluwSnBW4JTAlsClMCWQEqAEkwJUIIpwZRw84FsxrL8kmxDzt6fBCTNW6OZAfifQXJN8kByE0gnJ7khGS+tQME7DgE0Ij6wG3v+LVCHk/mbBNDQVr9MroCxUJuAJcNVgPv1uZNrA8nU2zwmxkly7yKs3L8OpH92rMUkz2Z9vJctAWliPlXrK/N97mslc3kb94jk3uKd+3ogVZe1xIFkVCrhRbjJZ6X45Q5+/nyMpQ/aGlMJ3mIwp8enIl54xeM4Pvtgq/96GGsnPXs2nAb5EK8afsO8otIXjUs31vquQ6A+xbOWc7G1LzZCa1n674RKjzb2ak/OdvgqsLNkKBx7aum26yX+ArAFcFPfHdX/N8wIEbmh/jKfjDz/RhMz1rBDKJ4o/ls9RzKh5ws/VKldmLwVps33AwQ/BArswpkefYWMFD4CkAGI1NcXPO7GVUSOA+jEeJTgEQAdJlcAXyIyurQeooBOZ5lRaCraMR+ZrZiSdslAP1reHVgPj5VS4hxO8UFYt32zFXKGV6J4BfXamqiuzWct5Bz8wkKJ9Rcxm1t4wzOo0beJ6dhfJlejVm4UQSGrcn6xlPCG7zrYcKwXdXsvIv4K6NBtDaM361CtkQaghb4uqYAp5sqCMWW9gq26wtpLbIRuww3AVkS+rTMp6i9zivqsx4VvyyBHEbladHcAutLoUUS+RB2IAPiNLp6Di4h86hDSLvsNaK+52N1tPRu1Cy0O4WMA09Uh0+MmIh/lB1dLaZcKVwCfIiIA/mCZbHQE8GF4/rXWugfJbDYrkbU+RyupkTtkssc36y4FdPyfB+wJmtSxOUDeOPbYRqvugSuExlZ/FxE5jTxTwdHBDal73HvongfZw6bEWg/qnhweGBJCWRfBRkyWShjhl0ixGR/j9rVjveoI3x063CwXRQDO7KwyJkcCoOjgWWVC1x3onSq8ACoZXQrY48RXw1aPXBoKWGXsyyP0d+Kn4z2cRDD0ZC/GvZ8IA5s/cdrYc4bZEO/TiL65a/+rz/uDVrZwmXlIZp8HOeRY80lTkcXwA9Aco7XKju/lAAAAAElFTkSuQmCC";')
    html.append(
        '    var UE37E = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAACQElEQVRo3u2a4XHiQAyFP91cA1wJbsFXApRgSvCVQEpISoASoARcAikhlBBKePeD9cDl7DXBWrOZyZvJ8EO7jN5Kq30SgW98YUgqJe01Ds+Pcr6Q9D7S+RaHe/34MYJDDcyczqOUVE9NoIzYdsDCrgA8AafInsLpMG5DJPdXkT0zSYeefet7/BgTgS68mNlLn9HMTsCSeCQeS2BogZkdgU2OBF7DCd+0NkcCn0mLLFPoMyXVq/y6Eigl3epYeeO6SQkArIYWSCo4P4J5Eoi9qCFCWzJNoRZrSVtJ/6RJIHbAMX0AfiYgAFABlaREX39BighMilQRgLN4u8Y8/LkiRQQa4FfQRO2DtTOzBWcdlAd61Ogh2KoO2z6o0S7b3WrUm8A84uA1wb0XAW8x1xB/zEpJFZmq0Tbfh+p8QaZi7iORSeAq5sJnM7DulUzF3CzIhVjzvgn3JFsx1w6pFvwfiScz+xMGWdNOILoQmUq8h0rTtWcdKbF3ldEUUmIGbCWduDT5rj1AagLXRJLPPVOr0R3DE7nHQPGpdN2xvgiNjusd8I7ACfgNNB8u7BtQmtkSRxkxCj0RqMNJv8Ui02N/eAROZrbhLOb66vxa56lElmKuHRdWA+sqMh0ttnAbmUxNoHV86HSPniS9R4sF8RH70cx2DKdZeijeE6+6bKFCufbE3lKilLQ1s6WkDRf9czSznaQ54Nq8p7jEVXi4aqAJ45UinPCe/vyfVm5IepYvkqjVGIEsfugeS+Lr/qvBN5zwF1uu96Vz+2R8AAAAAElFTkSuQmCC";')
    html.append(
        '    var UE37F = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAACh0lEQVRo3u1Y23GjQBDsuXICXAikwIWAQ5BDIAU5BCkEOwQRghSCFIIIQQ6h/cEsN2Bgd0ESdXV0lUrFY96zO70AK1asWLHif4b4XiC5AZDqrxh4rQLwCQAisl86KOv8B2t8aCBD721J7vTdK8lkad+dUxxzvEcmU5nD0s6nLvMzAs+WDGCyEyST2Mo9IoAryfMM+aetgRdj1O02lf6/T1UqIl/PCqAByRvbWK4FItDMAZLsPLsAKM11BqAvqC8AewAnEbksFgnJDach7+ixMyEWB5WPX0PG6EGvM9NWaYB8qov/Xsj9XptFjHrhAsAJAETkQvICIEfdJmPOJwCORkdDLQCUIlKNyOao2xNqyzl+JPknuC1JnjXyY08FesvdUz2S3EaXf7iS1xjB2PKnxqDDbo7zxpfM6AxLCP9O3xBsjZwjfOHZCvMnTi/J3OP0mZ3dgTVtcG1WBBkKD8BWdnAm/fLoqVBP5HcAbz0TNgeQoF7kJe4IXfhOp5+TaTbPnawXPe8VLiNm8Uaz1hCoLVf9rflNY7ocHnazdp4Re9mAvYZovkTqHIq8YM1EStQtZYfQ2FHUydi9vhKRUpMyNMyateg9E3cy4obOBiF9eX843lWJSBkdgAnkZrPwZLSms28X6nM+W9B5oNNW0QF0FFSaEQHwijZnOkkEALx17Oz1/m8oP1PMa10zIX/sPmxzougDkZG9de4n5llrMk+pwDPaZ4z9eqm9L0sHmw03VJSKWPYa9W3IDC2Hnd5PSB7tg7kBxJC+R6D1tWRKC5WeEj8ara8l0QEoyVoygNbpbkoF/n3wJ2u1mE0xOHyUvc3V7QwMnZfvwkp7djTqddCXihUrVqxYEYxvjk3SBt4cZTYAAAAASUVORK5CYII=";')
    html.append(
        '    var UE02E = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAC7klEQVRo3u1Y3XnrIAyFbuAVvIJX8ApZIXcE3xHcEdIR3BGSEbyCM4I7wrkPhUYoAoTJT+/3RS+JAQkdJMQBY17ykpf812JrDQBojTE79/lhrf16Niit4x2AFaGsALpn+6YFcIQsh2f7pgWwRgAsj/TjrUK3KWz/dQDOhe2/DsBnpP39kQCqBMBQU4FcJRsADM8CsCcA5gK9BsDMNr9a/xaOdwAOUgVyK9pk9AfIstP6UON8bHK1MwkbY6k/RZvY0QZtvk6J3I5VqvuW4Eja5GQU7DQPTyEA7QbnvRwEeyMbs9579acKAJDSie2F4vwvcb4vXXHI6dYyu5QQrgD29wJw1HhNZCbAab3vFRGdOdBa5/lKxlhodLWJTq+M6KIF8ZZwvHUrScP61+hLHa0oX4k+SVrzXYazc4kA8F3OZmMM5TbvzrBWqO6Z/WrKZWeMKb8csZRZ8V0pWmwro42z6fO9AbArtKEneszJPgGsaHKv6/6PCr0R4V7TH3BugwWEDOVl1Mvq9L0zA/m/pICzObczVcivD0VRiOinSOGBRX37KY1r3l4cBVwuLhNpaxI6RyHy24ketuV/sNoRu1HQZIyPXt3h5lZxyboayoJEFcno+grmN33dtVMI+ZKIzIxM5cikEHA5tTvStp0rIbz//kwgTAIoXucEHS57MraPRaDkRhbkoLX2RD7PJqQLmteJnn3/ic1nrT1Za8XnmhIAtAoE3Ma9SNN3og7sIBSEgvy01n4YY06R/npBSKlnoZ9TjTFhi+d/69p5mt7uvQhhBRojY+imPiZsUT40MWD00Ftxq7uBtGLCmE4JgPIhzrk4bZlMrTCjh8zYWQHghw8p5gNqXytI/q/Iv7qNKQAsSql9QvfDkptX45BqJXAhaWukn96Fu4ytSQNWWqEB1ywym4su9Fc83m3OgRWCLMNkRUFXkRBnn0tGT6LHU64vY9MD1lMIXEiUj4JPoTWhE7vwSOeFt9sr7eUOxJe8pEb+ARnn4rUoLVH1AAAAAElFTkSuQmCC";')
    html.append('    var UE030 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAB1UlEQVRo3u1Z25HCMAyUmWsgLdACLdBCWqCFtJAWaAFKgBKOEqCEUMLex5kZjyPbimPH8V32iwxE3pXWyA+iDRtmQaUIAmBPRK1+7B0/uxLRg4heSqlraeEf4h2AJ+LQAziUJJ4KF13BRYgfZ2Q8hK6mrLtwA9DkIH8WDN5rkXtHjA6yOfNMaqkA+W8Ap4iYh0DcNCI8thliiDuE3DzJibcTgHYpn3oSdYkN2Di8ek5JXJiw6ZV2+DMuG/NFDJMqDmDP+TE3eWN8zk7yHuHI/qItX0/g6VXQ3rfRC8ZMLYBzQbgKTPmm+S+tCNsJYRvjd2FVNPsGlwNThSb00lDS+wwfey6M/lJ3pmIiMhW+lVKPkgKI6G49j5YXO8+XdyoPO4EjR/gEvLiIyAeu49ocvBWw8S6XeDmHnSTKmvGnBRRpYAEOI0t9GZ/tCcP2AKVUkrMkIexJOxJgVkAkYGHYHPx9ienEx5LsmU7chl6w10LZdmAC8lFroVPtq9GGsVGJ/QCXfdmuDDXvyPTLde+JdZCeCVLHqYQOVPe5UCDg+k/mBIHXfzZqDFLv6bRQxAef+4HGEaPM/YBFIDfy3NAYIuq9I8tYjeVuKR1Cit4T/++b+g0J8AM1HTteeD/mQgAAAABJRU5ErkJggg==";')
    html.append('')
    html.append('    var map;')
    html.append('    var markers = [];')
    html.append('    var types = {};')
    html.append('')
    html.append('    function initMap() {')
    html.append('        map = new google.maps.Map(document.getElementById("map"), {')
    html.append('            zoom: 5,')
    html.append('            center: new google.maps.LatLng(48.208775, 16.372477)')
    html.append('        });')
    html.append('')
    html.append('        var speedCams = [')

    for sc in speedcams:
        latitude, longitude, speed_limit, kind, angle, both_ways = sc

        latitude = str(float(latitude) / 100000)
        longitude = str(float(longitude) / 100000)

        html.append('{y:' + latitude + ',x:' + longitude + ',s:' + str(speed_limit) + ',t:' + str(kind) + '},')

    html.append('        ];')
    html.append('')
    html.append('        speedCams.forEach(function (speedCam) {')
    html.append('')
    html.append('            types[speedCam.t] = (speedCam.t in types) ? types[speedCam.t] += 1 : 1;')
    html.append('')
    html.append('            markers.push(')
    html.append('                new google.maps.Marker({')
    html.append('                    map: map,')
    html.append('                    position: new google.maps.LatLng(speedCam.y, speedCam.x),')
    html.append('                    title: "SPEED: " + speedCam.t + "; TYPE: " + speedCam.t + ";",')
    html.append('                    type: speedCam.t,')
    html.append('                    visible: false')
    html.append('                }));')
    html.append('        });')
    html.append('')
    html.append('        function markersShowHide(type, visible) {')
    html.append('            console.log(type, visible);')
    html.append('            markers.forEach(function (marker) {')
    html.append('                if (marker.type == type) {')
    html.append('                    marker.setVisible(visible);')
    html.append('                }')
    html.append('            });')
    html.append('        }')
    html.append('')
    html.append('        var controlUI = document.createElement("div");')
    html.append('        controlUI.style.backgroundColor = "#fff";')
    html.append('        controlUI.style.border = "2px solid #fff";')
    html.append('        controlUI.style.borderRadius = "3px";')
    html.append('        controlUI.style.boxShadow = "0 2px 6px rgba(0,0,0,.3)";')
    html.append('        controlUI.style.lineHeight = "35px";')
    html.append('        controlUI.style.marginLeft = "10px";')
    html.append('        controlUI.style.textAlign = "left";')
    html.append('        controlUI.title = "Filter markers";')
    html.append('')
    html.append('        var centerControlDiv = document.createElement("div");')
    html.append('        centerControlDiv.index = 1;')
    html.append('        centerControlDiv.appendChild(controlUI);')
    html.append('')
    html.append('        for (var type in types) {')
    html.append('            var controlCheckBox = document.createElement("input");')
    html.append('            controlCheckBox.value = type;')
    html.append('            controlCheckBox.type = "checkbox";')
    html.append('            controlCheckBox.checked = false;')
    html.append('            controlCheckBox.style.margin = "0px 5px 0px 5px";')
    html.append('            controlCheckBox.style.verticalAlign = "middle";')
    html.append('')
    html.append('            controlCheckBox.addEventListener("click", function () {')
    html.append('                markersShowHide(this.value, this.checked);')
    html.append('            });')
    html.append('')
    html.append('            var controlImage = document.createElement("img");')
    html.append('            controlImage.src = (type in sygicTypes) ? eval(sygicTypes[type].symbol) : UE37A;')
    html.append('            controlImage.width = "24";')
    html.append('            controlImage.height = "24";')
    html.append('            controlImage.style.backgroundColor = "rgba(0,0,0,0.7)";')
    html.append('            controlImage.style.borderRadius = "10%";')
    html.append('            controlImage.style.margin = "0px 5px 0px 5px";')
    html.append('            controlImage.style.verticalAlign = "middle";')
    html.append('')
    html.append('            var controlText = document.createElement("span");')
    html.append('            controlText.style.color = "rgb(25,25,25)";')
    html.append('            controlText.style.fontFamily = "Roboto,Arial,sans-serif";')
    html.append('            controlText.style.fontSize = "11px";')
    html.append('            controlText.style.lineHeight = "38px";')
    html.append('            controlText.style.verticalAlign = "middle";')
    html.append('            controlText.innerHTML = type.toString() + " " + ((type in sygicTypes) ? sygicTypes[type].name : "") + " (" + types[type] + ")";')
    html.append('')
    html.append('            var controlHolder = document.createElement("div");')
    html.append('            controlHolder.style.paddingLeft = "5px";')
    html.append('            controlHolder.style.paddingRight = "5px";')
    html.append('            controlHolder.style.whiteSpace = "nowrap";')
    html.append('')
    html.append('            controlHolder.appendChild(controlCheckBox);')
    html.append('            controlHolder.appendChild(controlImage);')
    html.append('            controlHolder.appendChild(controlText);')
    html.append('')
    html.append('            controlUI.appendChild(controlHolder);')
    html.append('        }')
    html.append('')
    html.append('        map.controls[google.maps.ControlPosition.LEFT_CENTER].push(centerControlDiv);')
    html.append('    }')
    html.append('</script>')
    html.append('<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap"></script>')
    html.append('</body>')
    html.append('</html>')

    with open(html_filename + '.html', 'w') as html_file:
        html_file.write('\n'.join(html))


def save_dat(speedcams, dat_filename, unit, debug):
    db_is_new = not os.path.exists(dat_filename)

    speed_limit_units = 1 if unit == 'mph' else 0

    conn = sqlite3.connect(dat_filename, isolation_level=None)

    if db_is_new:
        db_schema = """
                    CREATE TABLE Info (Version REAL not null, CreatedAt text not null, Note nvarchar(255) null);
                    CREATE TABLE OfflineSpeedcam (Id int not null, Latitude int not null, Longitude int not null, Type byte not null, Angle int null, BothWays bit, SpeedLimit int, Osm bit, PairId int null, SpeedLimitUnits byte null);
                    CREATE TABLE OfflineZone (Id int not null, Type byte not null, SpeedLimit smallint not null, LatitudeMin int not null, LongitudeMin int not null, LatitudeMax int not null, LongitudeMax int not null);

                    CREATE INDEX speedcamsLatLon ON OfflineSpeedcam (Latitude, Longitude);                    
                    """

        conn.executescript(db_schema)
        conn.commit()

    cursor = conn.cursor()

    #get max id
    cursor.execute('SELECT coalesce(max(Id), 0) AS max_id FROM OfflineSpeedcam')
    max_id = cursor.fetchone()[0]

    cursor.execute('SELECT Latitude, Longitude FROM OfflineSpeedcam ORDER BY Id')

    offspeedcams = cursor.fetchall()

    offspeedcams = set(offspeedcams)

    #generate SQL
    speedcams_added = []
    sql = []
    for sc in speedcams:
        latitude, longitude, speed_limit, kind, angle, both_ways = sc

        if (latitude, longitude) not in offspeedcams:
            max_id += 1
            sql.append('INSERT INTO OfflineSpeedcam (Id, Latitude, Longitude, Type, Angle, BothWays, SpeedLimit, Osm, PairId, SpeedLimitUnits) VALUES ({Id}, {Latitude}, {Longitude}, {Type}, {Angle}, {BothWays}, {SpeedLimit}, 0, NULL, {SpeedLimitUnits});'.format(Id=max_id, Latitude=latitude, Longitude=longitude, SpeedLimit=speed_limit, Type=kind, Angle=angle, BothWays=both_ways, SpeedLimitUnits=speed_limit_units))
            speedcams_added.append(sc)
        else:
            if debug:
                print('Already exists in db', (latitude, longitude))

    if sql:
        sql.append('DELETE FROM Info;')
        sql.append('INSERT INTO Info (Version, CreatedAt, Note) VALUES ({version}, "{createdAt}", NULL);'.format(version=2, createdAt=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
        conn.executescript('BEGIN TRANSACTION')
        conn.executescript(''.join(sql))
        conn.executescript('COMMIT')

    return speedcams_added


if __name__ == '__main__':
    import argparse
    import fnmatch
    import locale

    arg_parser = argparse.ArgumentParser(description='Sygic offlinespeedcams.dat generator. Convert Speed Camera / Photo Radar from IGO to Sygic', formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    arg_parser.add_argument('-t', '--type', choices=['igo'], default='igo', help='Input type')
    arg_parser.add_argument('-d', '--dat', type=str, default='offlinespeedcams.dat', help='DAT file')
    arg_parser.add_argument('-u', '--unit', choices=['kmh','mph'], default='kmh', help='Unit: kmh or mph')
    arg_parser.add_argument('-it', '--igotypes', action=type(b'', (argparse.Action,), dict(__call__=lambda self, parser, namespace, values, option_string: getattr(namespace, self.dest).update(dict([v.split('=') for v in values.replace(';', ',').split(',') if len(v.split('=')) == 2])))), default={'1':'1','2':'6','3':'2','4':'4','5':'5','6':'2','7':'2','8':'11','9':'16','10':'10','11':'6','12':'2','13':'10','15':'12','17':'9','31':'11'}, metavar='KEY1=VAL1,KEY2=VAL2;KEY3=VAL3...', dest='igo_types', help='You can specific your own types, first IGO, second Sygic')
    arg_parser.add_argument('--debug', action='store_true', help='Print debug data')
    arg_parser.add_argument('--map', action='store_true', default=True, help='Generate Google Maps with added points')
    arg_parser.add_argument('--dat2map', action='store_true', default=False, help='Generate Google Maps with points from offlinespeedcams.dat')

    arg_parser.add_argument('files', nargs='*', help='Source files')

    args = arg_parser.parse_args()


    def list_dir(cur_dir, mask, files_list):
        cur_dir = os.path.normpath(cur_dir)

        for f in os.listdir(cur_dir):
            file_name = os.path.normpath(os.path.join(cur_dir, f))
            if not f.startswith('.') and os.path.isdir(file_name):
                list_dir(file_name, mask, files_list)
            if f.startswith('.'):
                continue
            if fnmatch.fnmatch(f, mask) and os.path.isfile(file_name):
                files_list.append(file_name)


    all_files = []
    for filename in args.files:
        filename = os.path.abspath(os.path.normpath(unicode(filename, locale.getpreferredencoding())))

        if os.path.isfile(filename):
            all_files.append(filename)
        else:
            if os.path.isdir(filename):
                list_dir(os.path.dirname(filename), '*.*', all_files)
            else:
                list_dir(os.path.dirname(filename), os.path.basename(filename), all_files)

    if args.type == 'igo' and all_files:
        speedcams = igo2sygic(all_files, args.igo_types, args.debug)

        print('\nSpeedCameras after cleaning: {:,}'.format(len(speedcams)))

        speedcams_added = save_dat(speedcams, args.dat, args.unit, args.debug)

        print('\nSpeedCameras added: {:,}'.format(len(speedcams_added)))

        if args.map and speedcams_added:
            points2map(speedcams_added)

    if args.dat2map:
        dat_points = dat2points(args.dat)
        if dat_points:
            points2map(dat_points)
{{ message }}

{{ 'Comments are closed.' | trans }}