#!/usr/bin/python # -*- coding: koi8-r -*- import locale def GetMapName(ini): import ConfigParser cp = ConfigParser.RawConfigParser() try: cp.readfp(ini) except: pass sec = cp.sections() lsec = map(lambda a: a.lower(), sec) if 'options' in lsec: s = sec[lsec.index('options')] if cp.has_option(s, 'name'): name = cp.get(s, 'name') return unicode(name, detect_encoding(name)) return None def detect_encoding(s): for enc in ('cp1251', 'koi8-r', 'utf-8', 'iso8859-5'): try: locale.setlocale(locale.LC_ALL, ('ru_RU', enc)) l, u = count_lu(s) if u * 2 < l: return enc except: pass (nlcode, nlenc) = locale.getdefaultlocale() if not nlenc: nlenc = 'iso8859-1' if nlenc.lower() == 'utf': nlenc = 'utf-8' return nlenc def count_lu(s): u = len(filter(lambda a: a.isupper(), s)) l = len(filter(lambda a: a.islower(), s)) return l, u def cp_get(cp, section, option, fallback): if not cp.has_option(section, option): return fallback return cp.get(section, option) def cp_get_int(cp, section, option, fallback): if not fallback: fallback = 0 if not cp.has_option(section, option): return fallback v = cp.get(section, option) if not v.isdigit(): return fallback return int(v) def cp_get_color(cp, section, option, fallback): if not fallback: fallback = "000000" if not cp.has_option(section, option): return fallback v = cp.get(section, option) if not len(v) == 6: return fallback try: val = int(v, 16) except: return fallback return v def parse_with_quotes(s): res = list() ns = '' waitclose = None for c in s: if waitclose: if c == waitclose: if waitclose == ')': ns += c waitclose = None else: ns += c else: if c == '"': waitclose = '"' elif c == '(': ns += c waitclose = ')' elif c == ',': #delimiter ns = ns.lstrip().rstrip() res.append(ns) ns = '' else: ns += c ns = ns.lstrip().rstrip() res.append(ns) return res def split_by_comma(s, num): #FIXME: int() can raise expception l = map(lambda a: int(a.lstrip().rstrip()), s.split(',')) res = list() for i in xrange(len(l) / num): res.append(l[i * num : i * num + num]) return res def split_by_comma2(s, num): #FIXME: int() can raise expception l = map(lambda a: int(a.lstrip().rstrip()), s.split(',')) res = list() for i in xrange(len(l) / num): res.append(tuple(l[i * num : i * num + num])) e = 0 if len(l) % num: e = l[-1] return e, res def read_full_section(cp, section): res = list() try: for i in cp.options(section): res.append(map(lambda a: a.rstrip().lstrip(), parse_with_quotes(cp.get(section, i)))) except Exception, e: print e pass return res class ReadMap: def __init__(self, datafile): self.ini = datafile self.Map = dict() self.Lines = dict() self.Stations = dict() self.Graph = list() self.delaytime = 'day' self._load() self._makeGraph() if 0: #change to 1 to produce datafile parsable by doc/findpath.c print len(self.Graph) for k in self.Graph: print len(k) for ge in k: for e in ge: print e print len(self.Lines) for line in self.Lines: print line print self.Lines[line]['delays']['day'] print self.Lines[line]['delays']['night'] def SetDelayTime(self, dtime): if dtime == 1 or dtime == '1' or \ (type(dtime) == str and dtime.lower() == 'night'): self.delaytime = 'night' else: self.delaytime = 'day' def WaitLen(self, station): return self.Lines[self.Stations[station]['line']]['delays'][self.delaytime] def set_encoding(self, encoding): self.MapEncoding = encoding for l in self.Lines.keys(): self.Lines[l]['name'] = unicode(self.Lines[l]['name_orig'], encoding) for s in self.Stations.keys(): self.Stations[s]['name'] = unicode(self.Stations[s]['name_orig'], encoding) self.Map['city'] = unicode(self.Map['city_orig'], encoding) self.Map['name'] = unicode(self.Map['name_orig'], encoding) for i in xrange(len(self.Map['vectors']['instructions'])): if self.Map['vectors']['instructions'][i][0] == 'text': self.Map['vectors']['instructions'][i][1]['text'] = unicode(self.Map['vectors']['instructions'][i][1]['text_orig'], encoding) def _get_line_key(self, name): n = name.lower() for k in self.Lines.keys(): sdn = self.Lines[k]['name'].lower() sdno = self.Lines[k]['name_orig'].lower() if (type(sdn) == type(n) and sdn == n) or \ (type(sdno) == type(n) and sdno == n): return k return None def _get_st_key(self, name, linenum): n = name.lower() for k in self.Stations.keys(): if self.Stations[k]['line'] == linenum: sdn = self.Stations[k]['name'].lower() sdno = self.Stations[k]['name_orig'].lower() if (type(sdn) == type(n) and sdn == n) or \ (type(sdno) == type(n) and sdno == n): return k return None def _makeGraph(self): lowers = 0 uppers = 0 station_num = 0 for line in self.Map['lines'].keys(): self.Lines[line] = dict() self.Lines[line]['name_orig'] = self.Map['lines'][line]['name'] self.Lines[line]['color'] = self.Map['lines'][line]['color'] self.Lines[line]['diameter'] = self.Map['diameter'] self.Lines[line]['stations'] = list() self.Lines[line]['delays'] = dict() self.Lines[line]['delays']['day'] = self.Map['lines'][line]['delayday'] self.Lines[line]['delays']['night'] = self.Map['lines'][line]['delaynight'] for sn in xrange(len(self.Map['lines'][line]['names'])): if self.Map['lines'][line]['coordinates'][sn][0] != 0 and \ self.Map['lines'][line]['coordinates'][sn][1] != 0: self.Lines[line]['stations'].append(station_num) self.Stations[station_num] = dict() self.Stations[station_num]['line'] = line self.Stations[station_num]['number'] = station_num self.Stations[station_num]['name_orig'] = self.Map['lines'][line]['names'][sn] self.Stations[station_num]['x'] = self.Map['lines'][line]['coordinates'][sn][0] self.Stations[station_num]['y'] = self.Map['lines'][line]['coordinates'][sn][1] self.Stations[station_num]['diameter'] = self.Map['diameter'] #HACK: workaround [0,0,0,0] namerect for some stations on Moscow map if (self.Map['lines'][line]['rects'][sn][0] == 0 and self.Map['lines'][line]['rects'][sn][1] == 0): self.Map['lines'][line]['rects'][sn] = [5, self.Map['lines'][line]['coordinates'][sn][1] - 12, self.Stations[station_num - 1]['namerect'][2] + self.Stations[station_num - 1]['namerect'][0] - 5, self.Stations[station_num - 1]['namerect'][3]] self.Stations[station_num]['namerect'] = self.Map['lines'][line]['rects'][sn] self.Stations[station_num]['add'] = dict() self.Stations[station_num]['neighbours'] = list() self.Stations[station_num]['transfers'] = list() self.Graph.append(list()) station_num += 1 self.set_encoding(detect_encoding(self.Map['city_orig'] + self.Map['name_orig'])) for node in self.Map['addnodes']: #node - 'line name', 'from station name', 'to station name' ln = self._get_line_key(node[0]) fs = self._get_st_key(node[1], ln) ts = self._get_st_key(node[2], ln) if ln != None and fs != None and ts != None: self.Stations[fs]['add'][ts] = dict() self.Stations[ts]['add'][fs] = dict() atype = 'line' if not node[-1].isdigit(): atype = node[-1].lower() self.Stations[fs]['add'][ts]['type'] = atype self.Stations[ts]['add'][fs]['type'] = atype self.Stations[fs]['add'][ts]['coords'] = list() self.Stations[ts]['add'][fs]['coords'] = list() for i in xrange((len(node) - 3) / 2): if node[3 + i].isdigit() and node[4 + i].isdigit(): self.Stations[fs]['add'][ts]['coords'].append(tuple(map(int, node[3 + i * 2: 5 + i * 2]))) self.Stations[ts]['add'][fs]['coords'].append(tuple(map(int, node[3 + i * 2 : 5 + i * 2]))) #add all neighbours to graph for line in self.Map['lines']: for i in xrange(len(self.Map['lines'][line]['neighbours'])): sf = self._get_st_key(self.Map['lines'][line]['names'][i], line) if sf == None: continue for j in self.Map['lines'][line]['neighbours'][i]: st = self._get_st_key(self.Map['lines'][line]['names'][j[0]], line) if st == None: continue if not [st, j[1], line, line] in self.Graph[sf]: self.Graph[sf].append([st, j[1], line, line]) self.Stations[sf]['neighbours'].append(st) if not [sf, j[1], line, line] in self.Graph[st]: self.Graph[st].append([sf, j[1], line, line]) self.Stations[st]['neighbours'].append(sf) #add all transfers to graph for trans in self.Map['transfers']: if len(trans) > 3: lf = self._get_line_key(trans[0]) lt = self._get_line_key(trans[2]) sf = self._get_st_key(trans[1], lf) st = self._get_st_key(trans[3], lt) if sf == None or st == None: continue if len(trans) < 5: d = 0 else: d = float(trans[4]) if d == 0: #hack =/ d = 0.01 self.Graph[sf].append([st, d, lf, lt]) self.Graph[st].append([sf, d, lt, lf]) self.Stations[sf]['transfers'].append(st) self.Stations[st]['transfers'].append(sf) #workaround broken data files for ki in xrange(len(self.Graph)): tor = list() for i in xrange(len(self.Graph[ki]) - 1): for j in xrange(i + 1, len(self.Graph[ki])): if self.Graph[ki][i][0] == self.Graph[ki][j][0]: tor.append(self.Graph[ki][j]) #print '"' + self.StNames[ki][0] + '"', #print '"' + self.StNames[self.Graph[ki][i][0]][0] + '"', #print '(' + self.LineNames[self.StNames[ki][1]] + ')', #print self.Graph[ki][i][1], self.Graph[ki][j][1] if tor: print 'Bad data:', tor for r in tor: self.Graph[ki].remove(r) #mark all "under construction" stations for s in self.Stations.values(): uc = True for n in self.Graph[s['number']]: if n[1] != 0 and n[2] == n[3]: uc = False break self.Stations[s['number']]['uc'] = uc #...and remove them from graph uclist = map(lambda a: a['number'], filter(lambda a: a['uc'], self.Stations.values())) for i in xrange(len(self.Graph)): if i in uclist: self.Graph[i] = () else: self.Graph[i] = filter(lambda a: a[0] not in uclist, self.Graph[i]) def _load(self): import ConfigParser cp = ConfigParser.RawConfigParser() try: cp.readfp(self.ini) except: #ConfigParser.readfp raises exception, when he thinks, #that file is not "absolutely good" ini #so fuck it pass sec = cp.sections() lsec = map(lambda a: a.lower(), sec) s = sec[lsec.index('options')] self.Map['city_orig'] = cp_get(cp, s, 'cityname','') self.Map['name_orig'] = cp_get(cp, s, 'name','') self.Map['image'] = cp_get(cp, s, 'imagefilename', None) self.Map['diameter'] = cp_get_int(cp, s, 'stationdiameter', 16) self.Map['lines'] = dict() for nows in lsec: if nows.startswith('line') and len(nows[4:]): linenum = nows[4:] s = sec[lsec.index(nows)] self.Map['lines'][linenum] = dict() self.Map['lines'][linenum]['name'] = cp_get(cp, s, 'name', '') self.Map['lines'][linenum]['color'] = cp_get_color(cp, s, 'color', None) if cp.has_option(s, 'delayday'): self.Map['lines'][linenum]['delayday'] = cp_get_int(cp, s, 'delayday', 0) self.Map['lines'][linenum]['delaynight'] = cp_get_int(cp, s, 'delaynight', 0) else: #new version of Metro.ini delays = cp_get(cp, s, 'delays', '0,0').split(',') try: self.Map['lines'][linenum]['delayday'] = float(delays[0]) self.Map['lines'][linenum]['delaynight'] = float(delays[1]) except: self.Map['lines'][linenum]['delayday'] = 0.0 self.Map['lines'][linenum]['delaynight'] = 0.0 self.Map['lines'][linenum]['labelscolor'] = cp_get_color(cp, s, 'labelscolor', 'ffffff') self.Map['lines'][linenum]['coordinates'] = split_by_comma(cp_get(cp, s, 'coordinates', ''), 2) self.Map['lines'][linenum]['rects'] = split_by_comma(cp_get(cp, s, 'rects', ''), 4) names = parse_with_quotes(cp_get(cp, s, 'names', '')) drive = parse_with_quotes(cp_get(cp, s, 'driving', '')) self.Map['lines'][linenum]['names'] = list() for i in xrange(len(names)): if not len(names[i]): continue if names[i].find('(') != -1: nowst = names[i][:names[i].index('(')] else: nowst = names[i] #workaround duplicate station names if nowst in self.Map['lines'][linenum]['names']: nowst += ' [' + str(i) + ']' self.Map['lines'][linenum]['names'].append(nowst) self.Map['lines'][linenum]['neighbours'] = list() for i in xrange(len(names)): if not len(names[i]): continue self.Map['lines'][linenum]['neighbours'].append([]) if names[i].find('(') != -1: jnames = map(lambda a: a.lstrip('-'), parse_with_quotes(names[i][names[i].index('(') + 1:-1])) if drive[i][0] == '(': jdrives = drive[i][1:-1].split(',') else: if drive[i].lstrip().rstrip() == "": drive[i] = '0' jdrives = [drive[i]] * len(jnames) for j in xrange(len(jnames)): if not len(jnames[j]): continue if j >= len(jdrives) or jdrives[j] == "": jd = "0" else: jd = jdrives[j] self.Map['lines'][linenum]['neighbours'][-1].append((self.Map['lines'][linenum]['names'].index(jnames[j]), float(jd))) else: if i < len(names) - 1: if len(drive) < i + 1: drive.append("1") t = drive[i] if drive[i][0] == '(': t = drive[i][1:-1].split(',')[1] self.Map['lines'][linenum]['neighbours'][-1].append((i + 1, float(t))) if 'additionalnodes' in lsec: self.Map['addnodes'] = read_full_section(cp, sec[lsec.index('additionalnodes')]) else: self.Map['addnodes'] = list() if 'transfers' in lsec: self.Map['transfers'] = read_full_section(cp, sec[lsec.index('transfers')]) else: self.Map['transfers'] = list() self.Map['vectors'] = dict() vp = 'vectormapparameters' if vp in lsec: self.Map['vectors']['lineswidth'] = cp_get_int(cp, sec[lsec.index(vp)], 'lineswidth', 0) if self.Map['vectors']['lineswidth'] == 0: del self.Map['vectors']['lineswidth'] self.Map['vectors']['instructions'] = list() del cp self.ini.seek(0) insec = False for l in self.ini.readlines(): l = l.rstrip() if l.lower() == '[vectors]': insec = True continue if not l or not insec: continue if l[0] == '[': insec = False continue res = l.split(" ", 1) if len(res) != 2 or res[0][0] == ';': continue if res[0].lower() == 'pencolor': self.Map['vectors']['instructions'].append(['set_rgb_fg_color', res[1]]) elif res[0].lower() == 'spline': width, points = split_by_comma2(res[1], 2) self.Map['vectors']['instructions'].append(['spline', width, points]) elif res[0].lower() == 'textout': lst = parse_with_quotes(res[1]) if len(lst) > 4: foo = dict() foo['font'] = lst[0] foo['size'] = int(lst[1]) foo['x'] = int(lst[2]) foo['y'] = int(lst[3]) if foo['y'] < 0: foo['y'] = 0 foo['text_orig'] = lst[4] self.Map['vectors']['instructions'].append(['text', foo])