-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathnvdbswe2osm.py
1512 lines (1136 loc) · 53.9 KB
/
nvdbswe2osm.py
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# -*- coding: utf8
# nvdbswe2osm
# Converts NVDB data to OSM.
# Program loads geojson file for municipality.
# Order "homogeniserad" file from Lastkajen at Trafikverket and convert to geojson in QGIS or elsewehere.
# No dependencies beyond standard python.
import json
import sys
import copy
import math
import time
from xml.etree import ElementTree as ET
version = "0.5.0"
debug = False # Add extra tags for debugging/testing
angle_margin = 45.0 # Maximum turn at intersection before highway is split into new way (degrees).
bridge_margin = 50.0 # Bridges over this length is always tagged as bridge, instead of tunnel duct for road underneath (meters)
coordinate_decimals = 7 # Number of decimals in coordinates
simplify_method = "refname" # Options for generating long ways before output: "recursive", "route" or "refname"
simplify_factor = 0.2 # Minimum deviation permitted for node in segment polygons (meters).
# Set to 0 to avoid simplifying polygons.
segment_output = False # When true: Output each highway segments as in input file, without creating longer ways
# Conversion table for nvdb attribute names, to make them more readable in code.
# Some of them are not used.
nvdb_attributes = {
'Hogst_55_30': 'Begränsat axel-boggitryck/Högsta tillåtna tryck',
'L_Blandskydd_2': 'Bländskydd(V)',
'R_Blandskydd_2': 'Bländskydd(H)',
'Ident_191': 'Bro och tunnel/Identitet',
'Langd_192': 'Bro och tunnel/Längd',
'Namn_193': 'Bro och tunnel/Namn',
'konst_190': 'Bro och tunnel/Konstruktion',
'Brunn___Slamsugning_2': 'Brunn-slamsugning',
'L_Mater_301': 'Bullerskydd-väg/Materialtyp(V)',
'R_Mater_301': 'Bullerskydd-väg/Materialtyp(H)',
'Barig_64': 'Bärighet/Bärighetsklass',
'Barig_504': 'Bärighet/Bärighetsklass vinterperiod',
'Namn_457': 'C-Cykelled/Namn',
'C_Cykelled': 'C-Cykelled',
'C_Rekommenderad_bilvag_for_c': 'C-Rekommenderad bilväg for cykel',
'F_Cirkulationsplats': 'Cirkulationsplats(F)',
'B_Cirkulationsplats': 'Circulationsplats(B)',
'Vagde_10379': 'Driftbidrag statligt/Vägdelsnr',
'Vagnr_10370': 'Driftbidrag statligt/Vägnr',
'drift_2': 'Driftområde/namn',
'entre_380': 'Driftområde/entreprenör',
'Driftvandplats_2': 'Driftvändplats',
'LageF_83': 'Farthinder/Läge',
'TypAv_82': 'Farthinder/Typ',
'FPV_dagliga_personresor_2': 'FPV-Dagliga personresor',
'FPV_godstransporter_2': 'FPV-Godstransporter',
'FPV_kollektivtrafik_2': 'FPV-Kollektivtrafik',
'FPV_langvaga_personresor_2': 'FPV-Långväga personresor',
'FPV_k_309': 'Funktionellt prioriterat vägnät/FPV-klass',
'Framk_161': 'Framkomlighet för vissa fordonskombinationer/Framkomlighetsklass',
'Klass_181': 'Funktionell vägklass/Klass',
'Farjeled': 'Färjeled',
'Farje_139': 'Färjeled/Färjeledsnamn',
'F_Forbjuden_fardriktning': 'Förbjuden färdriktning(F)',
'B_Forbjuden_fardriktning': 'Förbjuden färdriktning(B)',
'F_Forbud_mot_trafik': 'Förbud mot trafik(F)',
'B_Forbud_mot_trafik': 'Förbud mot trafik(B)',
'Namn_130': 'Gatunamn/Namn',
'GCM_belyst_1': 'GCM-belyst',
'Trafi_86': 'GCM-passage/Trafikanttyp',
'Passa_85': 'GCM-passage/Passagetyp',
'GCM_passage_1': 'GCM-passage',
'L_Separ_500': 'GCM-separation/Separation(V)',
'R_Separ_500': 'GCM-separation/Separation(H)',
'GCM_t_502': 'GCM-vägtyp/GCM-typ',
'L_Gagata': 'Gågata(V)',
'R_Gagata': 'Gågata(H)',
'L_Gangfartsomrade': 'Gångfartsområde(V)',
'R_Gangfartsomrade': 'Gångfartsområde(H)',
'F_Hogst_225': 'Hastighetsgräns/Högsta tillåtna hastighet(F)',
'B_Hogst_225': 'Hastighetsgräns/Högsta tillåtna hastighet(B)',
'Hallplats_2': 'Hållplats',
'Hojdh_145': 'Höjdhinder upp till 4,5 m/Höjdhinderidentitet',
'Hojdh_144': 'Höjdhinder upp till 4,5 m/Höjdhindertyp',
'Fri_h_143': 'Höjdhinder upp till 4,5 m/Fri höjd',
'F_Beskr_124': 'Inskränkningar för transport av farligt gods/Beskrivning(F)',
'B_Beskr_124': 'Inskränkningar för transport av farligt gods/Beskrivning(B)',
'Plank_92': 'Järnvägskorsning/Plankorsnings-Id',
'Senas_107': 'Järnvägskorsning/Senast ändrad',
'X_koo_105': 'Järnvägskorsning/X-koordinat',
'Y_koo_106': 'Järnvägskorsning/Y-koordinat',
'Konta_103': 'Järnvägskorsning/Kontaktledning',
'Kort__104': 'Järnvägskorsning/Kort magasin',
'Antal_96': 'Järnvägskorsning/Antal spår',
'Jvg_b_93': 'Järnvägskorsning/Jvg-bandel',
'Vagpr_98': 'Järnvägskorsning/Vägprofil tvär kurva',
'Vagpr_99': 'Järnvägskorsning/Vägprofil brant lutning',
'Vagsk_100': 'Järnvägskorsning/Vägskydd',
'Porta_101': 'Järnvägskorsning/Portalhöjd',
'Tagfl_102': 'Järnvägskorsning/Tågflöde',
'Vagpr_97': 'Järnvägskorsning/Vägprofil farligt vägkrön',
'Jvg_k_94': 'Järnvägskorsning/Jvg-kilometer',
'Jvg_m_95': 'Järnvägskorsning/Jvg-meter',
'Katastrofoverfart_2': 'Katastroföverfart',
'F_Korfa_517': 'Kollektivkörfält/Körfält-Körbana(F)',
'B_Korfa_517': 'Kollektivkörfält/Körfält-Körbana(B)',
'Lever_292': 'Leveranskvalitet DoU 2017/Leveranskvalitetsklass DoU 2017',
'Miljozon': 'Miljözon',
'mittr_10': 'Mittremsa/bredd',
'Motortrafikled': 'Motortrafikled',
'Motorvag': 'Motorväg',
'F_Omkorningsforbud': 'Omkörningsförbud(F)',
'B_Omkorningsforbud': 'Omkörningsförbud(B)',
'L_Rastficka_2': 'Rastficka(V)',
'R_Rastficka_2': 'Rastficka(H)',
'Huvud_117': 'Rastplats/Huvudman',
'Rastp_118': 'Rastplats/Rastplatsnamn',
'Antal_124': 'Rastplats/Antal markerade parkeringsplatser för buss',
'Antal_123': 'Rastplats/Antal markerade parkeringsplatser för husbil',
'Antal_121': 'Rastplats/Antal markerade parkeringsplatser för lastbil',
'Antal_122': 'Rastplats/Antal markerade parkeringsplatser för lastbil+släp',
'Antal_120': 'Rastplats/Antal markerade parkeringsplatser för personbil+släp',
'Ovrig_125': 'Rastplats/Övrig parkeringsmöjlighet',
'Lanka_127': 'Rastplats/Länkadress',
'Resta_131': 'Rastplats/Restaurang',
'Serve_132': 'Rastplats/Servering',
'Hundr_133': 'Rastplats/Hundrastgård',
'Rastplats': 'Rastplats',
'Antal_119': 'Rastplats/Antal markerade parkeringsplatser för personbil',
'Dusch_126': 'Rastplats/Duschmöjlighet',
'Rekom_185': 'Rekommenderad väg för farligt gods/Rekommendation',
'L_Raffe_396': 'Räffla/Räffeltyp(V)',
'R_Raffe_396': 'Räffla/Räffeltyp(H)',
'Slitl_152': 'Slitlager/Slitlagertyp',
'F_Stigningsfalt': 'Stigningsfält(F)',
'B_Stigningsfalt': 'Stigningsfält(B)',
'Vagna_406': 'Strategiskt vägnät för tyngre transporter/Vägnät för tyngre transporter',
'TEN_T_489': 'TEN-T Vägnät/TEN-T-Logisk-Länk-id',
'TEN_T_488': 'TEN-T Vägnät/TEN-T-Länk-id',
'Tillg_169': 'Tillgänglighet/Tillgänglighetsklass',
'ADT_f_117': 'Trafik/ÅDT fordon',
'ADT_l_115': 'Trafik/ÅDT lastbilar',
'ADT_a_113': 'Trafik/ÅDT axelpar',
'Matar_121': 'Trafik/Mätårsperiod',
'Tattbebyggt_omrade': 'Tättbebyggt område',
'Viltpassage_i_plan_2': 'Viltpassage i plan',
'L_Uppsa_320': 'Viltstängsel/uppsättningsår(V)',
'R_Uppsa_320': 'Viltstängsel/uppsättningsår(H)',
'L_Vilts_319': 'Viltstängsel/viltstängselstyp(V)',
'R_Vilts_319': 'Viltstängsel/viltstängselstyp(H)',
'L_Viltuthopp_2': 'Viltuthopp(V)',
'R_Viltuthopp_2': 'Viltuthopp(H)',
'drift_50': 'Vinter2003/driftsklass',
'L_VVIS': 'VVIS(V)', # Weather stations
'R_VVIS': 'VVIS(H)',
'slitl_25': 'VV-Slitlager/typ',
'Bredd_156': 'Vägbredd/Bredd',
'Passe_73': 'Väghinder/Passerbar bredd',
'Hinde_72': 'Väghinder/Hindertyp',
'Vagha_7': 'Väghållare/Väghållarnamn',
'Forva_9': 'Väghållare/Förvaltningsform',
'Vagha_6': 'Väghållare/Väghållartyp',
'Kateg_380': 'Vägkategori/Kategori',
'VN_Europavag': 'Europaväg',
'F_Vagnummer': 'Vägnummer(F)',
'B_Vagnummer': 'Vägnummer(B)',
'Europ_16': 'Vägnummer/Europaväg',
'Huvud_13': 'Vägnummer/Huvudnummer',
'Under_14': 'Vägnummer/Undernummer',
'Lanst_15': 'Vägnummer/Länstillhörighet',
'L_vagra_282': 'Vägräcke/vägräckstyp(V)',
'R_vagra_282': 'Vägräcke/vägräckstyp(H)',
'L_gemen_283': 'Vägräcke/gemensamt mitträcke(V)',
'R_gemen_283': 'Vägräcke/gemensamt mitträcke(H)',
'L_vagra_277': 'Vägräckesavslutning/vägräckesavslutningstyp(V)',
'R_vagra_277': 'Vägräckesavslutning/vägräckesavslutningstyp(H)',
'Vagtr_474': 'Vägtrafiknät/Nättyp',
'korfa_52': 'Vägtyp/körfältsbeskrivning',
'vagty_41': 'Vägtyp/typ',
'Vandm_176': 'Vändmöjlighet/Vändmöjlighetsklass',
'Overledningsplats_2': 'Överledningsplats',
'Namns_133': 'Övrigt vägnamn/Namnsättande organisation',
'Namn_132': 'Övrigt vägnamn/Namn',
'Korfa_497': 'Antal körfält/Körfältsantal',
'F_ATK_Matplats_117': 'ATK-Mätplats(F)',
'B_ATK_Matplats_117': 'ATK-Mätplats(B)',
'F_Hogst_24': 'Begränsad bruttovikt/Högsta tillåtna bruttovikt(F)',
'B_Hogst_24': 'Begränsad bruttovikt/Högsta tillåtna bruttovikt(B)',
'F_Avser_28': 'Begränsad bruttovikt/Avser även fordonståg(F)',
'B_Avser_28': 'Begränsad bruttovikt/Avser även fordonståg(B)',
'Hogst_36': 'Begränsad fordonsbredd/Högsta tillåtna fordonsbredd',
'Hogst_46': 'Begränsad fordonslängd/Högsta tillåtna fordonslängd',
'OBJECTID': '',
'ROUTE_ID': '',
'FROM_MEASURE': 'MEASURE_FROM',
'TO_MEASURE': 'MEASURE_TO',
'KOMMUNNR': '',
'Shape': '',
'Shape_Length': ''
}
# Output message
def message (line):
sys.stdout.write (line)
sys.stdout.flush()
# Extension of dict class which returns an empty string if element does not exist
class Properties(dict):
def __missing__(self, key):
return None
# Convert tags
def osm_tags (segment):
prop = Properties(segment['properties'])
tags = {}
# 1. Tag nodes
crossing = {
# 1: {} # planskild passage överfart --> Should create bridge on both neighbour segments
# 2: {} # planskild passage underfart
3: {}, # övergångsställe och/eller cykelpassage/cykelöverfart i plan
4: {'crossing': 'traffic_signals'}, # signalreglerat övergångsställe och/eller signalreglerad cykelpassage/cykelöverfart i plan
5: {} # annan ordnad passage i plan
}
if prop['GCM-passage/Passagetyp'] in crossing: # Foot/cycleway crossing highway
tags['highway'] = "crossing"
tags.update(crossing[ prop['GCM-passage/Passagetyp'] ])
create_node(segment, tags, ["GCM-passage/Passagetyp", "GCM-passage/Trafikanttyp"])
railway_crossing = {
1: {'crossing:barrier': 'full'}, # Helbom
2: {'crossing:barrier': 'half'}, # Halvbom
3: {'crossing:bell': 'yes', 'crossing:light': 'yes'}, # Ljus och ljudsignal
4: {'crossing:light': 'yes'}, # Ljussignal
5: {'crossing:bell': 'yes'}, # Ljudsignal
6: {'crossing:saltire': 'yes'}, # Kryssmärke
7: {'crossing': 'uncontrolled'} # Utan skydd
}
if prop['Järnvägskorsning/Vägskydd'] in railway_crossing: # Railway crossing
if prop['Vägtrafiknät/Nättyp'] == 1:
tags['railway'] = "level_crossing"
else:
tags['railway'] = "crossing"
tags.update(railway_crossing[ prop['Järnvägskorsning/Vägskydd'] ])
create_node(segment, tags, ["Järnvägskorsning/Vägskydd", "Vägtrafiknät/Nättyp"])
traffic_calming = {
1: 'choker', # avsmalning till ett körfält
2: 'hump', # gupp (cirkulärt gupp eller gupp med ramp utan gcm-passage)
3: 'chicane', # sidoförskjutning - avsmalning
4: 'island', # sidoförskjutning - refug
5: 'dip', # väghåla
6: 'cushion', # vägkudde
7: 'table', # förhöjd genomgående gcm-passage
8: 'table', # förhöjd korsning
9: 'yes' # övrigt farthinder
}
if prop['Farthinder/Typ'] in traffic_calming: # Speed humps and other traffic calming objects
tags['traffic_calming'] = traffic_calming[ prop['Farthinder/Typ'] ]
create_node(segment, tags, ["Farthinder/Typ"])
barrier = {
1: 'bollard', # pollare
2: 'swing_gate', # eftergivlig grind
3: 'cycle_barrier', # ej öppningsbar grind eller cykelfålla
4: 'lift_gate', # låst grind eller bom
5: 'jersey_barrier', # betonghinder block?
6: 'bus_trap', # spårviddshinder
99: 'yes' # övrigt
}
if prop['Väghinder/Hindertyp'] in barrier: # Barriers
tags['barrier'] = barrier[ prop['Väghinder/Hindertyp'] ]
create_node(segment, tags, ["Väghinder/Hindertyp"])
if prop['ATK-Mätplats(F)'] or prop['ATK-Mätplats(B)']: # Speed camera (currently put on highway node)
tags['highway'] = "speed_camera"
if prop['ATK-Mätplats(F)'] and prop['Hastighetsgräns/Högsta tillåtna hastighet(F)']:
tags['maxspeed'] = str(prop['Hastighetsgräns/Högsta tillåtna hastighet(F)'])
elif prop['ATK-Mätplats(B)'] and prop['Hastighetsgräns/Högsta tillåtna hastighet(B)']:
tags['maxspeed'] = str(prop['Hastighetsgräns/Högsta tillåtna hastighet(B)'])
create_node(segment, tags, ["ATK-Mätplats(F)", "ATK-Mätplats(B)", \
"Hastighetsgräns/Högsta tillåtna hastighet(F)", "Hastighetsgräns/Högsta tillåtna hastighet(B)"])
if prop['Rastplats']: # Rest area (currently put on highway node)
tags['highway'] = "rest_area"
tags['name'] = prop['Rastplats/Rastplatsnamn'].strip()
if prop['Rastplats/Antal markerade parkeringsplatser för personbil']:
tags['capacity'] = str(prop['Rastplats/Antal markerade parkeringsplatser för personbil'])
if prop['Rastplats/Antal markerade parkeringsplatser för lastbil+släp']:
tags['capacity:hgv'] = str(prop['Rastplats/Antal markerade parkeringsplatser för lastbil+släp'])
create_node(segment, tags, ["RastPlats", "Rastplats/Rastplatsnamn", "Rastplats/Restaurang", \
"Rastplats/Antal markerade parkeringsplatser för personbil", \
"Rastplats/Antal markerade parkeringsplatser för lastbil+släp"])
if prop['Rastficka(V)'] or prop['Rastficka(H)']: # Parking along highway (currently put on highway node)
tags['amenity'] = "parking"
create_node(segment, tags, ["Rastficka(V)", "Rastficka(H)"])
tags = {} # Reset tags for segments
# 2. Tag ferries
if prop['Färjeled']: # Ferry
tags['route'] = "ferry"
tags['foot'] = "yes"
if prop['Vägtrafiknät/Nättyp'] == 1:
tags['motor_vehicle'] = "yes"
else:
tags['motor_vehicle'] = "no"
ferry = {
1: 'trunk', # E road
2: 'trunk', # National road
3: 'primary', # Primary county road
4: 'secondary' # Other county road
}
if prop['Vägkategori/Kategori'] in ferry: # Road catagory
tags['ferry'] = ferry[ prop['Vägkategori/Kategori'] ]
if prop['Vägnummer/Huvudnummer']: # Road number
if prop['Vägkategori/Kategori'] == 1: # E road
tags['ref'] = "E " + str(prop['Vägnummer/Huvudnummer'])
else:
tags['ref'] = str(prop['Vägnummer/Huvudnummer'])
if prop['Färjeled/Färjeledsnamn']: # Ferry line name
tags['name'] = prop['Färjeled/Färjeledsnamn'].strip()
return tags
# 3. Tag bridges and tunnels (common for vehicles and pedestrians)
# If only a foot/cycleway is underneath a short bridge, then the foot/cycleway is tagged as tunnel and there is no bridge tag.
# The bridges dict contains the results of initial analysis of bridges and tunnels.
if prop['Bro och tunnel/Konstruktion'] in [1, 4] and \
(not prop['Bro och tunnel/Identitet'] or bridges[ prop['Bro och tunnel/Identitet'] ]['tag'] == "bridge" or \
prop['Shape_Length'] > bridge_margin):
tags['bridge'] = "yes"
if prop['Bro och tunnel/Identitet']:
tags['layer'] = bridges[ prop['Bro och tunnel/Identitet'] ]['layer']
else:
tags['layer'] = "1"
elif prop['Bro och tunnel/Konstruktion'] == 3 or prop['Bro och tunnel/Konstruktion'] == 2 and \
(prop['Bro och tunnel/Identitet'] and bridges[ prop['Bro och tunnel/Identitet'] ]['tag'] == "tunnel" or \
not prop['Bro och tunnel/Identitet'] and (prop['Vägtrafiknät/Nättyp'] != 1 or prop['Shape_Length'] > bridge_margin)):
tags['tunnel'] = "yes"
tags['layer'] = "-1"
# Check oneway, used for other tags later
if prop['Förbjuden färdriktning(B)']:
tags['oneway'] = "yes"
oneway = "forward"
elif prop['Förbjuden färdriktning(F)']:
tags['oneway'] = "yes"
oneway = "backward"
reverse_segment(segment, False) # Reverse way nodes
if debug:
segment['properties']['REVERSE'] = 'yes' # debug
else:
oneway = ""
# 4. Tag cycleways/footways
if prop['Vägtrafiknät/Nättyp'] in [2, 4]: # 2: Cycleway, 4: footway
cycleway = {
1: {'highway': 'cycleway'}, # cykelbana
2: {'highway': 'cycleway'}, # cykelfält
3: {'highway': 'cycleway'}, # 'cycleway': 'crossing', 'segregated': 'yes'}, # cykelöverfart i plan/cykelpassage
4: {'highway': 'footway'}, # 'footway': 'crossing'}, # övergångsställe
5: {'highway': 'cycleway'}, # gatupassage utan utmärkning
8: {'highway': 'cycleway'}, # koppling till annat
9: {'highway': 'cycleway'}, # annan cykelbar förbindelse
10: {'highway': 'footway'}, # annan ej cykelbar förbindelse
11: {'highway': 'footway'}, # gångbana
12: {'highway': 'footway', 'footway': 'sidewalk'}, # trottoar
13: {'highway': 'cycleway'}, # fortsättning i nätet
14: {'highway': 'footway', 'covered': 'yes'}, # passage genom byggnad
15: {'highway': 'cycleway'}, # ramp
16: {'highway': 'platform'}, # perrong
17: {'highway': 'steps'}, # trappa
18: {'highway': 'footway', 'conveying': 'yes'}, # rulltrappa
19: {'highway': 'footway', 'conveying': 'yes'}, # rullande trottoar
20: {'highway': 'elevator'}, # hiss
21: {'highway': 'elevator'}, # snedbanehiss
22: {'aerialway': 'cable_car'}, # linbana
23: {'railway': 'furnicular'}, # bergbana
24: {'highway': 'pedestrian'}, # torg
25: {'highway': 'footway'}, # kaj
26: {'highway': 'pedestrian'}, # öppen yta
27: {'route': 'ferry', 'foot': 'yes', 'motor_vehicle': 'no'}, # färja
28: {'highway': 'cycleway'}, # 'cycleway': 'crossing', 'segregated': 'yes'}, # cykelpassage och övergångsställe
29: {'highway': 'cycleway', 'foot': 'no'} # cykelbana ej lämplig för gång
}
if prop['GCM-separation/Separation(V)'] and prop['GCM-separation/Separation(V)'] == 1 or \
prop['GCM-separation/Separation(H)'] and prop['GCM-separation/Separation(H)'] == 1: # Sidewalk
tags['highway'] = "footway"
tags['footway'] = "sidewalk"
elif prop['GCM-vägtyp/GCM-typ'] in cycleway:
tags.update( cycleway[ prop['GCM-vägtyp/GCM-typ'] ] )
else:
tags['highway'] = "cycleway"
# Swap cycleway to footway if footway network
if prop['Vägtrafiknät/Nättyp'] == 4 and 1and "highway" in tags and tags['highway'] == "cycleway":
tags['highway'] = "footway"
if "cycleway" in tags:
tags['footway'] = tags['cycleway']
del tags['cycleway']
# Include street name only for pedestrian highway or if only used by cycleway/footway
if prop['Gatunamn/Namn'] and ("highway" in tags and tags['highway'] == "pedestrian" or \
"stig" in prop['Gatunamn/Namn'].lower() or "gång" in prop['Gatunamn/Namn'].lower() or "park" in prop['Gatunamn/Namn'].lower() or \
prop['Gatunamn/Namn'].strip() not in street_names):
tags['name'] = prop['Gatunamn/Namn'].strip()
if prop['GCM-belyst'] and "highway" in tags: # Street light
tags['lit'] = "yes"
# Foot/cycleways only get name if marked as cycleway route
if prop['C-Cykelled/Namn'] and "highway" in tags and tags['highway'] == "cycleway":
tags['cycleway:name'] = prop['C-Cykelled/Namn'].strip() # Cycleway route
if "bridge" in tags:
if prop['Övrigt vägnamn/Namn'] and "bron" in prop['Övrigt vägnamn/Namn']: # Bridge name
tags['bridge:name'] = prop['Övrigt vägnamn/Namn'].strip()
if prop['Bro och tunnel/Namn']: # Description (may include bridge/tunnel name)
tags['description'] = prop['Bro och tunnel/Namn'].strip()
return tags
# 5. Tag highways for motor vehicles
# Follows official Swedish categories as used by Trafikverket and Lantmäteriet.
# Sweden OSM has very strange category definitions for national and county roads which requires manual editing.
if prop['Vägkategori/Kategori'] == 1: # E road
tags['highway'] = "trunk"
elif prop['Vägkategori/Kategori'] == 2: # National road
tags['highway'] = "trunk"
elif prop['Vägkategori/Kategori'] == 3:
tags['highway'] = "primary" # Primary county road
elif prop['Vägkategori/Kategori'] == 4:
tags['highway'] = "secondary" # Other county road (alternative - use Lever_292)
else:
if prop['Gågata(V)']:
tags['highway'] = "pedestrian" # Pedestrian street
elif prop['Gangfartsområde(V)'] or prop['Gangfartsområde(H)']: # Sign E9
tags['highway'] = "living_street"
elif prop['Funktionell vägklass/Klass'] and prop['Funktionell vägklass/Klass'] < 6: # Functional road class
tags['highway'] = "tertiary"
# Private roads are tagged as residential/unclassified if they meet certain criteria (see below).
# Otherwise tagged as service.
elif prop['Väghållare/Väghållartyp'] == 3: # Private road owner
# if prop['Funktionell vägklass/Klass'] and prop['Funktionell vägklass/Klass'] < 9 or prop['Driftbidrag statligt/Vägnr']:
if prop['Funktionell vägklass/Klass'] and prop['Funktionell vägklass/Klass'] < 8 or prop['Driftbidrag statligt/Vägnr']:
or prop['Funktionell vägklass/Klass'] == 8 and not prop['Tillgänglighet/Tillgänglighetsklass']: # not in [3,4]:
if prop['Tättbebyggt område']:
tags['highway'] = "residential" # Residential for urban areas
else:
tags['highway'] = "unclassified" # Unclassified for rural areas
# elif prop['Tillgänglighet/Tillgänglighetsklass'] == 4:
elif prop['Tillgänglighet/Tillgänglighetsklass'] and not prop['Gatunamn/Namn'] and prop['Slitlager/Slitlagertyp'] != 1:
# and (prop['Funktionell vägklass/Klass'] == 9 or prop['Tillgänglighet/Tillgänglighetsklass'] in [3,4]):
tags['highway'] = "track"
else:
tags['highway'] = "service" # Service tag for functional road class 9
else:
tags['highway'] = "residential" # Municipality is owner, seems to exist mostly in urban areas
# Motorway/motorroad
if prop['Motorväg']:
tags['highway'] = "motorway"
elif prop['Motortrafikled']:
tags['motoroad'] = "yes"
# Highway links are recognized indirectly by looking for the presence of FPV (functional priority road network) and
# delivery class ("leveranskvalitetsklass") below 4. Roundabouts excluded.
if tags['highway'] in ['motorway', 'trunk', 'primary'] and prop['Funktionellt prioriterat vägnät/FPV-klass'] is None and \
prop['Leveranskvalitet DoU 2017/Leveranskvalitetsklass DoU 2017'] and \
prop['Leveranskvalitet DoU 2017/Leveranskvalitetsklass DoU 2017'] < 4 and \
prop['Cirkulationsplats(F)'] is None and prop['Circulationsplats(B)'] is None:
tags['highway'] += "_link"
# Highway ref
county_refs = {
1: 'AB', # Stockholms län
3: 'C', # Uppsala län
4: 'D', # Södermanlands län
5: 'E', # Östergötlands län
6: 'F', # Jönköpings län
7: 'G', # Kronobergs län
8: 'H', # Kalmar län
9: 'I', # Gotlands län
10: 'K', # Blekinge län
11: 'L', # (f.d. Kristianstads län)
12: 'M', # Skåne län (f.d. Malmöhus län)
13: 'N', # Hallands län
14: 'O', # Västra Götalands län (f.d. Götebors- och Bohus län)
15: 'P', # (f.d. Älvsborgs län)
16: 'R', # (f.d. Skaraborgs län)
17: 'S', # Värmlands län
18: 'T', # Örebro län
19: 'U', # Västmanlands län
20: 'W', # Dalarnas län (f.d. Kopparbergs län)
21: 'X', # Gävleborgs län
22: 'Y', # Västernorrlands län
23: 'Z', # Jämtlands län
24: 'AC', # Västerbottens län
25: 'BD' # Norrbottens län
}
if prop['Vägkategori/Kategori'] == 1: # E road
tags['ref'] = "E " + str(prop['Vägnummer/Huvudnummer'])
elif prop['Vägkategori/Kategori'] in [2, 3]: # Trunk and primary
tags['ref'] = str(prop['Vägnummer/Huvudnummer'])
elif prop['Vägkategori/Kategori'] == 4: # Secondary
tags['ref'] = county_refs[ prop['KOMMUNNR'] // 100 ] + " " + str(prop['Vägnummer/Huvudnummer']) # Include county letter
# Backward/forward tags
tag_direction(tags, "junction", "roundabout", prop['Cirkulationsplats(F)'], prop['Circulationsplats(B)'], oneway) # Roundabout
if not (tags['highway'] == "track" and prop['Hastighetsgräns/Högsta tillåtna hastighet(F)'] == 70 and
prop['Hastighetsgräns/Högsta tillåtna hastighet(B)'] == 70):
tag_direction(tags, "maxspeed", None, prop['Hastighetsgräns/Högsta tillåtna hastighet(F)'], \
prop['Hastighetsgräns/Högsta tillåtna hastighet(B)'], oneway) # Maxspeed (exclude on service roads?, not signed?)
# tag_direction(tags, "motor_vehicle", "no", prop['Förbud mot trafik(F)'], prop['Förbud mot trafik(B)'], oneway) # Access
tag_direction(tags, "overtaking", "no", prop['Omkörningsförbud(F)'], prop['Omkörningsförbud(F)'], oneway) # Overtaking
# Lanes
if prop['Antal körfält/Körfältsantal'] and (prop['Antal körfält/Körfältsantal'] > 2 or \
oneway and prop['Antal körfält/Körfältsantal'] > 1):
tags['lanes'] = str(prop['Antal körfält/Körfältsantal']) # Lanes
tag_direction(tags, "psv", "yes", prop['Kollektivkörfält/Körfält-Körbana(F)']==2, \
prop['Kollektivkörfält/Körfält-Körbana(B)']==2, oneway) # PSV lanes
tag_direction(tags, "motor_vehicle", "no", prop['Kollektivkörfält/Körfält-Körbana(F)']==2, \
prop['Kollektivkörfält/Körfält-Körbana(B)']==2, oneway) # PSV lanes
tag_direction(tags, "lanes:psv", "1", prop['Kollektivkörfält/Körfält-Körbana(F)']==1, \
prop['Kollektivkörfält/Körfält-Körbana(B)']==1, oneway) # PSV lanes
# Other highway tags
if prop['Slitlager/Slitlagertyp'] == 1: # Surface
tags['surface'] = "paved"
elif prop['Slitlager/Slitlagertyp'] == 2:
tags['surface'] = "unpaved"
if prop['Vägnummer/Huvudnummer']: # Priority road
tags['priority_road'] = "designated"
if prop['C-Rekommenderad bilväg for cykel']: # Highway recommended for bikes
tags['bicycle'] = "designated"
# Names
if not prop['Cirkulationsplats(F)'] and not prop['Circulationsplats(B)']: # Street name
if prop['Gatunamn/Namn']:
tags['name'] = prop['Gatunamn/Namn'].strip()
elif prop['Övrigt vägnamn/Namn']:
tags['name'] = prop['Övrigt vägnamn/Namn'].strip()
if prop['Övrigt vägnamn/Namn']: # Bridge/tunnel name
if "tunnel" in tags and "tunneln" in prop['Övrigt vägnamn/Namn']:
tags['tunnel:name'] = prop['Övrigt vägnamn/Namn'].strip()
elif "bridge" in tags and "bron" in prop['Övrigt vägnamn/Namn']:
tags['bridge:name'] = prop['Övrigt vägnamn/Namn'].strip()
if prop['Bro och tunnel/Namn'] and ("bridge" in tags or "tunnel" in tags): # Description (may include bridge/tunnel name)
tags['description'] = prop['Bro och tunnel/Namn'].strip()
# Restrictions
# if prop['Framkomlighet för vissa fordonskombinationer/Framkomlighetsklass'] == 4: # Truck restrictions on forest roads
# tags['hgv'] = "no"
if prop['Höjdhinder upp till 4,5 m/Fri höjd']:
tags['maxheight'] = str(prop['Höjdhinder upp till 4,5 m/Fri höjd']) # Maxh height
if prop['Begränsad fordonslängd/Högsta tillåtna fordonslängd']:
tags['maxlength'] = str(prop['Begränsad fordonslängd/Högsta tillåtna fordonslängd']) # Max length
if prop['Begränsat axel-boggitryck/Högsta tillåtna tryck']:
tags['maxaxleload'] = str(prop['Begränsat axel-boggitryck/Högsta tillåtna tryck']) # Max legal load weight per axle
maxweight = {
1: "64.0", # BK1
2: "51.4", # Bk2
3: "37.5", # BK3
4: "74.0", # BK4
5: "74.0" # BK4 särskilda vilkor
}
if prop['Bärighet/Bärighetsklass'] and "bridge" in tags:
tags['maxweight'] = maxweight[ prop['Bärighet/Bärighetsklass'] ] # Max total weight (only tagged on bridges)
return tags
# Add tagged node to list of nodes
# Use coordinate from short way segment
def create_node (way, tags, nvdb_properties):
node = {
'type': 'feature',
'properties': {},
'tags': copy.deepcopy(tags),
'geometry': {
'type': 'Point',
'coordinates': copy.deepcopy(way['geometry']['coordinates'][0][0]) # First coordinate of line
}
}
for prop in nvdb_properties:
if prop in way['properties']:
node['properties'][ prop ] = copy.deepcopy(way['properties'][ prop ])
nodes.append(node)
# Tag way with different forward and backward direction
# Adjust for possible oneway direction
# Paramters:
# - tags: will be update
# - tag: tag key to be used
# - value: if set, this is the tag value to be used
# - prop_forward/backward: if 1, use tag value paramter
# - oneway: direction of onway road (forward, backward), if any
def tag_direction (tags, tag, value, prop_forward, prop_backward, oneway):
if prop_forward or prop_backward:
if value and prop_forward == 1:
prop_forward = value
if value and prop_backward == 1:
prop_backward = value
if prop_forward == prop_backward:
tags[ tag ] = str(prop_forward) # Same tag for both directions, so no forwrd/backward suffix needed
else:
if prop_forward and oneway != "backward":
if oneway == "forward":
tags[ tag ] = str(prop_forward) # Forward suffix not needed on oneway street
else:
tags[ tag + ":forward" ] = str(prop_forward)
if prop_backward and oneway != "forward":
if oneway == "backward":
tags[ tag ] = str(prop_backward) # Backward suffix not needed on oneway street
else:
tags[ tag + ":backward" ] = str(prop_backward)
# Tag segments and nodes in road network
def tag_network():
message ("Converting tags ... ")
global bridges, street_names
for segment in segments['features']:
segment['tags'] = {}
# First build bridge/tunnel dict for the named structures
bridges = {}
bridge_segments = []
for segment in segments['features']:
prop = segment['properties']
if "Bro och tunnel/Identitet" in prop: # Unique id of structure
bridge_id = prop['Bro och tunnel/Identitet']
if bridge_id not in bridges:
bridges[ bridge_id ] = {
'car': 0, # Will contain number of car highways underneath bridge
'cycle': 0, # Will contain number of foot/cycleways underneath bridge
'length': 0, # Length of bridge
'layer': "1" # Which layer to use
}
if prop['Bro och tunnel/Konstruktion'] in [2,3,4]: # Current segment is under bridge
if prop['Vägtrafiknät/Nättyp'] == 1 and prop['Bro och tunnel/Konstruktion'] != 3:
bridges[ bridge_id ]['car'] += 1 # Highway is for cars
else:
bridges[ bridge_id ]['cycle'] += 1 # Foot/cycleways
if prop['Bro och tunnel/Konstruktion'] == 1: # Current segment is over bridge
bridges[ bridge_id ]['length'] = max( bridges[ bridge_id ]['length'], prop['Shape_Length'] )
elif prop['Bro och tunnel/Konstruktion'] == 4: # Middel layer bridge
bridges[ bridge_id ]['layer'] = "2"
if "Bro och tunnel/Konstruktion" in prop: # Build list of bridge segments for next iteration
bridge_segments.append(segment)
# Discover missing bridge segments (without any bridge id) for the named structures and update
# Loop all identified bridge segments (over and under)
for segment1 in bridge_segments:
prop1 = segment1['properties']
if "Bro och tunnel/Identitet" not in prop1 and prop1['Bro och tunnel/Konstruktion'] in [2,3,4]: # Under bridge, segment without structure
# Then try to find intersecting highways over, i.e. a bridge
for segment2 in bridge_segments:
prop2 = segment2['properties']
if debug:
segment1['tags']['intersects'] = str(prop1['Bro och tunnel/Konstruktion'])
segment2['tags']['intersects'] = str(prop2['Bro och tunnel/Konstruktion'])
if prop2['Bro och tunnel/Konstruktion'] == 1 and segment1 != segment2 and intersects(segment1, segment2): # Intersecting bridge found
if "Bro och tunnel/Identitet" in prop2: # Over bridge, segment with structure
if prop1['Vägtrafiknät/Nättyp'] == 1 and prop1['Bro och tunnel/Konstruktion'] != 3:
bridges[ prop2['Bro och tunnel/Identitet'] ]['car'] += 1
else:
bridges[ prop2['Bro och tunnel/Identitet'] ]['cycle'] += 1
elif debug:
segment1['tags']['intersection'] = "yes"
# Tag according to highways over/under structure
for bridge_id, bridge in bridges.items():
if bridge['car'] > 0 or bridge['length'] > bridge_margin:
bridges[ bridge_id ]['tag'] = "bridge" # Always tag bridge if highway underneath is for cars
elif bridge['cycle'] > 0:
bridges[ bridge_id ]['tag'] = "tunnel" # Tag tunnel underneath instead of bridge on top if only foot/cycleway underneath
else:
bridges[ bridge_id ]['tag'] = "bridge" # Catch all
message ("\n\t%i bridge/tunnel structures\n" % len(bridges))
# Build set of street names. Used for cycleways later.
# Also get urban/rural statistics.
street_names = set()
urban_streets = 0
rural_streets = 0
for segment in segments['features']:
if "Vägtrafiknät/Nättyp" in segment['properties'] and segment['properties']['Vägtrafiknät/Nättyp'] == 1:
if "Gatunamn/Namn" in segment['properties'] and segment['properties']['Gatunamn/Namn'].strip():
street_names.add(segment['properties']['Gatunamn/Namn'].strip())
if "Tättbebyggt område" in segment['properties']:
urban_streets += 1
else:
rural_streets += 1
message ("\t%i street names\n" % len(street_names))
message ("\t%i %% urban vs rural streets\n" % (100 * urban_streets / (rural_streets + urban_streets)))
# Loop all features and tag
for segment in segments['features']:
segment['tags'].update(osm_tags(segment))
message ("\t%i tagged nodes\n" % len(nodes))
# Identify intersecting segments
# Assumes line segments are stored in the format [(x0,y0),(x1,y1)]
def intersects (s0, s1):
dx0 = s0['end_node'][0] - s0['start_node'][0]
dx1 = s1['end_node'][0] - s1['start_node'][0]
dy0 = s0['end_node'][1] - s0['start_node'][1]
dy1 = s1['end_node'][1] - s1['start_node'][1]
p0 = dy1 * (s1['end_node'][0] - s0['start_node'][0]) - dx1 * (s1['end_node'][1] - s0['start_node'][1])
p1 = dy1 * (s1['end_node'][0] - s0['end_node'][0]) - dx1 * (s1['end_node'][1] - s0['end_node'][1])
p2 = dy0 * (s0['end_node'][0] - s1['start_node'][0]) - dx0 * (s0['end_node'][1] - s1['start_node'][1])
p3 = dy0 * (s0['end_node'][0] - s1['end_node'][0]) - dx0 * (s0['end_node'][1] - s1['end_node'][1])
return (p0 * p1 <= 0) and (p2 * p3 <= 0)
# Return bearing in degrees of line between two points (longitude, latitude)
def compute_bearing (point1, point2):
lon1, lat1, lon2, lat2 = map(math.radians, [point1[0], point1[1], point2[0], point2[1]])
dLon = lon2 - lon1
y = math.sin(dLon) * math.cos(lat2)
x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dLon)
angle = (math.degrees(math.atan2(y, x)) + 360) % 360
return angle
# Compute change in bearing at intersection between two segments
# Used to determine if way should be split at intersection
def compute_junction_angle (segment1, segment2):
line1 = segment1['geometry']['coordinates'][0]
line2 = segment2['geometry']['coordinates'][0]
if segment1['end_node'] == segment2['start_node']:
angle1 = compute_bearing(line1[-2], line1[-1])
angle2 = compute_bearing(line2[0], line2[1])
elif segment1['start_node'] == segment2['end_node']:
angle1 = compute_bearing(line1[1], line1[0])
angle2 = compute_bearing(line2[-1], line2[-2])
elif segment1['start_node'] == segment2['start_node']:
angle1 = compute_bearing(line1[1], line1[0])
angle2 = compute_bearing(line2[0], line2[1])
else: # elif segment1['end_node'] == segment2['end_node']:
angle1 = compute_bearing(line1[-2], line1[-1])
angle2 = compute_bearing(line2[-1], line2[-2])
delta_angle = (angle2 - angle1 + 360) % 360
if delta_angle > 180:
delta_angle = delta_angle - 360
return delta_angle
# Compute closest distance from point p3 to line segment [s1, s2].
# Works for short distances.
def line_distance(s1, s2, p3):
x1, y1, x2, y2, x3, y3 = map(math.radians, [s1[0], s1[1], s2[0], s2[1], p3[0], p3[1]])
# Simplified reprojection of latitude
x1 = x1 * math.cos( y1 )
x2 = x2 * math.cos( y2 )
x3 = x3 * math.cos( y3 )
A = x3 - x1
B = y3 - y1
dx = x2 - x1
dy = y2 - y1
dot = (x3 - x1)*dx + (y3 - y1)*dy
len_sq = dx*dx + dy*dy
if len_sq != 0: # in case of zero length line
param = dot / len_sq
else:
param = -1
if param < 0:
x4 = x1
y4 = y1
elif param > 1:
x4 = x2
y4 = y2
else:
x4 = x1 + param * dx
y4 = y1 + param * dy
# Also compute distance from p to segment
x = x4 - x3
y = y4 - y3
distance = 6371000 * math.sqrt( x*x + y*y ) # In meters
'''
# Project back to longitude/latitude
x4 = x4 / math.cos(y4)
lon = math.degrees(x4)
lat = math.degrees(y4)
return (lon, lat, distance)
'''
return distance
# Simplify polygon, i.e. reduce nodes within epsilon distance.
# Ramer-Douglas-Peucker method: https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm
def simplify_polygon(polygon, epsilon):
dmax = 0.0
index = 0
for i in range(1, len(polygon) - 1):
d = line_distance(polygon[0], polygon[-1], polygon[i])
if d > dmax:
index = i
dmax = d
if dmax >= epsilon:
new_polygon = simplify_polygon(polygon[:index+1], epsilon)[:-1] + simplify_polygon(polygon[index:], epsilon)
else:
new_polygon = [polygon[0], polygon[-1]]
return new_polygon
# Travel recursively in highway network to identify connected service highways which are not connected to anythin but tracks.
# Then retag to highway=track.
# Current limitation: If a group of service roads are not connected to anything at all, it will be retagged to track.
# Paramters:
# - segment: To check.
# - tested_segments: Already tested segments, do not traverse.
# - tested_junctions: Already tested junctions do not traverse.
# - remaining_segments: Select test segments from this list (only service roads).
# Returns True if no connected service roads are connected to anything else than tracks.
# - Also updates tested_segments and tested_junctions
def connected_track(segment, tested_segments, tested_junctions, remaining_segments):
# Tested segments are accumulating as we traverse
if segment not in tested_segments:
tested_segments.append(segment)
track = True
# Test segments connected to both start and end nodes of segment.
for node in [ segment['start_node'] , segment['end_node'] ]:
if node not in tested_junctions:
tested_junctions.append(node)
# Iterate connected ways at junction. Check segments starting from next junction node.
for test_segment in junctions[ node ]['segments']:
if "highway" in test_segment['tags'] and test_segment['tags']['highway'] not in ["service", "track"]: