Skip to content

Python Detour Batch Mode Demo

MapTrip

If you want to create large amounts of Detour segments automatically, you should use the batch mode instead of adding all segments with separate requests.

This example shows how you can add all roadworks from an Open Data dataset published by the city of Cologne into a Detour file.

To do this, the data sets are parsed, the roadworks are filtered, matched to the road segment data, attributes such as name and time periods are added, and in the end all segments are added to the Detour file.

Loading and parsing the roadworks

The data is published as a JSON file containing an array of features.

A feature looks like this:

{
    "attributes": {
        "objectid": 979225,
        "name": "Luxemburger Straße (Sülz)",
        "datum_von": 1669104000000,
        "datum_bis": 1677247200000,
        "link": "/leben-in-koeln/verkehr/verkehrskalender/stoerungen/17249/index.html",
        "typ": 3,
        "anzeige": 1,
        "beschreibung": "Die Luxemburger Straße ist zwischen Universitätsstraße und Greinstraße in Fahrtrichtung stadtauswärts eingeengt."
    },
    "geometry": {
        "x": 6.9338580266526977,
        "y": 50.922109820387853
    }
}

The highlighted lines (name, date from, date to, type and coordinate) should be imported to the Detour file.

The value typ indicates what kind of traffic information it is. According to the metadata description of the dataset, features of type 2 are roadworks that lead to road closure and features of type 3 are other roadworks.

This code loads the data from the API, parses the JSON document and filters all features with type 2 (roadworks with blocked roads) and type 2 (other roadworks):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import json
import requests

URL_ROADWORKS = 'https://geoportal.stadt-koeln.de/arcgis/rest/services/verkehr/verkehrskalender/MapServer/0/query' \
                '?where=objectid%20is%20not%20null&text=&objectIds=&time=&geometry=&geometryType=esriGeometryEnvelope' \
                '&inSR=&spatialRel=esriSpatialRelIntersects&distance=&units=esriSRUnit_Foot&relationParam=' \
                '&outFields=%2A&returnGeometry=true&returnTrueCurves=false&maxAllowableOffset=&geometryPrecision=' \
                '&outSR=4326&havingClause=&returnIdsOnly=false&returnCountOnly=false&orderByFields=' \
                '&groupByFieldsForStatistics=&outStatistics=&returnZ=false&returnM=false&gdbVersion=&historicMoment=' \
                '&returnDistinctValues=false&resultOffset=&resultRecordCount=&returnExtentOnly=false' \
                '&datumTransformation=&parameterValues=&rangeValues=&quantizationParameters=' \
                '&featureEncoding=esriDefault&f=pjson'

ROADWORKS_BLOCKED = 2
ROADWORKS = 3

def load_features():
    response_text = requests.get(URL_ROADWORKS).text

    data_json = json.loads(response_text)
    return data_json['features']


def get_features_of_type(features, feature_type):
    result = []
    for feature in features:
        if feature['attributes']['typ'] == feature_type:
            result.append(feature)
    return result

features = load_features()

roadworks_blocked = get_features_of_type(features, ROADWORKS_BLOCKED)
roadworks = get_features_of_type(features, ROADWORKS)

Querying Detour attributes for the roadworks

For all features in roadworks_blocked a Detour segment with the attribute block will be created. The features in roadworks will get the attribute penalize. This code queries the attributes from the MapTrip Server API:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
URL_SERVER_API = 'https://api.maptrip.de/'
TOKEN = '<insert your MapTrip Server API token here>'

headers = {
    "Accept": "application/json",
    "Authorization": "Bearer {token}".format(token=TOKEN)
}


def get_attributes(path):
    response = requests.get(URL_SERVER_API + path, headers=headers)
    if response.status_code == 200:
        return json.loads(response.text)
    else:
        print(f'Failed to query attribute: {response}')
        exit(0)


attributes_blocked = get_attributes('v1/detour/attribute/block?direction=both')
attributes_penalized = get_attributes('v1/detour/attribute/penalize')

Matching the roadworks to Detour segments

To turn the features into segments, they must be matched to the road data using the endpoint [POST] /detour/match.

The function add_features_to_segments matches the coordinates to segments, adds some attributes and adds the result to segments:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import pytz
import urllib.parse
from datetime import datetime

PROVIDER = 'tomtom'
timezone = pytz.timezone('Europe/Berlin')


def match_feature_to_segment(feature):
    coordinate = [{
        'lat': feature['geometry']['y'],
        'lon': feature['geometry']['x']
    }]

    response = requests.post(URL_SERVER_API + 'v1/detour/match?provider=' + PROVIDER, json=coordinate, headers=headers)
    if response.status_code == 200:
        return json.loads(response.text)
    else:
        return None


def add_features_to_segments(features_to_add, attributes):
    for feature in features_to_add:
        match = match_feature_to_segment(feature)
        if match is not None:

            segment_attributes = attributes.copy()
            segment_attributes.append({'name': 'description', 'value': feature['attributes']['name']})

            date_from = datetime.fromtimestamp(feature['attributes']['datum_von'] / 1000, timezone).isoformat()
            date_to = datetime.fromtimestamp(feature['attributes']['datum_bis'] / 1000, timezone).isoformat()

            att_from = get_attributes('v1/detour/attribute/timedomain/start?date=' + urllib.parse.quote(date_from))
            att_to = get_attributes('v1/detour/attribute/timedomain/end?date=' + urllib.parse.quote(date_to))

            segment_attributes.append(att_from)
            segment_attributes.append(att_to)

            segment = {
                'coordinates': match['coordinates'],
                'openlr': match['openlr'],
                'attributes': segment_attributes
            }
            segments.append(segment)


segments = []

add_features_to_segments(roadworks_blocked, attributes_blocked)
add_features_to_segments(roadworks, attributes_penalized)

Removing existing segments from a Detour file

If you want to do a batch insert on a regular basis, you should remove the old segments before inserting them again. This can be done using the endpoint [DELETE] /detour/file/{fileid}/segments:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
DETOUR_FILE_ID = '<insert your file ID here>'


def delete_all_segments():
    url = URL_SERVER_API + f'v1/detour/file/{DETOUR_FILE_ID}/segments/?provider={PROVIDER}'
    response = requests.delete(url, headers=headers)
    return response.status_code == 200


if delete_all_segments():
    print(f'Deleted all existing segments from detour file')

Adding the segments to a Detour file

Using the endpoint [POST] /detour/file/{fileid}/segments the segments are are added to the Detour file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def add_segments(segments):
    url = URL_SERVER_API + f'v1/detour/file/{DETOUR_FILE_ID}/segments/?provider={PROVIDER}'
    response = requests.post(url, json=segments, headers=headers)
    return response.status_code == 200


if add_segments(segments):
    print(f'Successfully added {len(roadworks_blocked)} blocked and {len(roadworks)} normal roadworks to detour file')
else:
    print('Failed')

Complete example

Here is the complete code of the previous sections:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import json
import requests
import pytz
import urllib.parse
from datetime import datetime

PROVIDER = 'tomtom'
DETOUR_FILE_ID = '<insert your file ID here>'
URL_SERVER_API = 'https://api.maptrip.de/'
TOKEN = '<insert your MapTrip Server API token here>'

URL_ROADWORKS = 'https://geoportal.stadt-koeln.de/arcgis/rest/services/verkehr/verkehrskalender/MapServer/0/query' \
                '?where=objectid%20is%20not%20null&text=&objectIds=&time=&geometry=&geometryType=esriGeometryEnvelope' \
                '&inSR=&spatialRel=esriSpatialRelIntersects&distance=&units=esriSRUnit_Foot&relationParam=' \
                '&outFields=%2A&returnGeometry=true&returnTrueCurves=false&maxAllowableOffset=&geometryPrecision=' \
                '&outSR=4326&havingClause=&returnIdsOnly=false&returnCountOnly=false&orderByFields=' \
                '&groupByFieldsForStatistics=&outStatistics=&returnZ=false&returnM=false&gdbVersion=&historicMoment=' \
                '&returnDistinctValues=false&resultOffset=&resultRecordCount=&returnExtentOnly=false' \
                '&datumTransformation=&parameterValues=&rangeValues=&quantizationParameters=' \
                '&featureEncoding=esriDefault&f=pjson'

ROADWORKS_BLOCKED = 2
ROADWORKS = 3

timezone = pytz.timezone('Europe/Berlin')

headers = {
    "Accept": "application/json",
    "Authorization": "Bearer {token}".format(token=TOKEN)
}


def load_features():
    response_text = requests.get(URL_ROADWORKS).text

    data_json = json.loads(response_text)
    return data_json['features']


def get_features_of_type(features, feature_type):
    result = []
    for feature in features:
        if feature['attributes']['typ'] == feature_type:
            result.append(feature)
    return result


def get_attributes(path):
    response = requests.get(URL_SERVER_API + path, headers=headers)
    if response.status_code == 200:
        return json.loads(response.text)
    else:
        print(f'Failed to query attribute: {response}')
        exit(0)


def match_feature_to_segment(feature):
    coordinate = [{
        'lat': feature['geometry']['y'],
        'lon': feature['geometry']['x']
    }]

    response = requests.post(URL_SERVER_API + 'v1/detour/match?provider=' + PROVIDER, json=coordinate, headers=headers)
    if response.status_code == 200:
        return json.loads(response.text)
    else:
        return None


def delete_all_segments():
    url = URL_SERVER_API + f'v1/detour/file/{DETOUR_FILE_ID}/segments/?provider={PROVIDER}'
    response = requests.delete(url, headers=headers)
    return response.status_code == 200


def add_features_to_segments(features_to_add, attributes):
    for feature in features_to_add:
        match = match_feature_to_segment(feature)
        if match is not None:

            segment_attributes = attributes.copy()
            segment_attributes.append({'name': 'description', 'value': feature['attributes']['name']})

            date_from = datetime.fromtimestamp(feature['attributes']['datum_von'] / 1000, timezone).isoformat()
            date_to = datetime.fromtimestamp(feature['attributes']['datum_bis'] / 1000, timezone).isoformat()

            att_from = get_attributes('v1/detour/attribute/timedomain/start?date=' + urllib.parse.quote(date_from))
            att_to = get_attributes('v1/detour/attribute/timedomain/end?date=' + urllib.parse.quote(date_to))

            segment_attributes.append(att_from)
            segment_attributes.append(att_to)

            segment = {
                'coordinates': match['coordinates'],
                'openlr': match['openlr'],
                'attributes': segment_attributes
            }
            segments.append(segment)


def add_segments(segments):
    url = URL_SERVER_API + f'v1/detour/file/{DETOUR_FILE_ID}/segments/?provider={PROVIDER}'
    response = requests.post(url, json=segments, headers=headers)
    return response.status_code == 200


features = load_features()
segments = []

roadworks_blocked = get_features_of_type(features, ROADWORKS_BLOCKED)
attributes_blocked = get_attributes('v1/detour/attribute/block?direction=both')
add_features_to_segments(roadworks_blocked, attributes_blocked)

roadworks = get_features_of_type(features, ROADWORKS)
attributes_penalized = get_attributes('v1/detour/attribute/penalize')
add_features_to_segments(roadworks, attributes_penalized)

if delete_all_segments():
    print(f'Deleted all existing segments from detour file')

if add_segments(segments):
    print(f'Successfully added {len(roadworks_blocked)} blocked and {len(roadworks)} normal roadworks to detour file')
else:
    print('Failed')

When viewed in the Detour Editor, you can see blocked and penalized segments which have the name and date from the JSON dataset:

MapTrip