-
Notifications
You must be signed in to change notification settings - Fork 3
/
tag_translations.py
2016 lines (1849 loc) · 83.1 KB
/
tag_translations.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
# AGGREGATLevkval_DoU_2017 o not relevant (leveranskvalitetsklass)
# AGGREGATVagkategori o redundant (aggregate road types, government roads only)
# ATKATK_Matplats o not relevant (measurement place, not food place)
# BATMAN_* o redundant (bridges and tunnels, doesn't add relevat info over NVDB-Bro_och_tunnel)
# Miljö_Landskap* o not relevant
# Net o redundant
# TFR-Tjalrestriktion o not relevant (temporary information of road damage)
# TRAFIK-Trafik o not relevant (traffic statistics)
# EVB-Driftbidrag_statligt - for resolving highway (figuring out difference between track/unclassified on small roads)
# VIS-Brunn___Slamsugning o not translated
# VIS-Bullerskydd_vag o not translated (noise barrier, but incomplete and geometry on road rather than wall)
# VIS-Driftomrade o not relevant
# VIS-Driftvandplats o not relevant (operating turning point can be routed in road network)
# VIS-FPV_* o not relevant (prioritized roads for certain uses)
# VIS-Funktionellt_priovagnat - for resolving highway (a less detailed NVDB-FunkVagklass)
# VIS-Hallplatslage o not translated (suitably used separately when mapping bus lines)
# VIS-Hallplats o not translated (suitably used separately when mapping bus lines)
# VIS-Jarnvagskorsning - railway crossings (poorly aligned, railways loaded to snap alignment)
# VIS-Kalibreringsstracka o not relevant (measurement reference)
# VIS-Kantstolpe o not translated (rare, and mainly for winter maintenance)
# VIS-Katastrofoverfart o not translated (not existing OSM tags, not important for normal users)
# VIS-Mittremsa o not relevant (lane separation in meters)
# VIS-Omkorningsforbud - overtaking=no
# VIS-Overledningsplats o not translated (similar to VIS-Katastrofoverfart)
# VIS-Pendlings__och_servicevg o not relevant
# VIS-P_ficka - parking=layby
# VIS-Raffla o not translated (rare, no existing OSM tag, not important)
# VIS-Rastplats - amenity=parking etc, requires manual splitting to multiple nodes/areas (fixme tag added)
# VIS-Sidoanlaggningsvag o redundant
# VIS-Slitlager - detailed road surface for government managed roads (complements NVDB_Slitlager)
# VIS-Stamvag o redundant
# VIS-Stigningsfalt o not translated (rare, generally covered by increase of lanes)
# VIS-Storstadsvag o redundant
# VIS-Strateg_VN_tyngretranspo o not relevant
# VIS-TEN_T_Vagnat o redundant
# VIS-Vagnybyggnad_2009 o not relevant
# VIS-Vagtyp o redundant (government roads only)
# VIS-Viltpassage_i_plan o not translated
# VIS-Viltstangsel o not translated (wildlife fence, but not mapped at fence but rather on road)
# VIS-Viltuthopp o not translated
# VIS-Vinter2003 o not translated (winter_service=yes, but only covering the large roads,
# ie not all roads that actually have winter service)
# VIS-VVIS o not relevant (weather stations)
#
# At the time of writing these are all NVDB files, and how they are used in the translation:
#
# NVDB-Antal_korfalt2 - lanes=x
# NVDB-Barighet - maxweight=x
# NVDB-BegrAxelBoggiTryck - maxaxleload=x, maxbogieweight=x, maxaxleload:conditional=x
# NVDB-BegrBruttovikt - maxweightrating=x
# NVDB-BegrFordBredd - maxwidth=x
# NVDB-BegrFordLangd - maxlength=x
# NVDB-Bro_och_tunnel - bridges and tunnels
# NVDB-C_Cykelled o not translated (named cycling routes, similar to NVDB-Turismcykelled)
# NVDB-Cirkulationsplats - junction=roundabout
# NVDB-C_Rekbilvagcykeltrafik o not translated (recommended road for cyclists)
# NVDB-CykelVgsKat - used to resolve highway, often redundant (subset of GCM_vagtyp), but not everywhere
# NVDB-Farjeled - ferry routes
# NVDB-Farthinder - traffic_calming=x
# NVDB-ForbjudenFardriktning - oneway
# NVDB-ForbjudenSvang o ? empty data in Stockholm, Göteborg and other test areas
# NVDB-ForbudTrafik - traffic restrictions, partly translated (too specific, not enough OSM tags available)
# NVDB-FramkFordonskomb o not translated (rare tag, possibility for trucks etc to get through)
# NVDB-FunkVagklass - used to resolve highway
# NVDB-Gagata - used to resolve highway
# NVDB-Gangfartsomrade - used to resolve highway
# NVDB-Gatunamn - street names
# NVDB-Gatutyp - used to resolve highway
# NVDB-GCM_belyst - lit=yes
# NVDB-GCM_passage - traffic signals for footway/cycleway
# NVDB-GCM_separation - cycleway separation
# NVDB-GCM_vagtyp - cycleways and footways (and some more other rare types)
# NVDB-Hastighetsgrans - maxspeed, including time limits
# NVDB-Hojdhinder45dm - maxheight
# NVDB-Huvudled - priority_road=designated
# NVDB-InskrTranspFarligtGods - hazmat=no (hazardous goods)
# NVDB-Kollektivkorfalt - psv:lanes=* (not enough info to know placement of lanes or if bus or psv)
# NVDB-Korsning - highway=traffic_signals, names for roundabouts
# NVDB-Miljozon - Swedish environment zones, custom tag used for now
# NVDB-Motortrafikled - used to resolve highway
# NVDB-Motorvag - used to resolve highway
# NVDB-Ovrigt_vagnamn - road names
# NVDB-Reflinjetillkomst - used as reference geometry, no tags used
# NVDB-RekomVagFarligtGods - hazmat=designated
# NVDB-Slitlager - road surface paved/gravel
# NVDB-Stopplikt - highway=stop
# NVDB-Svangmojlighet o not translated (rare tag, possibility for trucks to turn in tight crossings)
# NVDB-TattbebyggtOmrade o redundant (subset of Gatutyp)
# NVDB-Tillganglighet o redundant (similar to FunkVagKlass)
# NVDB-Turismcykelled o not translated (named cycling routes)
# NVDB-Vagbredd - street width
# NVDB-Vaghallare o not translated (road maintainer)
# NVDB-Vaghinder - barrier=x
# NVDB-Vagnummer - ref=x and used to resolve highway
# NVDB-Vagtrafiknat o redundant
# NVDB-Vajningsplikt - highway=give_way
# NVDB-Vandmojlighet o was turning_circle, but is too often wrong so no longer used
import logging
import string
import math
import pandas
from nvdb_ti import parse_time_interval_tags, parse_range_date
_log = logging.getLogger("translations")
# append_fixme_value()
#
def append_fixme_value(tags, fixme_value):
if fixme_value is None:
return
if tags.get("fixme", None) is None:
tags["fixme"] = "NVDB import: " + fixme_value
else:
tags["fixme"] = tags["fixme"] + "; " + fixme_value
# tag_translation_expect_unset_time_intervals()
#
def tag_translation_expect_unset_time_intervals(tags):
time_intervals = parse_time_interval_tags(tags)
if time_intervals == -1:
append_fixme_value(tags, "time interval parse failure")
elif time_intervals is not None:
# if this happens, we probably need to implement some code to handle time intervals for that given type
_log.warning(f"unexpected time interval (not implemented) RLID {tags['RLID']}")
append_fixme_value(tags, "Warning: time interval handling not implemented)")
# add_extra_nodes_tag()
#
# Add key/value tags to the custom NVDB_extra_nodes tag, the purpose being to
# store tags that cannot be mapped to the single node due to conflicts.
#
def add_extra_nodes_tag(tags, extra_nodes):
if len(extra_nodes) == 0:
return
kvset = set()
for kv in extra_nodes:
node_str = "["
for k, v in kv.items():
node_str += "%s=%s, " % (k, v)
node_str = node_str[:-2] + "], "
kvset.add(node_str)
en_str = "".join(kvset)
en_str = en_str[:-2]
tags["NVDB_extra_nodes"] = en_str
append_fixme_value(tags, "NVDB_extra_nodes need to be mapped manually")
# parse_speed_limit()
#
# Parse NVDB speed limit, taking certain NVDB peculiarities into account.
#
def parse_speed_limit(tags, key):
speed = tags[key]
# text "gångfart" is very rare (forgot which dataset it was seen in)
# we use Gangfartsomrade rather than this to resolve living_street
# This could probably be used also on pedestrian streets (gågata) which
# is not the same as living street.
if speed == "gångfart":
speed = 5
elif speed in [ "varierande", "1000", 1000 ]:
# FIXME no support for tagging varying speed limit
speed = -1
elif isinstance(speed, str):
speed = int(speed)
elif speed is None:
speed = -1
if not (isinstance(speed, int) or isinstance(speed, float)):
_log.warning(f"unexpected speed value {key} {speed} (RLID {tags['RLID']})")
append_fixme_value(tags, "Bad %s speed value" % key)
speed = 5
del tags[key]
return speed
# parse_direction()
#
# Parse NVDB direction and convert to OSM :forward/:backward or nothing.
#
def parse_direction(tags):
if tags.get("RIKTNING", None) in ["Med och mot", None, "", -1, "-1"]:
dir_str = ""
elif tags["RIKTNING"] == "Med":
dir_str = ":forward"
elif tags["RIKTNING"] == "Mot":
dir_str = ":backward"
else:
dir_str = ""
_log.warning(f"invalid value of RIKTNING {tags['RIKTNING']} (RLID {tags['RLID']})")
append_fixme_value(tags, "Bad RIKTNING value")
tags.pop("RIKTNING", None)
return dir_str
# parse_vehicle_types()
#
# Parse NVDB vehicle types ("fordonstyp"), and alternatively if purpose/user lists are provided
# also road users ("trafikant"
#
def parse_vehicle_types(tags, key_base, purpose_list=None, user_list=None):
del_tags = []
vtypes = []
fixme_tags = { "fixme": tags.get("fixme", None) }
for key in tags:
if key.startswith(key_base) and tags[key] not in [None, "", -1, "-1"]:
v = tag_translation_fordon_trafikant(tags[key], tags["RLID"])
if v == "FIXME":
append_fixme_value(fixme_tags, f"Unknown tag {tags[key]}")
elif v == "IGNORE":
pass
elif v.startswith("PURPOSE"):
if purpose_list is not None:
purpose_list.append(v.split()[1])
else:
_log.warning(f"{key_base} contains conditional element (RLID {tags['RLID']})")
append_fixme_value(fixme_tags, f"{key_base} contains conditional element")
elif v.startswith("USER"):
if user_list is not None:
user_list.append(v.split()[1])
else:
_log.warning(f"{key_base} contains conditional element (RLID {tags['RLID']})")
append_fixme_value(fixme_tags, f"{key_base} contains conditional element")
else:
vtypes.append(v)
if key.startswith(key_base):
del_tags.append(key)
for k in del_tags:
del tags[k]
if fixme_tags.get("fixme", None) is not None:
tags["fixme"] = fixme_tags["fixme"]
return vtypes
# Translation table used by the tag_translation_fordon_trafikant() function
VEHICLES_AND_ROAD_USERS_TRANSLATIONS = {
"ambulans": "emergency",
"anläggningens fordon": "IGNORE",
"arbetsfordon": "IGNORE",
"beskickningsfordon": "USER diplomatic", # custom tag
"beställd taxi": "taxi",
"besökare": "PURPOSE destination",
"bil": "motorcar",
"bilägare med arrendekontrakt": "USER permit_holder",
"bokbuss": "bus",
"buss": "bus",
"cykel": "bicycle",
"efterfordon": "trailer",
"fordon": "vehicle",
# the actual vehicle is described in an extra free-text tag as it's free text it's not
# reasonable to make a translation tabel for it
"fordon enligt beskrivning": "IGNORE",
"fordon i linjetrafik": "bus",
"fordon i linjetrafik vid på- och avstigning": "bus",
"fordon med särskilt tillstånd": "USER permit_holder",
"fordon eller fordonståg vars längd, lasten inräknad, överstiger 10 meter": "lhv",
"fordon som används av rörelsehindrade med särskilt tillstånd": "USER disabled",
"fordon som används för distribution av post": "IGNORE",
"fyrhjulig moped": "atv",
"färdtjänstfordon": "psv",
"godstransporter": "hgv",
"hästfordon": "carriage",
"kommunens servicefordon": "IGNORE",
"lastbil": "hgv",
"lastbil vid på- och avlastning": "PURPOSE delivery",
"2-axlig lastbil": "hgv",
"lätt lastbil": "goods",
"lätt motorcykel": "motorcycle",
"lätt terrängvagn": "atv",
"moped": "moped",
"moped klass I": "moped",
"moped klass II": "mofa",
"motorcykel": "motorcycle",
"motordrivet fordon med tillkopplad släpvagn annat än påhängsvagn eller enaxlig släpvagn": "hgv",
"motordrivna fordon": "motor_vehicle",
"motorfordon": "motor_vehicle",
"motorredskap": "IGNORE",
"motorredskap klass I": "IGNORE",
"motorredskap klass II": "IGNORE",
"okänt": "IGNORE",
"personbil": "motorcar",
"renhållningsbil": "IGNORE",
"taxi": "taxi",
"terrängmotorfordon": "atv",
"terrängmotorfordon och terrängsläp": "atv",
"terrängskoter": "snowmobile",
"terrängsläp": "trailer",
"terrängvagn": "atv",
"trafik": "vehicle",
"traktor": "agricultural",
"transporter": "PURPOSE delivery",
"trehjulig moped": "moped",
"truck": "IGNORE",
"tung lastbil": "hgv",
"tung motorcykel": "motorcycle",
"tung terrängvagn": "atv",
"utryckningsfordon": "emergency",
"på- eller avlastning av gods": "PURPOSE delivery",
"på- eller avstigning": "PURPOSE embark_disembark", # custom conditional
"påhängsvagn": "trailer",
"skolskjuts": "psv",
"släpkärra": "trailer",
"släpvagn": "trailer"
}
# tag_translation_fordon_trafikant()
#
# Translate NVDB vehicles and road users into OSM tags. Note that in this case OSM tags are
# not as detailed as NVDB tags, so translation is not 100% correct. Most of those marked as
# "IGNORE" are used for exemptions, and we don't consider those as important to document.
#
def tag_translation_fordon_trafikant(tag, rlid):
if isinstance(tag, int):
# in some cases the tag is provided as an int instead of text
trans_number = {
10: "bil",
20: "buss",
30: "cykel",
40: "fordon",
50: "fordon i linjetrafik",
70: "fordon som används av rörelsehindrade med särskilt",
80: "hästfordon",
90: "lastbil",
100: "lätt lastbil",
110: "lätt terrängvagn",
120: "moped",
130: "moped klass I",
140: "moped klass II",
150: "motorcykel",
160: "motordrivet fordon med tillkopplad släpvagn annat",
170: "motordrivna fordon",
180: "motorredskap",
190: "motorredskap klass I",
200: "motorredskap klass II",
210: "personbil",
230: "terrängmotorfordon",
240: "terrängmotorfordon och terrängsläp",
250: "terrängskoter",
260: "terrängvagn",
270: "traktor",
280: "tung lastbil",
290: "tung terrängvagn",
300: "utryckningsfordon",
9999: "okänt"
}
tag = trans_number.get(tag, "okänt")
if not tag in VEHICLES_AND_ROAD_USERS_TRANSLATIONS:
_log.warning(f"unexpected NVDB fordon-trafikant tag '{tag}' (RLID {rlid})")
return "FIXME"
return VEHICLES_AND_ROAD_USERS_TRANSLATIONS[tag]
# tag_translation_single_value_with_time_interval()
#
# Generic function for translating NVDB tags that are a single value combined with a
# time interval
#
def tag_translation_single_value_with_time_interval(tags, key, value):
if value is None:
return
time_intervals = parse_time_interval_tags(tags)
if time_intervals == -1:
append_fixme_value(tags, "time interval parse failure")
elif time_intervals is not None:
tags[key + ":conditional"] = "%s @ (%s)" % (value, time_intervals)
else:
tags[key] = value
# tag_translation_Barighet()
#
# Maxweight
# - "Särskilda villkor" (specific conditions) not documented in NVDB so cannot be translated
#
def tag_translation_Barighet(tags):
winter_bk = tags["BAEIGHTSOD"] not in [None, "", -1, "-1"] and tags["BAEIGHTSOD"] != tags["BAEIGHTSSS"]
tag_translations = {
"BAEIGHTSSS=BK 1": "maxweight=64",
"BAEIGHTSSS=BK 2": "maxweight=51.4",
"BAEIGHTSSS=BK 3": "maxweight=37.5",
"BAEIGHTSSS=BK 4": "maxweight=74",
"BAEIGHTSSS=BK 4 - Särskilda villkor": "maxweight=74",
"BAEIGHTSOD=BK 1": "wc=64",
"BAEIGHTSOD=BK 2": "wc=51.4",
"BAEIGHTSOD=BK 3": "wc=37.5",
"BAEIGHTSOD=BK 4": "wc=74",
"BAEIGHTSOD=BK 4 - Särskilda villkor": "wc=74",
"STATDAUMOD": None,
"SLUDATMSOD": None,
}
process_tag_translations(tags, tag_translations)
if winter_bk:
if tags.get("STATDAUMO3", None) is None or tags.get("SLUDATMVOD", None) is None:
_log.warning(f"Winter-specific maxweight without date range for RLID {tags['RLID']}. All tags: {tags}")
else:
start_winter = parse_range_date(tags["STATDAUMO3"])
stop_winter = parse_range_date(tags["SLUDATMVOD"])
tags["maxweight:conditional"] = str(tags["wc"]) + " @ (" + start_winter + "-" + stop_winter + ")"
_ = [tags.pop(key, None) for key in ["wc", "BAEIGHTSOD", "SLUDATMVOD", "STATDAUMO3", "RIKTNING", "BETECKNING"]]
# tag_translation_BegrAxelBoggiTryck()
#
# Maxaxleload/maxbogieweight/triple axle
# - we skip information about organisation (Länsstyrelse) etc, and just keep the weight information
#
def tag_translation_BegrAxelBoggiTryck(tags):
time_interval = parse_time_interval_tags(tags)
if time_interval == -1:
append_fixme_value(tags, "time interval parse failure")
time_interval = None
for i in range(1, 4): # i = 1..3
weight = tags.get("TRYCK" + str(i), -1)
if weight is not None and weight > 0 and "TYPTRYCK" + str(i) in tags:
typ = tags["TYPTRYCK" + str(i)]
if time_interval is None:
if typ == "axeltryck":
tags["maxaxleload"] = weight
elif typ == "boggitryck":
tags["maxbogieweight"] = weight
elif typ == "trippelaxeltryck":
tags["maxaxleload:conditional"] = '%s @ "triple axle"' % weight
else:
if typ == "axeltryck":
tags["maxaxleload:conditional"] = "%s @ (%s)" % (weight, time_interval)
elif typ == "boggitryck":
tags["maxbogieweight:conditional"] = "%s @ (%s)" % (weight, time_interval)
elif typ == "trippelaxeltryck":
tags["maxaxleload:conditional"] = '%s @ (%s AND "triple axle")' % (weight, time_interval)
tags.pop("TRYCK" + str(i), None)
tags.pop("TYPTRYCK" + str(i), None)
# FIXME: we should probably not ignore the new field FORDTRAF (restriction does not apply to vehicle type XYZ)
_ = [tags.pop(key, None) for key in [ "AARTAL1", "AARTAL2", "LOEPNUMME1", "LOEPNUMME2", "MEDELADEON", "ORGNISTI1", "ORGNISTI2", "BETECKNING", "FORDTRAF" ]]
# tag_translation_BegrBruttovikt()
#
# Maxweightrating
# - FORD_TAG=ja means "also applies to vehicle trains". OSM doesn't do that distinction, so we ignore it
#
def tag_translation_BegrBruttovikt(tags):
key = "maxweightrating"
value = tags["BRUTTOVIKT"]
tags.pop("RIKTNING", None) # ignored
tags.pop("FORD_TAG", None) # ignored
tags.pop("BETECKNING", None) # ignored
tags.pop("FORDTRAF", None) # ignored, FIXME probably should not be ignored
tags.pop("BRUTTOVIKT", None)
tag_translation_single_value_with_time_interval(tags, key, value)
# tag_translation_BegrFordBredd()
#
# Maxwidth
#
def tag_translation_BegrFordBredd(tags):
key = "maxwidth"
value = tags["FORD_BREDD"]
tags.pop("FORD_BREDD", None)
for i in range(1, 4):
tags.pop("FORDTRAF" + str(i), None) # doesn't seem to be used
_ = [tags.pop(key, None) for key in [ "BETECKNING" ]]
tag_translation_single_value_with_time_interval(tags, key, value)
# tag_translation_BegrFordLangd()
#
# Maxlength
#
def tag_translation_BegrFordLangd(tags):
maxlength = tags["FORD_LGD"]
tags.pop("FORD_LGD", None)
applies_to_passage_only = tags.get("GENOMFART", None) == "sant"
tags.pop("GENOMFART", None)
for i in range(1, 4):
tags.pop("FORDTRAF" + str(i), None) # doesn't seem to be used
if maxlength is None:
return
time_intervals = parse_time_interval_tags(tags)
if time_intervals == -1:
append_fixme_value(tags, "time interval parse failure")
return
if time_intervals is not None and applies_to_passage_only:
tags["maxlength:conditional"] = f"{maxlength} @ ({time_intervals}); none @ (destination)"
elif time_intervals is not None:
tags["maxlength:conditional"] = f"{maxlength} @ ({time_intervals})"
elif applies_to_passage_only:
tags["access:conditional"] = f"destination @ (maxlength>{maxlength})"
else:
tags["maxlength"] = maxlength
_ = [tags.pop(key, None) for key in [ "BETECKNING" ]]
# tag_translation_ForbudTrafik()
#
# Traffic restrictions.
# - Quite tricky to get right.
# - Cannot be translated 100% correct due to too few OSM tags, and some NVDB free text fields
#
def tag_translation_ForbudTrafik(tags):
# BESKRGFART Beskrivning (fritext, sällsynt, verkar vara samma som BSEKR_GEJ1)
# BSEKR_GEJ1 Gäller ej.Beskrivning (fritext, exempel: "Fordon som används vid bärgningsarbete")
# FORDTYP[1..3] Gäller fordon
# TOTALVIKT Anger totalvikt på fordon över vilken förbudet gäller om det bara gäller fordon med viss totalvikt.
# GENOMFART Anger om trafikregeln enbart gäller genomfart eller inte (sant/falskt).
# / Tidsintervall
# GE/ Gäller ej.Tidsintervall (= tidsintervall när "gäller-ej"-fordonen får köra)
# FORDTRA1[1..10] Gäller ej.Fordon/trafikant
# VERKSAMH1[1..7] Gäller ej.Verksamhet
# RIKTNING Riktning
rlid = tags["RLID"]
ti = parse_time_interval_tags(tags)
if ti == -1:
append_fixme_value(tags, "time interval parse failure")
return
for k, v in list(tags.items()):
if v in [None, "", -1, "-1"]:
del tags[k]
_log.debug(f"ForbudTrafik input: {tags} {ti}")
forbidden_ti = ti["/"]
exemption_ti = ti["GE/"]
# Seem to be a quite common error/style to specify 00:00-00:00 as in this layer (Stockholm data),
# we assume it means 00:00-00:24 ie same as no time interval
if forbidden_ti == "00:00-00:00":
forbidden_ti = None
if exemption_ti == "00:00-00:00":
exemption_ti = None
only_applies_to_passing_through = tags.get("GENOMFART", None) == "sant"
total_weight = tags.get("TOTALVIKT", -1)
if total_weight is None:
total_weight = -1
direction = parse_direction(tags)
vtypes = parse_vehicle_types(tags, "FORDTYP")
if len(vtypes) == 0:
vtypes.append("vehicle")
purpose_list = []
user_list = []
exemption_vtypes = parse_vehicle_types(tags, "FORDTRA1", purpose_list, user_list)
# make sure we don't have overlap between vehicle types
for v in vtypes:
if v in exemption_vtypes:
# This can happen when NVDB tag is more specific than the translated OSM tag
# We then remove the exemption and keep the generic OSM
_log.warning(f"vehicle overlap with exemption for restrictions ({v}) (RLID {rlid})")
exemption_vtypes.remove(v)
# remove duplicates
purpose_list = list(dict.fromkeys(purpose_list))
user_list = list(dict.fromkeys(user_list))
if only_applies_to_passing_through and "destination" in purpose_list:
purpose_list.remove("destination")
if len(purpose_list) > 0 and exemption_ti is None:
# The only valid alternative for multiple purposes seems to make multiple conditionals,
# so we create a dummy time to be able to make conditionals
exemption_ti = "00:00-24:00"
base_str= ""
cond_str = ""
if forbidden_ti is None:
if total_weight > 0 and only_applies_to_passing_through:
cond_str = "destination @ (weight>%s)" % total_weight
elif only_applies_to_passing_through:
base_str = "destination"
elif total_weight > 0:
cond_str = "no @ (weight>%s)" % total_weight
else:
base_str = "no"
else:
if total_weight > 0 and only_applies_to_passing_through:
cond_str = "no @ (weight>%s AND %s); destination @ (%s)" % (total_weight, forbidden_ti, forbidden_ti)
elif only_applies_to_passing_through:
base_str = "no"
cond_str = "destination @ (%s)" % forbidden_ti
elif total_weight > 0:
cond_str = "no @ (weight>%s AND %s)" % (total_weight, forbidden_ti)
else:
cond_str = "no @ (%s)" % forbidden_ti
for purpose in purpose_list:
if not purpose in cond_str:
cond_str += "; %s @ (%s)" % (purpose, exemption_ti)
for user in user_list:
if not user in cond_str:
if exemption_ti is None:
cond_str += "; yes @ (%s)" % (user)
else:
# mixing 'no @' and 'yes @' ond the same line is not common, but seems to be okay with
# the (loose) specification https://wiki.openstreetmap.org/wiki/Conditional_restrictions
cond_str += "; yes @ (%s AND %s)" % (exemption_ti, user)
if cond_str != "" and cond_str[0] == ';':
cond_str = cond_str[2:]
for v in vtypes:
if base_str != "":
tags[v + direction] = base_str
if cond_str != "":
tags[v + direction + ":conditional"] = cond_str
for v in exemption_vtypes:
if exemption_ti is None or exemption_ti == "00:00-24:00" or exemption_ti == forbidden_ti:
tags[v] = "yes"
else:
tags[v + direction + ":conditional"] = "yes @ (%s)" % exemption_ti
idx = 1
while "VERKSAMH1" + str(idx) in list(tags):
del tags["VERKSAMH1" + str(idx)]
idx += 1
_ = [tags.pop(key, None) for key in ["BESKRGFART", "BSEKR_GEJ1", "GENOMFART", "TOTALVIKT", "BETECKNING"]]
_log.debug(f"ForbudTrafik output {tags}")
# tag_translation_GCM_separation()
#
# At the time of writing cycleway separation is just a proposal, but quite much use in Germany
# https://wiki.openstreetmap.org/wiki/Proposed_features/cycleway:separation
#
def tag_translation_GCM_separation(tags):
key = None
key2 = None
if tags["SIDA"] == "Höger":
key = "separation:right"
elif tags["SIDA"] == "Vänster":
key = "separation:left"
elif tags["SIDA"] == "Vänster och höger": # rare, but exists
key = "separation:left"
key2 = "separation:right"
elif tags["SIDA"] in ["Mitt", None, "", -1, "-1"]:
_log.info(f"ignoring separation with SIDA {tags['SIDA']} (RLID {tags['RLID']})")
else:
_log.warning(f"unknown SIDA {tags['SIDA']} (RLID {tags['RLID']})")
append_fixme_value(tags, "GCM_separation: unknown SIDA value")
trans_separation = {
# NVDB has been using both numeric and string enumeration
1: "separation_kerb", # kantsten
2: None, # skiljeremsa
3: "railing", # räcke
4: None, # friliggande
5: "solid_line", # vägmarkering
99: None, # okänt
"kantsten": "separation_kerb",
"skiljeremsa": None,
"räcke": "railing",
"friliggande": None,
"vägmarkering": "solid_line",
"okänt": None
}
separation = tags.pop("SEPARATION", None)
tags.pop("SIDA", None)
if separation not in trans_separation:
_log.warning(f"unknown SEPARATION {separation} (RLID {tags['RLID']})")
append_fixme_value(tags, f"GCM_separation: unknown SEPARATION value {separation}")
elif trans_separation.get(separation, None) is not None and key is not None:
tags[key] = trans_separation[separation]
if key2 is not None:
tags[key2] = trans_separation[separation]
# tag_translation_Hastighetsgrans()
#
# Maxspeed, sometimes conditional
#
def tag_translation_Hastighetsgrans(tags):
time_interval = parse_time_interval_tags(tags)
if time_interval == -1:
append_fixme_value(tags, "time interval parse failure")
return
vtypes = parse_vehicle_types(tags, "FORDTYP1")
direction = parse_direction(tags)
maxspeed = parse_speed_limit(tags, "HTHAST")
alt_maxspeed = parse_speed_limit(tags, "HAVHAST1")
max_weight = tags.get("TOTALVIKT1", -1)
if max_weight is None:
max_weight = -1
only_applies_to_these_vehicles = tags["HAVGIE1"] == 2
doesnt_apply_to_these_vehicles = tags["HAVGIE1"] == 1
if len(vtypes) > 0 and tags["HAVGIE1"] == -1:
append_fixme_value(tags, "Hastighetsgrans: got vehicles but applies-to not set")
_log.warning(f"Hastighetsgrans: got vehicles without applies-to set RLID {tags['RLID']}")
only_applies_to_these_vehicles = True # guess
if (only_applies_to_these_vehicles or doesnt_apply_to_these_vehicles) and len(vtypes) == 0:
append_fixme_value(tags, "Hastighetsgrans: got applies-to but no vehicles")
_log.warning(f"Hastighetsgrans: got applies-to without vehicles RLID {tags['RLID']}")
only_applies_to_these_vehicles = False
doesnt_apply_to_these_vehicles = False
cond_str = ""
if time_interval:
if max_weight > 0:
cond_str = "(weight>%s AND %s)" % (max_weight, time_interval)
else:
cond_str = "(%s)" % time_interval
else:
if max_weight > 0:
cond_str = "(weight>%s)" % max_weight
if (cond_str != "" or len(vtypes) > 0) and (alt_maxspeed < 0 or alt_maxspeed == maxspeed):
append_fixme_value(tags, "Hastighetsgrans: conditions without alternate speed")
_log.warning(f"Hastighetsgrans: conditions without alternate speed {tags} (RLID {tags['RLID']} {vtypes} {cond_str})")
cond_str = ""
if only_applies_to_these_vehicles:
tags["maxspeed" + direction] = maxspeed
for v in vtypes:
if cond_str != "":
tags["maxspeed:" + v + direction + ":conditional"] = "%s @ %s" % (alt_maxspeed, cond_str)
else:
tags["maxspeed:" + v + direction] = alt_maxspeed
elif doesnt_apply_to_these_vehicles:
tags["maxspeed" + direction] = alt_maxspeed
for v in vtypes:
if cond_str != "":
tags["maxspeed:" + v + direction + ":conditional"] = "%s @ %s" % (maxspeed, cond_str)
else:
tags["maxspeed:" + v + direction] = maxspeed
else:
tags["maxspeed" + direction] = maxspeed
if cond_str != "":
tags["maxspeed" + direction + ":conditional"] = "%s @ %s" % (alt_maxspeed, cond_str)
tags.pop("HAVGIE1", None)
tags.pop("TOTALVIKT1", None)
tags.pop("BETECKNING", None)
# tag_translation_InskrTranspFarligtGods()
#
# Restrictions for hazardous materials
#
def tag_translation_InskrTranspFarligtGods(tags):
may_not = tags["FARINTE"]
applies_to_passage_only = tags.get("GENOMFART", None) == "sant"
# we translate all values to hazmat=no
if may_not not in ("föras", "stannas", "parkeras", "stannas eller parkeras"):
_log.warning(f"unknown FARINTE value {tags} (RLID {tags['RLID']})")
append_fixme_value(tags, "InskrTranspFarligtGods: unknown FARINTE value")
time_intervals = parse_time_interval_tags(tags)
if time_intervals == -1:
append_fixme_value(tags, "time interval parse failure")
return
if time_intervals is not None and applies_to_passage_only:
tags["hazmat:conditional"] = f"no @ ({time_intervals}); yes @ (destination)"
if time_intervals is not None:
tags["hazmat:conditional"] = f"no @ ({time_intervals})"
elif applies_to_passage_only:
tags["hazmat"] = "no"
tags["hazmat:conditional"] = "yes @ (destination)"
else:
tags["hazmat"] = "no"
tags.pop("FARINTE", None)
tags.pop("GENOMFART", None)
# FIXME: we don't care about these fields yet (are they ever used?)
for i in range(1, 6):
tags.pop("FORDTYP" + str(i), None)
for i in range(1, 4):
tags.pop("FORDTRAF1" + str(i), None)
for i in range(1, 4):
tags.pop("VERKSAMH1" + str(i), None)
_ = [tags.pop(key, None) for key in ["PLATS1", "RIKTNING", "BETECKNING"]]
# tag_translation_Kollektivkorfalt()
#
# Bus lanes
# - NVDB doesn't specify which lane is the bus one
# - NVDB doesn't specify if taxi may drive there
# - NVDB has odd way to specify time interval, sometimes only TIM12 set
# - We do some lane tricks
#
def tag_translation_Kollektivkorfalt(tags):
ti_tags = {
# FIXME maybe we should look at RIKTNING as well
"DAGSL1": tags.get("DAGSLAG", None),
"STDAT1": tags.get("STARTDATUM", "1899-12-29"),
"SLDAT1": tags.get("SLUTDATUM", "1899-12-29"),
"STDAG1": tags.get("STARTDAG", None),
"SLDAG1": tags.get("SLUTDAG", None),
"STTIM11": tags.get("TIMME", -1),
"SLTIM11": tags.get("TIM12", -1),
"STMIN11": tags.get("MINUT", -1),
"SLMIN11": tags.get("MIN13", -1),
"RLID": tags["RLID"]
}
# DAGSLAG seems to have been replaced by DAGSSL
if (ti_tags["DAGSL1"] is None):
ti_tags["DAGSL1"] = tags.get("DAGSSL", None)
# case with only TIM12 set seems to be quite common, so we fix that to make a parsable time
all_undefined = True
for k in [ "STTIM11", "SLTIM11", "STMIN11", "SLMIN11"]:
if ti_tags[k] != -1 and ti_tags[k] is not None:
all_undefined = False
if not all_undefined:
for k in [ "STTIM11", "SLTIM11", "STMIN11", "SLMIN11"]:
if ti_tags[k] == -1 or ti_tags[k] is None:
ti_tags[k] = 0
time_interval = parse_time_interval_tags(ti_tags)
if time_interval == -1:
append_fixme_value(tags, "time interval parse failure")
time_interval = None
if time_interval in ("00:00-00:00", "00:00-24:00"):
time_interval = None
all_lanes = tags["KOEFAETKNA"] == "Körbana"
direction = parse_direction(tags)
# 'NVDB_guess_lanes' is a guess for lanes, later converted to 'lanes' if not better source is available
lane_count = 1
if direction == "" and all_lanes:
# assume two lanes
lane_count = 2
tags["NVDB_guess_lanes"] = 2
elif all_lanes:
tags["NVDB_guess_lanes"] = 1
else:
# hopefully Antal_korfalt2 has this covered
pass
if time_interval is not None:
tags["lanes:bus" + direction + ":conditional"] = "%s @ (%s)" % (lane_count, time_interval)
else:
tags["lanes:bus" + direction] = lane_count
_ = [tags.pop(key, None) for key in [ "MIN13", "MINUT", "TIM12", "TIMME", "DAGSLAG", "DAGSL", "STARTDATUM", "SLUTDATUM", "STARTDAG", "SLUTDAG", "KOEFAETKNA", "RIKTNING", "BETECKNING" ]]
# tag_translation_P_ficka()
#
# Layby parkings
#
def tag_translation_P_ficka(tags):
tags["amenity"] = "parking"
tags["parking"] = "layby"
if tags["SIDA"] == "Höger":
tags["NVDB_layby_side"] = "right"
elif tags["SIDA"] == "Vänster":
tags["NVDB_layby_side"] = "left"
else:
# Note: "Mitt" has been observed in Rättvik
_log.warning(f"unknown SIDA {tags} (RLID {tags['RLID']})")
append_fixme_value(tags, "P_ficka: unknown SIDA value")
# "Uppställbar längd", that is the number of meters that can be used for parking,
# specified for about 1/3 of the laybys. There is no established OSM key for this
# particular purpose, we use 'maxlength'here.
length = tags.get("UPPTAELBGD", None)
if length is not None and length > 0:
tags["maxlength"] = length
del_keys = [
"PLACERING", # "Längs vägen" or "Avskild från vägen, still close enough for layby
"UPPTAELBGD",
"SIDA",
"XKOORDINAT",
"YKOORDINAT"
]
for k, v in tags.items():
if v in [None, "", -1, "-1", "Finns ej", "Okänt"]:
del_keys.append(k)
for k in del_keys:
tags.pop(k, None)
del_keys = []
add_tags = {}
extra_nodes = []
for k, v in tags.items():
unknown_key = False
if k == "NAMN" and v != -1: # Namn
add_tags["name"] = v
elif k == "TOALETT": # Toalett
extra_nodes.append({"amenity": "toilets"})
elif k == "BORMEDITER": # Bord med sittplatser
extra_nodes.append({"leisure": "picnic_table"})
elif k == "SOPKAERL": # Sopkärl
extra_nodes.append({"amenity": "waste_disposal"})
elif k == "MOLOK": # Molok
extra_nodes.append({"amenity": "waste_disposal"}) # not 100% sure what a Molok is...
else:
unknown_key = True
if not unknown_key:
del_keys.append(k)
for k in del_keys:
del tags[k]
tags.update(add_tags)
add_extra_nodes_tag(tags, extra_nodes)
# tag_translation_Rastplats()
#
# Highway rest areas
# - NVDB point data, packed with tags
# - Cannot be mapped as a single node in OSM due to the many tags
# - As these are few (1 - 5 per kommun), we don't solve this automatically but add the
# extra tags in a custom field and a fixme tag so these can be completed manually,
# preferably by making the rest area parking as an area rather than a node and mapping
# the individual parts as separate nodes
#
def tag_translation_Rastplats(tags):
del_keys = [
"HUVUDMAN", # Huvudman
"RASPLASTYP", # Rastplatstyp (rastplats, enkel rastplats)
"AVKERNNGAT", # Avkörningspunkt X-Koordinat
"AVKERNNG13", # Avkörningspunkt Y-Koordinat
"ALTRNAIVAT", # Alternativ avkörningspunkt X-Koordinat
"ALTRNAIV15", # Alternativ avkörningspunkt Y-Koordinat
"OEVIGURUNG", # Övrig utrustning
"SAEERHTSDD", # Säkerhetsskydd (rare tag, unclear what it means...)
"OEVIGPRKET", # Övrig parkeringsmöjlighet
]
# remove all keys with value No or Unknown or -1
for k, v in list(tags.items()):
if v in [None, "", "nej", "Nej", "okänt", "Okänt", "-1", -1]:
del_keys.append(k)
for k in del_keys:
tags.pop(k, None)
# Note: highway=rest_area would be more logical as main feature, but
# amenity=parking tag is much more mature, so we use that as the main
# tag here
parking_capacity = 0
tags["amenity"] = "parking"
tags["access"] = "yes"
del_keys = []
add_tags = {}
extra_nodes = []
for k, v in tags.items():
unknown_key = False
if k == "RASPLASNMN": # Rastplatsnamn
add_tags["name"] = v
elif k == "RASPLASASS": # Rastplatsadress
add_tags["addr:place"] = v
elif k == "BELYSNING": # Belysning
add_tags["lit"] = "yes"
elif k == "SKOTSEANIG": # Skötselansvarig
add_tags["operator"] = v
elif k == "LAEKADESSS": # Länkadress
add_tags["website"] = v
elif k == "UTPKADAETS": # Utpekad lämplig lastbilsparkeringsplats
add_tags["hgv"] = "yes" # unofficial but in-use
elif k == "ANTLMAKEIL": # Antal markerade parkeringsplatser för personbil
parking_capacity += v
elif k == "ANTLMAKEEP": # Antal markerade parkeringsplatser för personbil + släp
parking_capacity += v
elif k == "ANTLMAKE20": # Antal markerade parkeringsplatser för lastbil
add_tags["hgv"] = "yes"
parking_capacity += v
elif k == "ANTLMAKE21": # Antal markerade parkeringsplatser för lastbil + släp
add_tags["hgv"] = "yes"
parking_capacity += v
elif k == "ANTLMAKE22": # Antal markerade parkeringsplatser för husbil
parking_capacity += v
elif k == "ANTLMAKESS": # Antal markerade parkeringsplatser för buss
add_tags["bus"] = "yes" # unofficial but in-use
parking_capacity += v
elif k == "LATINTEMNG": # Latrintömning
extra_nodes.append({"amenity": "sanitary_dump_station"})
elif k == "LATINTEMNA": # Latrintömning löstagbar tunna
extra_nodes.append({"amenity": "sanitary_dump_station"})
elif k == "LATINTEM28": # Latrintömning fastmonterad tunna
extra_nodes.append({"amenity": "sanitary_dump_station"})
elif k == "HCPNPASATT": # HCP-anpassad toalett
extra_nodes.append({ "amenity": "toilets", "wheelchair": "yes"})
elif k == "SOPKAERL": # Sopkärl
extra_nodes.append({"amenity": "waste_disposal"})
elif k == "DUSHMOJLET": # Duschmöjlighet
extra_nodes.append({"amenity": "shower"})
elif k == "HUNRASGARD": # Hundrastgård
extra_nodes.append({"amenity": "dog_toilet"})