plan_gen.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #!/usr/bin/env python3
  2. ''' plan_gen main functions '''
  3. import lxml.etree as etree
  4. import numpy as np
  5. import pandas
  6. import time
  7. import utm
  8. # constants
  9. # ------------------
  10. MIN_DEPARTURE_TIME = '08:00:00'
  11. MAX_DEPARTURE_TIME = '09:00:00'
  12. WORK_DURATION = '04:00:00'
  13. # utils
  14. # ------------------
  15. def parse_value(string):
  16. ''' convert string to int, float or string '''
  17. try:
  18. return int(string)
  19. except ValueError:
  20. try:
  21. return float(string)
  22. except ValueError:
  23. return string
  24. def parse_params(param_str):
  25. ''' parse a param string to a dict '''
  26. dict_params = {}
  27. if param_str:
  28. for key_value_str in param_str.split(','):
  29. key, value = key_value_str.split('=')
  30. if key in ['hc', 'hw', 'wc', 'ww']:
  31. coords = value.split('|')
  32. dict_params[key] = [np.fromstring(str(x), dtype=int, sep=':') for x in coords]
  33. elif key in ['hr', 'wr']:
  34. dict_params[key] = np.fromstring(str(value), dtype=int, sep='|')
  35. else:
  36. dict_params[key] = parse_value(value)
  37. return dict_params
  38. def parse_latlon(csv):
  39. ''' parse lat and lon from a csv '''
  40. df = pandas.read_csv(csv, sep=';')
  41. lat = df.lat.tolist()
  42. lon = df.lon.tolist()
  43. return lat, lon
  44. def parse_weights(csv):
  45. ''' parse weights from a csv '''
  46. df = pandas.read_csv(csv, sep=';')
  47. weights = df.nbpeople.tolist()
  48. return weights
  49. def latlon_to_utm(lat, lon):
  50. ''' convert lat lon to utm coordinates '''
  51. utms = [utm.from_latlon(a, b) for a, b in zip(lat, lon)]
  52. xutm = [utm_coords[0] for utm_coords in utms]
  53. yutm = [utm_coords[1] for utm_coords in utms]
  54. return xutm, yutm
  55. def xy_to_ij(x, y, dx, dy, nb_clusters):
  56. i, j = (int(x/dx), int(y/dy))
  57. if i >= nb_clusters:
  58. i -= 1
  59. if j >= nb_clusters:
  60. j -= 1
  61. return i, j
  62. def get_seconds(time_str):
  63. ''' returns seconds from a time string '''
  64. h, m, s = time_str.split(':')
  65. return int(h) * 3600 + int(m) * 60 + int(s)
  66. def make_gaussian(size, center=None, radius=10):
  67. ''' make a square gaussian kernel '''
  68. x = np.arange(0, size, 1, float)
  69. y = x[:, np.newaxis]
  70. if center is None:
  71. x0 = y0 = size // 2
  72. else:
  73. x0 = center[0]
  74. y0 = center[1]
  75. return np.exp(-4*np.log(2) * ((x-x0)**2 + (y-y0)**2) / radius**2)
  76. # main functions
  77. # ------------------
  78. def make_clusters(nb_clusters, nodes):
  79. ''' make a grid of (nb_clusters*nb_clusters) from a nodes list '''
  80. xmin, xmax, ymin, ymax, dx, dy = get_min_max_steps(nodes, nb_clusters)
  81. clusters = np.empty((nb_clusters, nb_clusters), dtype=object)
  82. for node in nodes:
  83. x, y = (float(node.get('x')) - xmin, float(node.get('y')) - ymin)
  84. i, j = xy_to_ij(x, y, dx, dy, nb_clusters)
  85. if clusters[i][j] is None:
  86. clusters[i][j] = []
  87. clusters[i][j] += [node]
  88. return clusters
  89. def make_centers(csv, nb_clusters, nodes):
  90. ''' make centers from a csv file '''
  91. lat, lon = parse_latlon(csv)
  92. xutm, yutm = latlon_to_utm(lat, lon)
  93. xmin, xmax, ymin, ymax, dx, dy = get_min_max_steps(nodes, nb_clusters)
  94. centers = []
  95. for x, y in zip(xutm, yutm):
  96. i, j = xy_to_ij(x - xmin, y - ymin, dx, dy, nb_clusters)
  97. centers.append([i, j])
  98. return centers
  99. def make_densities(nb_clusters, centers=None, radius=None, weights=None):
  100. ''' make a list of gaussian probability densities '''
  101. if centers is None:
  102. return make_gaussian(nb_clusters, radius=nb_clusters/2)
  103. densities = np.zeros((nb_clusters, nb_clusters))
  104. for n, (c, w) in enumerate(zip(centers, weights)):
  105. densities += w * make_gaussian(nb_clusters, center=c, radius=radius[n])
  106. return densities
  107. def clean_densities(densities, clusters):
  108. ''' clean density probability if a cluster is empty '''
  109. densities = densities.flatten()
  110. clusters = clusters.flatten()
  111. for n, c in enumerate(clusters):
  112. if c is None:
  113. densities[n] = 0.0
  114. return densities
  115. # random generators
  116. # ------------------
  117. def rand_time(low, high):
  118. ''' returns a random time between low and high bounds '''
  119. low_s = get_seconds(low)
  120. high_s = get_seconds(high)
  121. delta = np.random.randint(high_s - low_s)
  122. return time.strftime('%H:%M:%S', time.gmtime(low_s + delta))
  123. def rand_node_xy(nodes, clusters, densities):
  124. ''' returns a random node coordinates from a random cluster '''
  125. node = None
  126. clusters = clusters.flatten()
  127. densities = densities.flatten()
  128. cluster = np.random.choice(clusters, p=densities/sum(densities))
  129. node = cluster[np.random.randint(len(cluster))]
  130. return (node.get('x'), node.get('y'))
  131. def rand_person(nodes, clusters, h_dens, w_dens):
  132. ''' returns a person as a dictionnary of random parameters '''
  133. home_xy = rand_node_xy(nodes, clusters, h_dens)
  134. work_xy = rand_node_xy(nodes, clusters, w_dens)
  135. home_departure = rand_time(MIN_DEPARTURE_TIME, MAX_DEPARTURE_TIME)
  136. return {'home': home_xy, 'work': work_xy, 'home_departure': home_departure}
  137. # xml builders
  138. # ------------------
  139. def make_child(parent_node, child_name, child_attrs=None):
  140. ''' creates an xml child element and set its attributes '''
  141. child = etree.SubElement(parent_node, child_name)
  142. if child_attrs is None:
  143. return child
  144. for attr, value in child_attrs.items():
  145. child.set(attr, value)
  146. return child
  147. def make_plans(persons):
  148. ''' makes xml tree of plans based on persons list '''
  149. plans = etree.Element('plans')
  150. for n, p in enumerate(persons):
  151. person = make_child(plans, 'person', {'id': str(n+1)})
  152. plan = make_child(person, 'plan')
  153. # plan
  154. make_child(plan, 'act', {'type': 'h', 'x': p['home'][0], 'y': p['home'][1], 'end_time': p['home_departure']})
  155. make_child(plan, 'leg', {'mode': 'car'})
  156. make_child(plan, 'act', {'type': 'w', 'x': p['work'][0], 'y': p['work'][1], 'dur': WORK_DURATION})
  157. make_child(plan, 'leg', {'mode': 'car'})
  158. make_child(plan, 'act', {'type': 'h', 'x': p['home'][0], 'y': p['home'][1]})
  159. return plans
  160. # xml readers
  161. # ------------------
  162. def get_nodes(input_network):
  163. ''' returns all network nodes as a list '''
  164. if not input_network:
  165. return None
  166. tree = etree.parse(input_network)
  167. return [node for node in tree.xpath("/network/nodes/node")]
  168. def get_extrem_nodes(nodes):
  169. ''' returns extremum coordinates of a nodeslist '''
  170. x = [float(node.get('x')) for node in nodes]
  171. y = [float(node.get('y')) for node in nodes]
  172. return min(x), max(x), min(y), max(y)
  173. def get_min_max_steps(nodes, nb_clusters):
  174. ''' returns min max steps '''
  175. xmin, xmax, ymin, ymax = get_extrem_nodes(nodes)
  176. dx = (xmax - xmin) / nb_clusters
  177. dy = (ymax - ymin) / nb_clusters
  178. return xmin, xmax, ymin, ymax, dx, dy