1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
2# Copyright (C) 2016-2019 German Aerospace Center (DLR) and others.
3# SUMOPy module
4# Copyright (C) 2012-2017 University of Bologna - DICAM
5# This program and the accompanying materials
6# are made available under the terms of the Eclipse Public License v2.0
7# which accompanies this distribution, and is available at
8# http://www.eclipse.org/legal/epl-v20.html
9# SPDX-License-Identifier: EPL-2.0
10
11# @file    landuse.py
12# @author  Joerg Schweizer
13# @date
14# @version $Id$
15
16import os
17import sys
18import time
19import shutil
20from xml.sax import saxutils, parse, handler
21if __name__ == '__main__':
22    try:
23        APPDIR = os.path.dirname(os.path.abspath(__file__))
24    except:
25        APPDIR = os.path.dirname(os.path.abspath(sys.argv[0]))
26    SUMOPYDIR = os.path.join(APPDIR, '..', '..')
27    sys.path.append(SUMOPYDIR)
28
29
30import numpy as np
31from numpy import random
32from collections import OrderedDict
33from coremodules.modules_common import *
34import agilepy.lib_base.classman as cm
35import agilepy.lib_base.arrayman as am
36import agilepy.lib_base.xmlman as xm
37from agilepy.lib_base.geometry import *
38from agilepy.lib_base.processes import Process, CmlMixin
39from coremodules.network.network import SumoIdsConf, MODES
40import maps
41
42
43def clean_osm(filepath_in, filepath_out):
44    """
45    Clean osm file from strange characters that compromize importing.
46    """
47    #
48    substitutes = {""": "'", "&": "+"}
49    fd_in = open(filepath_in, 'r')
50    fd_out = open(filepath_out, 'w')
51    for line in fd_in.readlines():
52        for oldstr, newstr in substitutes.iteritems():
53            line = line.replace(oldstr, newstr)
54        fd_out.write(line)
55    fd_in.close()
56    fd_out.close()
57
58
59class LanduseTypes(am.ArrayObjman):
60    def __init__(self, parent, is_add_default=True, **kwargs):
61
62        self._init_objman(ident='landusetypes',
63                          parent=parent,
64                          name='Landuse types',
65                          info='Table with information on landuse types',
66                          **kwargs)
67
68        self._init_attributes()
69        if is_add_default:
70            self.add_types_default()
71
72    def _init_attributes(self):
73        # landuse types table
74        self.add_col(am.ArrayConf('typekeys', '',
75                                  is_index=True,
76                                  dtype='object',  # actually a string of variable length
77                                  perm='r',
78                                  name='Type',
79                                  info='Type of facility. Must be unique, because used as key.',
80                                  ))
81
82        # self.add_col(am.ArrayConf( 'osmid', '',
83        #                                dtype = 'object',# actually a string of variable length
84        #                                perm='rw',
85        #                                name = 'Name',
86        #                                info = 'Name of facility type used as reference in OSM.',
87        #                                ))
88
89        self.add_col(am.ListArrayConf('osmfilters',
90                                      perm='r',
91                                      name='OSM filter',
92                                      info='List of openstreetmap filters that allow to identify this facility type.',
93                                      ))
94
95        self.add_col(am.ArrayConf('colors', np.ones(4, np.float32),
96                                  dtype=np.float32,
97                                  metatype='color',
98                                  perm='rw',
99                                  name='Colors',
100                                  info="Color corrispondig to landuse type as RGBA tuple with values from 0.0 to 1.0",
101                                  xmltag='color',
102                                  ))
103
104        self.add_col(am.ArrayConf('descriptions', '',
105                                  dtype='object',  # actually a string of variable length
106                                  perm='r',
107                                  name='Info',
108                                  info='Information about this landuse.',
109                                  ))
110
111        if len(self) > 0:
112            self.clear()
113            self.add_types_default()
114
115    def format_ids(self, ids):
116        return ','.join(self.typekeys[ids])
117
118    def get_id_from_formatted(self, idstr):
119        return self.typekeys.get_id_from_index(idstr)
120
121    def get_ids_from_formatted(self, idstrs):
122        return self.typekeys.get_ids_from_indices_save(idstrs.split(','))
123
124    def add_types_default(self):
125        # default types
126        self.add_row(typekeys='leisure',
127                     descriptions='Areas which offer leasure type activities',
128                     osmfilters=['sport', 'leisure.park', 'park'],
129                     colors=(0.2, 0.5, 0.3, 0.7)
130                     )
131        self.add_row(typekeys='commercial',
132                     descriptions='Areas with trade, offices, banks, shopping opportunitties, etc.',
133                     osmfilters=['shop.*', 'building.commercial'],
134                     colors=(0.6171875,  0.6171875,  0.875, 0.7),
135                     )
136        self.add_row(typekeys='industrial',
137                     descriptions='Areas with industrial production facilities.',
138                     osmfilters=['building.industrial'],
139                     colors=(0.89453125,  0.65625,  0.63671875, 0.7),
140                     )
141        self.add_row(typekeys='parking',
142                     descriptions='Areas reserved for car parking.',
143                     osmfilters=['building.parking', 'amenity.parking'],
144                     colors=(0.52734375,  0.875,  0.875, 0.7),
145                     )
146        self.add_row(typekeys='residential',
147                     descriptions='Residential Areas',
148                     osmfilters=['building.*', 'building'],
149                     colors=(0.921875,  0.78125,  0.4375, 0.7),
150                     )
151        self.add_row(typekeys='mixed',
152                     descriptions='Areas with mixed land use, which cannot be clearly assigned to one of the other landuse types.',
153                     osmfilters=[],
154                     colors=(0.5, 0.9, 0.5, 0.7),
155                     )
156        self.add_row(typekeys='sink',
157                     descriptions='Areas where vehicles disappear (evaporate). These zones are used for turn-flow demand models in order to avoid the creation of routes with loops.',
158                     osmfilters=[],
159                     colors=(0.5,  0.0,  0.1, 1.0),
160                     )
161        self.add_row(typekeys='education',
162                     descriptions='Educational facilities such as schools, universities',
163                     osmfilters=['building.scool', 'building.university'],
164                     colors=(0.921875,  0.78125,  0.4375, 0.7),
165                     )
166
167
168class Zones(am.ArrayObjman):
169    def __init__(self, parent, edges, **kwargs):
170        self._init_objman(ident='zones', parent=parent,
171                          name='Zones',
172                          info='Traffic Zones which can be used for zone-to-zone traffic transport demand or to specify zones for traffic evaporation.',
173                          is_plugin=True,
174                          version=0.1,
175                          **kwargs)
176
177        self._init_attributes()
178
179    def _init_attributes(self):
180        # print 'Zones._init_attributes',hasattr(self,'are_evaporate')
181        edges = self.parent.get_net().edges
182        self.add_col(SumoIdsConf('Zone', name='Name', perm='rw'))
183
184        self.add_col(am.IdsArrayConf('ids_landusetype',  self.parent.landusetypes,
185                                     id_default=6,
186                                     #choices = self.parent.landusetypes.typekeys.get_indexmap(),
187                                     #choiceattrname = 'typekeys',
188                                     groupnames=['state'],
189                                     perm='rw',
190                                     name='Type',
191                                     info='Zone type. This is actually the landuse type.',
192                                     ))
193
194        self.add_col(am.ArrayConf('coords',  np.zeros(3, dtype=np.float32),
195                                  groupnames=['state'],
196                                  perm='r',
197                                  name='Coords',
198                                  unit='m',
199                                  info='Zone center coordinates.',
200                                  is_plugin=True,
201                                  ))
202
203        self.add_col(am.ListArrayConf('shapes',
204                                      groupnames=['_private'],
205                                      perm='rw',
206                                      name='Shape',
207                                      unit='m',
208                                      info='List of 3D Shape coordinates delimiting a zone.',
209                                      is_plugin=True,
210                                      ))
211
212        self.add_col(am.IdlistsArrayConf('ids_edges_orig', edges,
213                                         groupnames=['state'],
214                                         name='IDs orig edges',
215                                         info='List with IDs of network edges that can be used as origins for trips in this zone.',
216                                         ))
217
218        self.add_col(am.IdlistsArrayConf('ids_edges_dest', edges,
219                                         groupnames=['state'],
220                                         name='IDs dest edges',
221                                         info='List with IDs of network edges that can be used as origins for trips in this zone.',
222                                         ))
223
224        self.add_col(am.ListArrayConf('probs_edges_orig',
225                                      groupnames=['_private'],
226                                      # perm='rw',
227                                      name='edge probs origin',
228                                      info='Probabilities of edges to be at the origin of a trip departing from this zone.',
229                                      ))
230        self.add_col(am.ListArrayConf('probs_edges_dest',
231                                      groupnames=['_private'],
232                                      # perm='rw',
233                                      name='edge probs dest',
234                                      info='Probabilities of edges to be a destination of a trip arriving at this zone.',
235                                      ))
236
237    def make(self, zonename='',
238             coord=np.zeros(3, dtype=np.float32),
239             shape=[],
240             id_landusetype=-1):
241        """
242        Add a zone
243        """
244        # print 'Zone.make',coord
245        # print '  shape',type(shape),shape
246
247        self.get_coords_from_shape(shape)
248        id_zone = self.add_row(coords=self.get_coords_from_shape(shape),
249                               shapes=shape,
250                               ids_landusetype=id_landusetype,
251                               )
252        if zonename == '':
253            self.ids_sumo[id_zone] = str(id_zone)
254        else:
255            self.ids_sumo[id_zone] = zonename
256
257        self.identify_zoneedges(id_zone)
258        # print '   shapes\n',self.shapes.value
259        # print '   zone.shapes[id_zone]\n',self.shapes[id_zone]
260
261        return id_zone
262
263    def format_ids(self, ids):
264        return ','.join(self.ids_sumo[ids])
265
266    def get_id_from_formatted(self, idstr):
267        return self.ids_sumo.get_id_from_index(idstr)
268
269    def get_ids_from_formatted(self, idstrs):
270        return self.ids_sumo.get_ids_from_indices_save(idstrs.split(','))
271
272    def get_coords_from_shape(self, shape):
273        # print 'get_coords_from_shape',np.array(shape),np.mean(np.array(shape),0)
274        return np.mean(np.array(shape), 0)
275
276    def del_element(self, id_zone):
277        # print 'del_element',id_zone
278        self.del_row(id_zone)
279
280    def get_edges(self):
281        return self.ids_edges_dest.get_linktab()
282
283    def refresh_zoneedges(self):
284        for _id in self.get_ids():
285            self.identify_zoneedges(_id)
286            self.make_egdeprobs(_id)
287
288    def update_netoffset(self, deltaoffset):
289        """
290        Called when network offset has changed.
291        Children may need to adjust theur coordinates.
292        """
293        # self.zones.update_netoffset(deltaoffset)
294        self.coords.value[:, :2] = self.coords.value[:, :2] + deltaoffset
295        shapes = self.shapes.value
296        for i in xrange(len(shapes)):
297            s = np.array(shapes[i])
298            s[:, :2] = s[:, :2] + deltaoffset
299            shapes[i] = list(s)
300
301    def identify_zoneedges(self, id_zone):
302        # print 'identify_zoneedges',id_zone
303        inds_within = []
304        ind = 0
305        # print '  self.shapes[id_zone]',self.shapes[id_zone]
306
307        polygon = np.array(self.shapes[id_zone])[:, :2]
308        for polyline in self.get_edges().shapes.value:
309            # print '  polygon',polygon,type(polygon)
310            # print '  np.array(polyline)[:,:2]',np.array(polyline)[:,:2],type(np.array(polyline)[:,:2])
311            if is_polyline_in_polygon(np.array(polyline)[:, :2], polygon):
312                inds_within.append(ind)
313            ind += 1
314
315        # print '  inds_within',inds_within
316
317        # select and determine weights
318        self.ids_edges_orig[id_zone] = self.get_edges().get_ids(inds_within)
319        self.ids_edges_dest[id_zone] = self.get_edges().get_ids(inds_within)
320
321    def make_egdeprobs(self, id_zone):
322        """
323        Returns two dictionaries with normalized edge weight distribution
324        one for departures and one for arrivals.
325
326        The  dictionaries have id_zone as key and a and an array of edge weights as value.
327        """
328        #zones = self.zones.value
329        #edgeweights_orig = {}
330        #edgeweights_dest = {}
331
332        # for id_zone in zones.get_ids():
333        n_edges_orig = len(self.ids_edges_orig[id_zone])
334        n_edges_dest = len(self.ids_edges_dest[id_zone])
335        # da fare meglio...
336        if n_edges_orig > 0:
337            self.probs_edges_orig[id_zone] = 1.0/float(n_edges_orig)*np.ones(n_edges_orig, np.float)
338        else:
339            self.probs_edges_orig[id_zone] = 1.0
340
341        if n_edges_dest > 0:
342            self.probs_edges_dest[id_zone] = 1.0/float(n_edges_dest)*np.ones(n_edges_dest, np.float)
343        else:
344            self.probs_edges_dest[id_zone] = 1.0
345
346    def export_evaporator_xml(self, filepath=None, encoding='UTF-8'):
347        """
348        Export trips to SUMO xml file.
349        Method takes care of sorting trips by departure time.
350        """
351        if filepath is None:
352            filepath = self.get_tripfilepath()
353        print 'export_trips_xml', filepath
354        try:
355            fd = open(filepath, 'w')
356        except:
357            print 'WARNING in write_obj_to_xml: could not open', filepath
358            return False
359
360        fd.write('<?xml version="1.0" encoding="%s"?>\n' % encoding)
361        fd.write(xm.begin('???'))
362        indent = 2
363
364        for id_zone in self.select_ids(self.are_evaporate.value):
365
366            inds_selected[ids_vtype == id_vtype] = False
367        ids_trip_selected = ids_trip[inds_selected]
368        ids_vtype_selected = set(ids_vtype[inds_selected])
369        #ids_vtypes_selected = set(ids_vtypes).difference(ids_vtypes_exclude)
370
371        self.parent.vtypes.write_xml(fd, indent=indent,
372                                     ids=ids_vtype_selected,
373                                     is_print_begin_end=False)
374
375        self.write_xml(fd,     indent=indent,
376                       ids=ids_trip_selected,
377                       attrconfigs_excluded=[self.routes, self.ids_routes],
378                       is_print_begin_end=False)
379
380        fd.write(xm.end(xmltag))
381        fd.close()
382        return filepath
383
384
385class FacilityTypeMixin(cm.BaseObjman):
386    def __init__(self, ident, parent,
387                 name='Facility Type Mixin',
388                 info='Provides methods to handle specific facility functions.',
389                 **kwargs):
390        """
391        To be overridden.
392        """
393        # attention parent is the Strategies table
394        self._init_objman(ident, parent, **kwargs)
395        attrsman = self.set_attrsman(cm.Attrsman(self))
396        self._init_attributes()
397        self._init_constants()
398
399    def _init_attributes(self):
400        self._init_attributes_common()
401
402    def _init_attributes_common(self):
403        # print 'StrategyMixin._init_attributes'
404        attrsman = self.get_attrsman()
405        landusetypes = self.get_landuse().landusetypes
406        self.ids_landusetype = attrsman.add(cm.AttrConf('ids_landusetype',
407                                                        [landusetypes.get_id_from_formatted('residential'), ],
408                                                        groupnames=['parameters'],
409                                                        perm='rw',
410                                                        #choices = landusetypes.typekeys.get_indexmap(),
411                                                        name='landuse',
412                                                        info='Default landuse type of this facility.',
413                                                        ))
414
415        self.osmkey = attrsman.add(cm.AttrConf('osmkey', 'building.yes',
416                                               groupnames=['parameters'],
417                                               perm='rw',
418                                               name='OSM key',
419                                               info='Default Open Street Map key for this facility.',
420                                               ))
421
422        self.length_min = attrsman.add(cm.AttrConf('length_min', 15.0,
423                                                   groupnames=['parameters'],
424                                                   perm='rw',
425                                                   name='Min. length',
426                                                   unit='m',
427                                                   info='Minimum length of entire property.',
428                                                   ))
429
430        self.length_max = attrsman.add(cm.AttrConf('length_max', 100.0,
431                                                   groupnames=['parameters'],
432                                                   perm='rw',
433                                                   name='Max. length',
434                                                   unit='m',
435                                                   info='Maximum length of entire property.',
436                                                   ))
437        self.width_min = attrsman.add(cm.AttrConf('width_min', 20.0,
438                                                  groupnames=['parameters'],
439                                                  perm='rw',
440                                                  name='Min. width',
441                                                  unit='m',
442                                                  info='Minimum width of entire property.',
443                                                  ))
444
445        self.width_max = attrsman.add(cm.AttrConf('width_max', 80.0,
446                                                  groupnames=['parameters'],
447                                                  perm='rw',
448                                                  name='Max. width',
449                                                  unit='m',
450                                                  info='Maximum width of entire property.',
451                                                  ))
452
453        self.height_min = attrsman.add(cm.AttrConf('height_min', 15.0,
454                                                   groupnames=['parameters'],
455                                                   perm='rw',
456                                                   name='Min. height',
457                                                   unit='m',
458                                                   info='Minimum height of facility.',
459                                                   ))
460
461        self.height_max = attrsman.add(cm.AttrConf('height_max', 35.0,
462                                                   groupnames=['parameters'],
463                                                   perm='rw',
464                                                   name='Max. height',
465                                                   unit='m',
466                                                   info='Maximum height of facility.',
467                                                   ))
468
469        self.dist_road_min = attrsman.add(cm.AttrConf('dist_road_min', 1.0,
470                                                      groupnames=['parameters'],
471                                                      perm='rw',
472                                                      name='Min. dist road',
473                                                      unit='m',
474                                                      info='Minimum distance from road.',
475                                                      ))
476
477        self.dist_road_max = attrsman.add(cm.AttrConf('dist_road_max', 5.0,
478                                                      groupnames=['parameters'],
479                                                      perm='rw',
480                                                      name='Max. dist road',
481                                                      info='Maximum distance from road.',
482                                                      ))
483
484        self.dist_prop_min = attrsman.add(cm.AttrConf('dist_prop_min', 1.0,
485                                                      groupnames=['parameters'],
486                                                      perm='rw',
487                                                      name='Min. prop dist',
488                                                      unit='m',
489                                                      info='Minimum distance to other properties.',
490                                                      ))
491
492        self.dist_prop_max = attrsman.add(cm.AttrConf('dist_prop_max', 4.0,
493                                                      groupnames=['parameters'],
494                                                      perm='rw',
495                                                      name='Max. prop dist',
496                                                      info='Maximum distance to other properties.',
497                                                      ))
498
499        self.shape_default = attrsman.add(cm.AttrConf('shape_default', [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]],
500                                                      groupnames=['parameters'],
501                                                      perm='r',
502                                                      name='Defaut shape',
503                                                      info='Basic facility shape.',
504                                                      ))
505
506        self.unitvolume_default = attrsman.add(cm.AttrConf('unitvolume_default',
507                                                           default=100.0,
508                                                           dtype=np.float32,
509                                                           perm='r',
510                                                           name='Unit volume',
511                                                           info='Default value of the volume necessary to store one person or container. Volume used to calculate capacity.',
512                                                           ))
513
514    def _init_constants(self):
515
516        # self.get_attrsman().do_not_save_attrs([
517        #                '_id_mode_bike','_id_mode_auto','_id_mode_moto',
518        #                        ])
519        pass
520
521    def get_id_type(self):
522        # print 'get_id_type from ',self.parent.get_ident_abs()
523        # print '  names',self.parent.names.get_value()
524        return self.parent.names.get_id_from_index(self.get_ident())
525
526    def get_facilities(self):
527        return self.parent.parent
528
529    def get_landuse(self):
530        return self.parent.parent.parent
531
532    def get_scenario(self):
533        return self.parent.parent.get_scenario()
534
535    def get_shape0(self, length_fac, width_fac, height_max, capacity):
536        """
537        Returns facility shape in origin coordinate system
538        and height as a function of the capacity and available space
539        """
540        # One could do some random operations on the default shape
541        # here just stretch default shape to fit area
542        shape = np.array(self.shape_default, dtype=np.float32) * [length_fac, width_fac, 0.0]
543        # y-axes must be flipped so that hoses grow
544        # to the right side of the road
545        shape[:, 1] *= -1
546        # Calculates height of the building in function of the
547        # required capacity and available space.
548
549        # here just use a random number
550        # to be overridden
551        height = min(random.uniform(self.height_min, self.height_max), height_max)
552
553        return shape, height
554
555    def generate(self,  offset=[0.0, 0.0, 0.0],
556                 length=10.0,
557                 width=10.0,
558                 angle=0.0,
559                 pos_edge=0.0,
560                 capacity=None,
561                 id_landusetype=None,
562                 height_max=30.0,
563                 id_edge=None,
564                 width_edge=3.0,
565                 ):
566        n_shape = len(self.shape_default)
567        shape_fac = np.zeros((n_shape, 3), dtype=np.float32)
568
569        # determine effecive land area
570        dist_edge = random.uniform(self.dist_road_min, self.dist_road_max) + width_edge
571        width_fac = width-dist_edge
572
573        dist_prop = random.uniform(self.dist_prop_min, self.dist_prop_max)
574        length_fac = length-2*self.dist_prop_max
575
576        # do offset
577        dxn = np.cos(angle-np.pi/2)
578        dyn = np.sin(angle-np.pi/2)
579
580        offset_fac = offset\
581            + np.array([dxn, dyn, 0.0], dtype=np.float32)*dist_edge\
582            + np.array([dyn, dxn, 0.0], dtype=np.float32)*dist_prop
583
584        shape, height_fac = self.get_shape0(length_fac, width_fac, height_max, capacity)
585
586        # transform in to the right place
587        shape_fac[:, 0] = shape[:, 0]*np.cos(angle) - shape[:, 1]*np.sin(angle)
588        shape_fac[:, 1] = shape[:, 0]*np.sin(angle) + shape[:, 1]*np.cos(angle)
589        shape_fac += offset_fac
590
591        if id_landusetype is None:
592            id_landusetype = self.ids_landusetype[0]
593
594        id_fac = self.get_facilities().make(id_landusetype=id_landusetype,
595                                            id_zone=None,
596                                            id_facilitytype=self.get_id_type(),
597                                            osmkey=self.osmkey,
598                                            area=None,
599                                            height=height_fac,
600                                            centroid=None,
601                                            shape=list(shape_fac),
602                                            id_roadedge_closest=id_edge,
603                                            position_roadedge_closest=pos_edge + 0.5 * length,
604                                            )
605
606
607class FacilityTypeHouse(FacilityTypeMixin):
608    def __init__(self, ident, parent,
609                 name='Single House',
610                 info='Parameters and methods for residential house.',
611                 **kwargs):
612
613        self._init_objman(ident, parent, name=name, info=info, **kwargs)
614        attrsman = self.set_attrsman(cm.Attrsman(self))
615
616        self._init_attributes()
617        self._init_constants()
618        # specific init
619
620
621class FacilityTypes(am.ArrayObjman):
622    def __init__(self, ident, facilities, is_add_default=True, **kwargs):
623        self._init_objman(ident=ident,
624                          parent=facilities,
625                          name='Facility types',
626                          info='Table holding facility type specific parameters and an object with methods ',
627                          **kwargs)
628
629        self._init_attributes()
630        if is_add_default:
631            self.add_default()
632
633    def _init_attributes(self):
634        # landuse types table
635        self.add_col(am.ArrayConf('names',
636                                  default='',
637                                  dtype='object',
638                                  perm='r',
639                                  is_index=True,
640                                  name='Short name',
641                                  info='Strategy name. Must be unique, used as index.',
642                                  ))
643
644        self.add_col(am.ArrayConf('unitvolumes',
645                                  default=100.0,
646                                  dtype=np.float32,
647                                  perm='rw',
648                                  name='Unit volume',
649                                  info='The volume necessary to store one person or container. Volume used to calculate capacity.',
650                                  ))
651
652        self.add_col(cm.ObjsConf('typeobjects',
653                                 #groupnames = ['state'],
654                                 name='Type objects',
655                                 info='Facility type object.',
656                                 ))
657
658        self.add_default()
659
660    def format_ids(self, ids):
661        return ','.join(self.names[ids])
662
663    def get_id_from_formatted(self, idstr):
664        return self.names.get_id_from_index(idstr)
665
666    def get_ids_from_formatted(self, idstrs):
667        return self.names.get_ids_from_indices_save(idstrs.split(','))
668
669    def add_default(self):
670        self.clear()
671        self.add_type('house', FacilityTypeHouse)
672
673    def get_typeobj(self, id_type):
674        return self.typeobjects[id_type]
675
676    def add_type(self, ident, TypeClass, **kwargs):
677        # print 'add_strategy', ident
678        if not self.names.has_index(ident):
679            factypeobj = TypeClass(ident, self)
680            id_type = self.add_row(names=ident,
681                                   typeobjects=factypeobj,
682                                   unitvolumes=factypeobj.unitvolume_default
683                                   )
684            return id_type
685        else:
686            return self.get_id_from_formatted(id_type)
687
688
689class Facilities(am.ArrayObjman):
690    def __init__(self, landuse, landusetypes, zones, net=None, **kwargs):
691        # print 'Facilities.__init__',hasattr(self,'lanes')
692        self._init_objman(ident='facilities',
693                          parent=landuse,
694                          name='Facilities',
695                          info='Information on buildings, their type of usage and access to the transport network.',
696                          xmltag=('polys', 'poly', 'ids_sumo'),
697                          is_plugin=True,
698                          **kwargs)
699
700        self._init_attributes()
701
702        if net is not None:
703            self.add_col(am.IdsArrayConf('ids_roadedge_closest', net.edges,
704                                         groupnames=['landuse'],
705                                         name='Road edge ID',
706                                         info='ID of road edge which is closest to this facility.',
707                                         ))
708
709            self.add_col(am.ArrayConf('positions_roadedge_closest', 0.0,
710                                      dtype=np.float32,
711                                      groupnames=['landuse'],
712                                      perm='r',
713                                      name='Road edge pos',
714                                      unit='m',
715                                      info='Position on road edge which is closest to this facility',
716                                      ))
717
718            # self.ids_stop_closest = self.facilities.add(cm.ArrayConf( 'ids_stop_closest', None,
719            #                        dtype = 'object',
720            #                        name = 'ID stops',
721            #                        perm='rw',
722            #                        info = 'List of IDs of closest public transport stops.',
723            #                        ))
724
725    def _init_attributes(self):
726        landusetypes = self.parent.landusetypes
727        zones = self.parent.zones
728        self.add(cm.ObjConf(FacilityTypes('facilitytypes', self,)))
729
730        self.add_col(SumoIdsConf('Facility', info='SUMO facility ID'))
731
732        self.add_col(am.IdsArrayConf('ids_landusetype', landusetypes,
733                                     groupnames=['landuse'],
734                                     perm='rw',
735                                     name='ID landuse',
736                                     info='ID of landuse.',
737                                     is_plugin=True,
738                                     ))
739
740        self.add_col(am.IdsArrayConf('ids_zone', zones,
741                                     groupnames=['landuse'],
742                                     perm='r',
743                                     name='ID zone',
744                                     info='ID of traffic zone, where this facility is located.',
745                                     ))
746        facilitytypes = self.get_facilitytypes()
747        if len(facilitytypes) > 0:
748            id_default = facilitytypes.get_ids()[0]
749        else:
750            id_default = -1
751        self.add_col(am.IdsArrayConf('ids_facilitytype', facilitytypes,
752                                     id_default=id_default,
753                                     groupnames=['landuse'],
754                                     perm='rw',
755                                     name='ID fac. type',
756                                     info='ID of facility type (house, scycraper, factory, parking,...).',
757                                     is_plugin=True,
758                                     ))
759
760        self.add_col(am.ArrayConf('osmkeys', 'building.yes',
761                                  dtype='object',  # actually a string of variable length
762                                  perm='rw',
763                                  name='OSM key',
764                                  info='OSM key of facility.',
765                                  xmltag='type',
766                                  is_plugin=True,
767                                  ))
768
769        self.add_col(am.ArrayConf('capacities', 0,
770                                  dtype=np.int32,
771                                  groupnames=['landuse'],
772                                  perm='r',
773                                  name='Capacity',
774                                  info='Person capacity of this facility. For example maximum number of adulds living in a building or number of people working in a factory.',
775                                  ))
776
777        self.add_col(am.ArrayConf('areas', 0.0,
778                                  dtype=np.float32,
779                                  groupnames=['landuse'],
780                                  perm='r',
781                                  name='Area',
782                                  unit='m^2',
783                                  info='Area of this facility.',
784                                  ))
785
786        self.add_col(am.ArrayConf('heights', 10.0,
787                                  dtype=np.float32,
788                                  groupnames=['landuse'],
789                                  perm='r',
790                                  name='Height',
791                                  unit='m',
792                                  info='Height above ground of this facility.',
793                                  ))
794
795        self.add_col(am.ArrayConf('centroids',  np.zeros(3, dtype=np.float32),
796                                  dtype=np.float32,
797                                  groupnames=['state', '_private'],
798                                  perm='r',
799                                  name='Center',
800                                  unit='m',
801                                  info='Center coordinates of this facility.',
802                                  ))
803
804        self.add_col(am.ListArrayConf('shapes',
805                                      groupnames=['_private'],
806                                      perm='rw',
807                                      name='Shape',
808                                      unit='m',
809                                      info='List of 3D Shape coordinates of facility.',
810                                      xmltag='shape',
811                                      is_plugin=True,
812                                      ))
813
814        # self.add_col(TabIdsArrayConf( 'ftypes',
815        #                            name = 'Types',
816        #                            info = 'Draw obj and ids',
817        #                            ))
818
819        if self.plugin is None:
820            self.init_plugin(True)
821            self.shapes.init_plugin(True)
822            self.osmkeys.init_plugin(True)
823            self.ids_landusetype.init_plugin(True)
824        # configure only if net is initialized
825
826    def make(self, id_sumo=None,
827             id_landusetype=None,
828             id_zone=None,
829             id_facilitytype=None,
830             osmkey=None,
831             area=None,
832             capacity=None,
833             height=None,
834             centroid=None,
835             shape=[],
836             id_roadedge_closest=None,
837             position_roadedge_closest=None,
838             ):
839        """
840        Adds a facilities
841        """
842        id_fac = self.suggest_id()
843        if id_sumo is None:
844            id_sumo = str(id_fac)
845
846        # stuff with landusetype must be done later
847        id_fac = self.add_row(_id=id_fac,
848                              ids_sumo=id_sumo,
849                              ids_landusetype=id_landusetype,
850                              ids_zone=id_zone,
851                              ids_facilitytype=id_facilitytype,
852                              osmkeys=osmkey,
853                              areas=area,
854                              capacities=capacity,
855                              heights=height,
856                              centroids=centroid,
857                              shapes=shape,
858                              ids_roadedge_closest=id_roadedge_closest,
859                              positions_roadedge_closest=position_roadedge_closest,
860                              )
861        # do area calcs and other
862        if area is None:
863            self.update_area(id_fac)
864
865        if capacity is None:
866            self.update_capacity(id_fac)
867
868        if centroid is None:
869            self.update_centroid(id_fac)
870        return id_fac
871
872    def generate(self, facilitytype, **kwargs):
873        """
874        Generates a facility. The generation of the facility will be
875        performed by the faciliy type instance.
876        """
877        return facilitytype.generate(**kwargs)
878
879    def format_ids(self, ids):
880        return ','.join(self.ids_sumo[ids])
881
882    def get_id_from_formatted(self, idstr):
883        return self.ids_sumo.get_id_from_index(idstr)
884
885    def get_ids_from_formatted(self, idstrs):
886        return self.ids_sumo.get_ids_from_indices_save(idstrs.split(','))
887
888    def del_element(self, id_fac):
889        # print 'del_element',id_zone
890        self.del_row(id_fac)
891
892    def write_xml(self, fd, indent=0, is_print_begin_end=True):
893        xmltag, xmltag_item, attrname_id = self.xmltag
894        layer_default = -1
895        fill_default = 1
896        ids_landusetype = self.ids_landusetype
897        landusecolors = self.get_landusetypes().colors
898
899        if is_print_begin_end:
900            fd.write(xm.begin(xmltag, indent))
901
902        attrsconfigs_write = [self.ids_sumo, self.osmkeys, self.shapes]
903        for _id in self.get_ids():
904            fd.write(xm.start(xmltag_item, indent+2))
905            for attrsconfig in attrsconfigs_write:
906                attrsconfig.write_xml(fd, _id)
907
908            landusecolors.write_xml(fd, ids_landusetype[_id])
909            fd.write(xm.num('layer', layer_default))
910            fd.write(xm.num('fill', fill_default))
911
912            fd.write(xm.stopit())
913
914        if is_print_begin_end:
915            fd.write(xm.end(xmltag, indent))
916
917    def get_landusetypes(self):
918        return self.ids_landusetype.get_linktab()
919
920    def get_facilitytypes(self):
921        return self.facilitytypes.get_value()
922
923    def get_net(self):
924        # print 'get_net',self.ids_edge_closest_road.get_linktab(),self.ids_edge_closest_road.get_linktab().parent
925        return self.ids_roadedge_closest.get_linktab().parent
926
927    def get_scenario(self):
928        return self.ids_roadedge_closest.get_linktab().parent.parent
929
930    def update_netoffset(self, deltaoffset):
931        """
932        Called when network offset has changed.
933        Children may need to adjust theur coordinates.
934        """
935        # self.zones.update_netoffset(deltaoffset)
936        self.centroids.value[:, :2] = self.centroids.value[:, :2] + deltaoffset
937        shapes = self.shapes.value
938        for i in xrange(len(shapes)):
939            s = np.array(shapes[i])
940            s[:, :2] = s[:, :2] + deltaoffset
941            shapes[i] = list(s)
942
943    def get_edges(self):
944        return self.ids_roadedge_closest.get_linktab()
945
946    def identify_taz(self):
947        """
948        Identifies id of traffic assignment zone for each facility.
949        Note that not all facilities are within such a zone.
950        """
951        zones = self.ids_zone.get_linktab()
952        # self.get_demand().get_districts()
953        for id_fac in self.get_ids():
954            for id_zone in zones.get_ids():
955                if is_polyline_in_polygon(self.shapes[id_fac], zones.shapes[id_zone]):
956                    self.ids_zone[id_fac] = id_zone
957                    break
958
959    def get_departure_probabilities(self):
960        """
961        Returns a dictionary, where probabilities[id_zone]
962        is a vector of departure probabilities for each facility
963        of zone id_zone.
964        """
965        zones = self.ids_zone.get_linktab()
966        # print 'get_departure_probabilities in n_zones',len(zones)
967        probabilities = {}
968
969        inds_fac = self.get_inds()
970        for id_zone in zones.get_ids():
971            # probabilities[id_zone]={}
972            # for id_landusetype in  set(self.ids_landusetype.value):
973            # print '  id_zone',id_zone
974            # print '  ids_landusetype',self.ids_landusetype.value[inds_fac]
975            # print '  ids_zone',self.ids_zone.value[inds_fac]
976            # print ''
977            util = self.capacities.value[inds_fac].astype(np.float32)*(self.ids_zone.value[inds_fac] == id_zone)
978            util_tot = np.sum(util)
979            # print '\n\n  [id_taz][ftype]',id_taz,ftype,util_tot,np.sum(util/np.sum(util))
980            # print '  self.type==ftype',self.type==ftype
981            # print '  self.id_taz==id_taz',self.id_taz==id_taz
982            # print '  util',util
983            if util_tot > 0.0:
984                probabilities[id_zone] = util/util_tot
985            else:
986                probabilities[id_zone] = util  # all zero prob
987
988        return probabilities, self.get_ids(inds_fac)
989
990    def get_departure_probabilities_landuse(self):
991        """
992        Returns the dictionnary of dictionaries with departure (or arrival)
993        probabilities where probabilities[id_zone][id_landusetype]
994        is a probability distribution vector  giving for each facility the
995        probability to depart/arrive in zone id_zone with facility type ftype.
996
997        The ids_fac is an array that contains the facility ids in correspondence
998        to the probability vector.
999        """
1000        print 'get_departure_probabilities_landuse'
1001        probabilities = {}
1002        zones = self.ids_zone.get_linktab()
1003        inds_fac = self.get_inds()
1004        for id_zone in zones.get_ids():
1005            probabilities[id_zone] = {}
1006            for id_landusetype in set(self.ids_landusetype.value):
1007                print '  id_zone,id_landusetype', id_zone, id_landusetype
1008                # print '  ids_landusetype',self.ids_landusetype.value[inds_fac]
1009                # print '  ids_zone',self.ids_zone.value[inds_fac]
1010                # print ''
1011                util = self.capacities.value[inds_fac].astype(
1012                    np.float32)*((self.ids_landusetype.value[inds_fac] == id_landusetype) & (self.ids_zone.value[inds_fac] == id_zone))
1013                util_tot = np.sum(util)
1014                # print '\n\n  [id_taz][ftype]',id_taz,ftype,util_tot,np.sum(util/np.sum(util))
1015                # print '  self.type==ftype',self.type==ftype
1016                # print '  self.id_taz==id_taz',self.id_taz==id_taz
1017                # print '  util',util
1018                if util_tot > 0.0:
1019                    probabilities[id_zone][id_landusetype] = util/util_tot
1020                else:
1021                    probabilities[id_zone][id_landusetype] = util  # all zero prob
1022
1023        return probabilities, self.get_ids(inds_fac)
1024
1025    def update(self, ids=None):
1026        # print 'update',ids
1027        if ids is None:
1028            ids = self.get_ids()
1029
1030        for _id in ids:
1031            # print '  self.centroids[_id]',self.centroids[_id]
1032            # print '  self.shapes[_id]',self.shapes[_id],np.mean(self.shapes[_id],0)
1033            self.update_centroid(_id)
1034            #self.areas[_id] = find_area(np.array(self.shapes[_id],float)[:,:2])
1035            self.update_area(_id)
1036            #self.areas[_id] = get_polygonarea_fast(np.array(self.shapes[_id],float)[:,0], np.array(self.shapes[_id],float)[:,1])
1037
1038        self.update_capacities(ids)
1039        # self.identify_closest_edge(ids)
1040
1041    def update_centroid(self, _id):
1042        self.centroids[_id] = np.mean(self.shapes[_id], 0)
1043
1044    def update_area(self, _id):
1045        self.areas[_id] = get_polygonarea_fast(np.array(self.shapes[_id], float)[
1046                                               :, 0], np.array(self.shapes[_id], float)[:, 1])
1047
1048    def update_capacity(self, id_fac):
1049        self.update_capacities([id_fac])
1050
1051    def update_capacities(self, ids):
1052        volumes_unit = self.get_facilitytypes().unitvolumes[self.ids_facilitytype[ids]]
1053        self.capacities[ids] = self.areas[ids]*self.heights[ids]/volumes_unit
1054
1055    def get_dists(self, ids_fac_from, ids_fac_to):
1056        """
1057        Returns centroid to centroid distance from facilities in vector
1058        ids_fac_from to facilities in vector ids_fac_to.
1059        """
1060
1061        return np.sqrt(np.sum((self.centroids[ids_fac_to]-self.centroids[ids_fac_from])**2))
1062
1063    def identify_closest_edge(self, ids=None, priority_max=5, has_sidewalk=True):
1064        """
1065        Identifies edge ID and position on this edge that
1066        is closest to the centoid of each facility and the satisfies certain
1067        conditions.
1068        """
1069        print 'identify_closest_edge'
1070        edges = self.get_edges()
1071
1072        # select edges...if (edges.priorities[id_edge]<=priority_max) & edges.has_sidewalk(id_edge):
1073
1074        ids_edge = edges.select_ids((edges.priorities.get_value() < priority_max)
1075                                    & (edges.widths_sidewalk.get_value() > 0.0))
1076
1077        edges.make_segment_edge_map(ids_edge)
1078
1079        if ids is None:
1080            ids = self.get_ids()
1081        for id_fac in ids:
1082            id_edge = edges.get_closest_edge(self.centroids[id_fac])
1083
1084            # determin position on edeg where edge is closest to centroid
1085            # TODO: solve this faster with precalculated maps!!
1086            xc, yc, zc = self.centroids[id_fac]
1087            shape = edges.shapes[id_edge]
1088            n_segs = len(shape)
1089
1090            d_min = 10.0**8
1091            x_min = 0.0
1092            y_min = 0.0
1093            j_min = 0
1094            p_min = 0.0
1095            pos = 0.0
1096            x1, y1, z1 = shape[0]
1097            edgelength = edges.lengths[id_edge]
1098            for j in xrange(1, n_segs):
1099                x2, y2, z2 = shape[j]
1100                d, xp, yp = shortest_dist(x1, y1, x2, y2, xc, yc)
1101                # print '    x1,y1=(%d,%d)'%(x1,y1),',x2,y2=(%d,%d)'%(x2,y2),',xc,yc=(%d,%d)'%(xc,yc)
1102                # print '    d,x,y=(%d,%d,%d)'%shortest_dist(x1,y1, x2,y2, xc,yc)
1103                if d < d_min:
1104                    d_min = d
1105                    # print '    **d_min=',d_min,[xp,yp]
1106                    x_min = xp
1107                    y_min = yp
1108                    j_min = j
1109                    p_min = pos
1110                # print '    pos',pos,[x2-x1,y2-y1],'p_min',p_min
1111                pos += np.linalg.norm([x2-x1, y2-y1])
1112                x1, y1 = x2, y2
1113
1114            x1, y1, z1 = shape[j_min-1]
1115            pos_min = p_min+np.linalg.norm([x_min-x1, y_min-y1])
1116            # print '  k=%d,d_min=%d, x1,y1=(%d,%d),xmin,ymin=(%d,%d),xc,yc=(%d,%d)'%(k,d_min,x1,y1,x_min,y_min,xc,yc)
1117            # print '  pos=%d,p_min=%d,pos_min=%d'%(pos,p_min,pos_min)
1118
1119            if pos_min > edgelength:
1120                pos_min = edgelength
1121
1122            if pos_min < 0:
1123                pos_min = 0
1124            # print '  id_fac,id_edge',id_fac,id_edge,pos_min
1125            self.ids_roadedge_closest[id_fac] = id_edge
1126            self.positions_roadedge_closest[id_fac] = pos_min
1127
1128    def set_shape(self, id_fac, shape):
1129        # print 'set_shape',id_fac,shape
1130        self.shapes[id_fac] = shape
1131        self.update([id_fac])
1132        #self.areas[id_fac] = find_area(shape[:,:2])
1133        #self.centroids[id_fac] =np.mean(shape,0)
1134
1135    def add_polys(self, ids_sumo=[], **kwargs):
1136        """
1137        Adds a facilities as used on sumo poly xml info
1138        """
1139        # stuff with landusetype must be done later
1140        return self.add_rows(n=len(ids_sumo),    ids_sumo=ids_sumo, **kwargs)
1141
1142    def add_poly(self, id_sumo, id_landusetype=None, osmkey=None, shape=np.array([], np.float32)):
1143        """
1144        Adds a facility as used on sumo poly xml info
1145        """
1146        # print 'add_poly',id_sumo,id_landusetype,osmkey
1147
1148        landusetypes = self.get_landusetypes()
1149        if id_landusetype is not None:
1150            # this means that landusetype has been previousely identified
1151            if osmkey is None:
1152                # use filter as key
1153                osmkey = landusetypes.osmfilters[id_landusetype][0]
1154
1155            id_fac = self.add_row(ids_sumo=id_sumo,
1156                                  ids_landusetype=id_landusetype,
1157                                  osmkeys=osmkey,
1158                                  )
1159            self.set_shape(id_fac, shape)
1160            return id_fac
1161
1162        else:
1163            # identify ftype from fkeys...
1164            keyvec = osmkey.split('.')
1165            len_keyvec = len(keyvec)
1166            is_match = False
1167            for id_landusetype in landusetypes.get_ids():
1168                # print '  ',landusetypes.osmfilters[id_landusetype]
1169                # if fkeys==('building.industrial'): print ' check',facilitytype
1170                for osmfilter in landusetypes.osmfilters[id_landusetype]:
1171                    # print '     ',
1172                    osmfiltervec = osmfilter.split('.')
1173                    if osmkey == osmfilter:  # exact match of filter
1174                        is_match = True
1175                        # if fkeys==('building.industrial'):print '    found exact',osmkey
1176                    elif (len(osmfiltervec) == 2) & (len_keyvec == 2):
1177
1178                        if osmfiltervec[0] == keyvec[0]:
1179
1180                            if osmfiltervec[1] == '*':
1181                                is_match = True
1182                                # if fkeys==('building.industrial'):print '    found match',osmkeyvec[0]
1183
1184                            # redundent to exact match
1185                            # elif osmkeyvec[1]==keyvec[1]:
1186                            #    is_match = True
1187                            #   if is_match:
1188                            #       print '    found exact',osmkey
1189
1190                    if is_match:
1191                        # if fkeys==('building.industrial'):print '  *found:',facilitytype,fkeys
1192                        # return self.facilities.set_row(ident, type = facilitytype, polygon=polygon, fkeys = fkeys,area=find_area(polygon),centroid=np.mean(polygon,0))
1193                        id_fac = self.add_row(ids_sumo=id_sumo,
1194                                              ids_landusetype=id_landusetype,
1195                                              osmkeys=osmkey,
1196                                              )
1197                        self.set_shape(id_fac, shape)
1198                        return id_fac
1199
1200    def clear(self):
1201        # self.reset()
1202        self.clear_rows()
1203
1204    def set_shapes(self, ids, vertices):
1205        self.shapes[ids] = vertices
1206        if not hasattr(ids, '__iter__'):
1207            ids = [ids]
1208        self.update(ids)
1209
1210    def import_poly(self, polyfilepath, is_remove_xmlfiles=False):
1211        print 'import_poly from %s ' % (polyfilepath,)
1212        self.clear()
1213        # let's read first the offset information, which are in the
1214        # comment area
1215        fd = open(polyfilepath, 'r')
1216        is_comment = False
1217        is_processing = False
1218        offset = self.get_net().get_offset()  # default is offset from net
1219        # print '  offset,offset_delta',offset,type(offset)
1220        #offset = np.array([0,0],float)
1221        for line in fd.readlines():
1222            if line.find('<!--') >= 0:
1223                is_comment = True
1224            if is_comment & (line.find('<processing') >= 0):
1225                is_processing = True
1226            if is_processing & (line.find('<offset.x') >= 0):
1227                offset[0] = float(xm.read_keyvalue(line, 'value'))
1228            elif is_processing & (line.find('<offset.y') >= 0):
1229                offset[1] = float(xm.read_keyvalue(line, 'value'))
1230                break
1231        fd.close()
1232        offset_delta = offset - self.get_net().get_offset()
1233
1234        exectime_start = time.clock()
1235
1236        counter = SumoPolyCounter()
1237        parse(polyfilepath, counter)
1238        fastreader = SumoPolyReader(self, counter, offset_delta)
1239        parse(polyfilepath, fastreader)
1240        fastreader.finish()
1241
1242        # update ids_landuse...
1243        # self.update()
1244
1245        # timeit
1246        print '  exec time=', time.clock() - exectime_start
1247
1248        # print '  self.shapes',self.shapes.value
1249
1250
1251class SumoPolyCounter(handler.ContentHandler):
1252    """Counts facilities from poly.xml file into facility structure"""
1253
1254    def __init__(self):
1255        self.n_fac = 0
1256
1257    def startElement(self, name, attrs):
1258        # print 'startElement',name,len(attrs)
1259        if name == 'poly':
1260            self.n_fac += 1
1261
1262
1263class SumoPolyReader(handler.ContentHandler):
1264    """Reads facilities from poly.xml file into facility structure"""
1265
1266    def __init__(self, facilities, counter, offset_delta):
1267
1268        self._facilities = facilities
1269        self._ids_landusetype_all = self._facilities.get_landusetypes().get_ids()
1270        self._osmfilters = self._facilities.get_landusetypes().osmfilters
1271
1272        self._ind_fac = -1
1273        self.ids_sumo = np.zeros(counter.n_fac, np.object)
1274        self.ids_landusetype = -1*np.ones(counter.n_fac, np.int32)
1275        self.osmkeys = np.zeros(counter.n_fac, np.object)
1276        self.shape = np.zeros(counter.n_fac, np.object)
1277        self.areas = np.zeros(counter.n_fac, np.float32)
1278        self.centroids = np.zeros((counter.n_fac, 3), np.float32)
1279
1280        #self._id_facility = None
1281        self._offset_delta = offset_delta
1282
1283    def startElement(self, name, attrs):
1284
1285        # print 'startElement', name, len(attrs)
1286        if name == 'poly':
1287            self._ind_fac += 1
1288            i = self._ind_fac
1289
1290            osmkey = attrs.get('type', 'building.yes')
1291
1292            id_landuse = self.get_landuse(osmkey)
1293            if id_landuse >= 0:  # land use is interesting
1294                shape = xm.process_shape(attrs.get('shape', ''), offset=self._offset_delta)
1295                shapearray = np.array(shape, np.float32)
1296                # print '  shapearray',shapearray
1297                self.ids_sumo[i] = attrs['id']
1298                self.ids_landusetype[i] = id_landuse
1299                self.osmkeys[i] = osmkey
1300                self.shape[i] = shape
1301                self.areas[i] = find_area(shapearray[:, :2])
1302                self.centroids[i] = np.mean(shapearray, 0)
1303
1304            # color info in this file no longer used as it is defined in
1305            # facility types table
1306            # color = np.array(xm.parse_color(attrs['color']))*0.8,# make em darker!!
1307
1308    def get_landuse(self, osmkey):
1309        keyvec = osmkey.split('.')
1310        len_keyvec = len(keyvec)
1311        # print 'get_landuse',len_keyvec,keyvec
1312        #is_match = False
1313        for id_landusetype in self._ids_landusetype_all:
1314            # print '  ',landusetypes.osmfilters[id_landusetype]
1315            # if fkeys==('building.industrial'): print ' check',facilitytype
1316            for osmfilter in self._osmfilters[id_landusetype]:
1317                osmfiltervec = osmfilter.split('.')
1318                # print '     osmfiltervec',id_landusetype,osmfiltervec,osmkey==osmfilter,(len(osmfiltervec)==2)&(len_keyvec==2)
1319
1320                if osmkey == osmfilter:  # exact match of filter
1321                    return id_landusetype
1322
1323                elif (len(osmfiltervec) == 2) & (len_keyvec == 2):
1324
1325                    if osmfiltervec[0] == keyvec[0]:
1326
1327                        if osmfiltervec[1] == '*':
1328                            return id_landusetype
1329        return -1
1330
1331    def finish(self):
1332
1333        # print 'write_to_net'
1334        inds_valid = np.flatnonzero(self.ids_landusetype >= 0)
1335        ids_fac = self._facilities.add_polys(
1336            ids_sumo=self.ids_sumo[inds_valid],
1337            ids_landusetype=self.ids_landusetype[inds_valid],
1338            osmkeys=self.osmkeys[inds_valid],
1339            shapes=self.shape[inds_valid],
1340            areas=self.areas[inds_valid],
1341            centroids=self.centroids[inds_valid],
1342        )
1343
1344    # def characters(self, content):
1345    #    if self._id is not None:
1346    #        self._currentShape = self._currentShape + content
1347
1348    # def endElement(self, name):
1349    #    pass
1350
1351
1352class Parking(am.ArrayObjman):
1353    def __init__(self, landuse, lanes, **kwargs):
1354        # print 'Parking.__init__',lanes,hasattr(self,'lanes')
1355        self._init_objman(ident='parking', parent=landuse,
1356                          name='Parking',
1357                          info='Information on private car parking.',
1358                          #is_plugin = True,
1359                          # **kwargs
1360                          )
1361
1362        self._init_attributes(lanes)
1363
1364    def _init_attributes(self, lanes=None):
1365        # print 'Parkin._init_attributes',lanes,hasattr(self,'lanes'),hasattr(self,'ids_lane')
1366        if lanes is None:
1367            # upgrade call
1368            # lanes exists already as link
1369            lanes = self.get_lanes()
1370
1371        # print '  lanes',lanes
1372        # --------------------------------------------------------------------
1373        # misc params...
1374
1375        # these are options for assignment procedure!!
1376        # self.add(AttrConf(  'length_noparking', kwargs.get('length_noparking',20.0),
1377        #                                groupnames = ['options'],
1378        #                                perm='wr',
1379        #                                unit = 'm',
1380        #                                name = 'Min Length',
1381        #                                info = 'Minimum edge length for assigning on-road parking space.' ,
1382        #                                #xmltag = 'pos',
1383        #                                ))
1384        #
1385        # self.add(AttrConf(  'length_space', kwargs.get('length_space',20.0),
1386        #                                groupnames = ['options'],
1387        #                                perm='wr',
1388        #                                unit = 'm',
1389        #                                name = 'Lot length',
1390        #                                info = 'Length of a standard parking lot.' ,
1391        #                                #xmltag = 'pos',
1392        #                                ))
1393
1394        self.add_col(am.IdsArrayConf('ids_lane', lanes,
1395                                     name='ID Lane',
1396                                     info='ID of lane for this parking position. ',
1397                                     ))
1398
1399        self.add_col(am.ArrayConf('positions', 0.0,
1400                                  dtype=np.float32,
1401                                  perm='r',
1402                                  name='Pos',
1403                                  unit='m',
1404                                  info="Position on lane for this parking.",
1405                                  ))
1406
1407        self.add_col(am.ArrayConf('lengths', 0.0,
1408                                  dtype=np.float32,
1409                                  #choices = OPTIONMAP_POS_DEPARTURE,
1410                                  perm='r',
1411                                  name='Length',
1412                                  unit='m',
1413                                  info="Length of parking lot in edge direction.",
1414                                  ))
1415
1416        self.add_col(am.ArrayConf('angles', 0.0,
1417                                  dtype=np.float32,
1418                                  perm='rw',
1419                                  name='Angle',
1420                                  unit='deg',
1421                                  info="Parking angle with respect to lane direction.",
1422                                  ))
1423
1424        self.add_col(am.ArrayConf('vertices',  np.zeros((2, 3), dtype=np.float32),
1425                                  dtype=np.float32,
1426                                  groupnames=['_private'],
1427                                  perm='r',
1428                                  name='Coords',
1429                                  unit='m',
1430                                  info="Start and end vertices of right side of parking space.",
1431                                  ))
1432
1433        self.add_col(am.ArrayConf('numbers_booking', 0,  # ???
1434                                  dtype=np.int32,
1435                                  perm='r',
1436                                  name='booked',
1437                                  info="Number of vehicles booked for this parking.",
1438                                  ))
1439
1440        # self.add_col(am.ArrayConf(  'durations', 0.0,# ???
1441        #                                dtype=np.float32,
1442        #                                perm='r',
1443        #                                name = 'Parking duration',
1444        #                                unit = 's',
1445        #                                info = "Default duration of car parking.",
1446        #                                ))
1447
1448        self.add(cm.ObjConf(lanes, is_child=False, groups=['_private']))
1449
1450        self.add(cm.ObjConf(lanes.parent.edges, is_child=False, groups=['_private']))
1451
1452    def get_edges(self):
1453        return self.edges.get_value()
1454
1455    def get_lanes(self):
1456        return self.lanes.get_value()
1457
1458    def link_vehiclefleet(self, vehicles):
1459        """
1460        Links to table with vehicle info.
1461        """
1462        self.add_col(am.IdsArrayConf('ids_bookedveh', vehicles,
1463                                     name='ID booked veh',
1464                                     info='ID of vehicle which has booked this parking position.',
1465                                     ))
1466
1467    def update_netoffset(self, deltaoffset):
1468        """
1469        Called when network offset has changed.
1470        Children may need to adjust their coordinates.
1471        """
1472        pass
1473
1474    def get_parkinglane_from_edge(self, id_edge, id_mode, length_min=15.0, priority_max=8, n_freelanes_min=1):
1475        """
1476        Check if edge can have on-road parking
1477        """
1478        edges = self.edges.get_value()
1479        lanes = self.lanes.get_value()
1480        # check size
1481        # laneindex =
1482        # print 'get_parkinglane_from_edge',id_edge, id_mode,priority_max,length_min
1483        # print '   check',(edges.priorities[id_edge]<=priority_max),(edges.lengths[id_edge]>length_min),(edges.widths_sidewalk[id_edge]>-1)
1484
1485        if (edges.priorities[id_edge] <= priority_max) & (edges.lengths[id_edge] > length_min) & (edges.widths_sidewalk[id_edge] > 0):
1486
1487            laneindex = edges.get_laneindex_allowed(id_edge, id_mode)
1488            # print '  found',laneindex,edges.nums_lanes[id_edge]-laneindex > n_freelanes_min
1489            if (laneindex >= 0) & (edges.nums_lanes[id_edge]-laneindex >= n_freelanes_min):
1490                return edges.ids_lanes[id_edge][laneindex]
1491            else:
1492                return -1
1493
1494        return -1  # no parking possible by default
1495
1496    def get_edge_pos_parking(self, id_parking):
1497        lanes = self.lanes.get_value()
1498        return lanes.ids_edge[self.ids_lane[id_parking]], self.positions[id_parking]
1499
1500    # def get_edge_pos_parking(self, id_parking):
1501    #    """
1502    #    Retuens edge and position of parking with id_parking
1503    #    """
1504    #    ind = self.parking.get_ind(id_parking)
1505    #
1506    #    return self.edges.get_value()(self.id_edge_parking[ind]),self.pos_edge_parking[ind]
1507
1508    def make_parking(self, id_mode=MODES['passenger'], length_min=42.0, length_noparking=15.0, length_lot=6.0, angle=0.0, is_clear=True, **kwargs):
1509        print 'make_parking'
1510        if is_clear:
1511            self.clear()
1512        edges = self.edges.get_value()
1513        lanes = self.lanes.get_value()
1514        n_parking = 0
1515        ids_parking = []
1516        ids_lane_current = self.ids_lane.get_value().copy()
1517        for id_edge in edges.get_ids():
1518            # check if edge is suitable...
1519            # print '  id_edge,length,n_lanes,',id_edge
1520            id_lane = self.get_parkinglane_from_edge(id_edge, id_mode, length_min, **kwargs)
1521
1522            is_eligible = id_lane >= 0
1523            if not is_clear:
1524                if id_lane not in ids_lane_current:
1525                    is_eligible = False
1526
1527            if is_eligible:
1528                n_spaces = int((edges.lengths[id_edge]-2*length_noparking)/length_lot)
1529                # print '    create',id_edge,lanes.indexes[id_lane],edges.lengths[id_edge],n_spaces
1530                # print '  delta',lanes.shapes[id_lane][0]-lanes.shapes[id_lane][-1]
1531                pos_offset = length_noparking
1532                pos = pos_offset
1533                if n_spaces > 0:
1534                    for i in xrange(n_spaces):
1535                        #id_park = self.suggest_id()
1536                        # print '    pos=',pos,pos/edges.lengths[id_edge]
1537
1538                        # print '    vertices',get_vec_on_polyline_from_pos(lanes.shapes[id_lane],pos, length_lot, angle = angle)
1539                        n_parking += 1
1540
1541                        id_park = self.add_row(ids_lane=id_lane,
1542                                               positions=pos,
1543                                               lengths=length_lot,
1544                                               angles=angle,
1545                                               vertices=get_vec_on_polyline_from_pos(
1546                                                   lanes.shapes[id_lane], pos, length_lot-0.5, angle=angle)
1547                                               )
1548                        # print '    created id_park,pos', id_park,pos#,get_coord_on_polyline_from_pos(lanes.shapes[id_lane],pos),lanes.shapes[id_lane]
1549                        ids_parking.append(id_park)
1550                        pos = pos_offset+(i+1)*length_lot
1551
1552        print '  created %d parking spaces' % n_parking
1553        return ids_parking
1554
1555    def clear_booking(self):
1556        self.numbers_booking.reset()
1557        if hasattr(self, 'ids_bookedveh'):
1558            self.ids_bookedveh.reset()
1559
1560    def get_closest_parking(self, id_veh, coord, c_spread=2.0):
1561        """
1562        Returns  parking space for  id_veh as close as possible to coord.
1563        """
1564
1565        #inds_person = self.persons.get_inds(ids_person)
1566        print 'get_closest_parking'
1567        ind_parking_closest = self.get_inds()[np.argmin(
1568            np.sum((coord-self.vertices.value[:, 1, :])**2, 1) + c_spread*lengths*self.lengths.get_value())]
1569        self.numbers_booking.get_value()[ind_parking_closest] += 1
1570        return self.get_ids(ind_parking_closest), ind_parking_closest
1571
1572    def get_closest_parkings(self, ids_veh, coords, c_spread=2.0):
1573        """
1574        Returns  parking space for each vehicle in ids_veh as close as possible to coords.
1575        """
1576
1577        #inds_person = self.persons.get_inds(ids_person)
1578        n = len(ids_veh)
1579        # print 'get_closest_parking',n,len(self)
1580        if len(self) == 0:
1581            print 'WARNING in get_closest_parkings: there is no parking.'
1582            return [], []
1583
1584        #parking = self.get_landuse().parking
1585        #inds_parking = parking.get_inds()
1586        coord_parking = self.vertices.value[:, 1, :]
1587        # print '  coord_parking',coord_parking
1588        numbers_booking = self.numbers_booking.get_value()
1589        lengths = self.lengths.get_value()
1590        inds_vehparking = np.zeros(n, int)
1591
1592        #inds_parking_avail = np.flatnonzero( self.ids_bookedveh.value == -1).tolist()
1593        inds_parking_avail = self.get_inds().copy()
1594        #ids_veh = np.zeros(n,object)
1595        i = 0
1596        for id_veh, coord in zip(ids_veh, coords):
1597            # print '\n  id_veh',id_veh
1598            # print '    landuse.id_bookedveh_parking',landuse.id_bookedveh_parking
1599            #
1600
1601            # print '    inds_parking_avail',inds_parking_avail
1602            # print '    dists',np.sum((coord-coord_parking[inds_parking_avail])**2,1),np.argmin(np.sum((coord-coord_parking[inds_parking_avail])**2,1))
1603            ind_parking_closest = inds_parking_avail[np.argmin(
1604                np.sum((coord-coord_parking)**2, 1) + c_spread*lengths*numbers_booking)]
1605            # print '  ind_parking_closest,n_avail',ind_parking_closest,len(inds_parking_avail)
1606            inds_vehparking[i] = ind_parking_closest
1607            # print '    coords_veh',coord
1608            # print '    coord_park',coord_parking[ind_parking_closest]
1609            numbers_booking[ind_parking_closest] += 1
1610
1611            id_parking = self.get_ids([ind_parking_closest])
1612            id_edge, pos = self.get_edge_pos_parking(id_parking)
1613            # print '    id_veh=%s,id_parking_closest=%s, dist =%.2fm'%(id_veh,id_parking,np.sqrt(np.sum((coord-coord_parking[ind_parking_closest])**2)))
1614            # ids_bookedveh[ind_parking_closest]=id_veh # occupy parking
1615            # print '    id_edge, pos',id_edge, pos
1616            # inds_parking_avail.remove(ind_parking_closest)
1617            i += 1
1618
1619        # print '  inds_vehparking',  inds_vehparking
1620        # print '  ids_vehparking',  self.get_ids(inds_vehparking)
1621        # print '  ids_veh',ids_veh
1622        #self.ids_bookedveh.value[inds_vehparking] = ids_veh
1623        # self.ids_bookedveh.[ids_parking] =ids_bookedveh
1624        return self.get_ids(inds_vehparking), inds_vehparking
1625
1626    def assign_parking(self, ids_veh, coords, is_overbook=False):
1627        """
1628        Assigns a parking space to each vehicle as close as possible to coords.
1629        Only one vehicle can be assigned to a parking space.
1630        """
1631
1632        #inds_person = self.persons.get_inds(ids_person)
1633        n = len(ids_veh)
1634        # print 'assign_parking',n
1635
1636        #parking = self.get_landuse().parking
1637        #inds_parking = parking.get_inds()
1638        coord_parking = self.vertices.value[:, 1, :]
1639
1640        inds_vehparking = np.zeros(n, int)
1641
1642        inds_parking_avail = np.flatnonzero(self.ids_bookedveh.value == -1).tolist()
1643
1644        #ids_veh = np.zeros(n,object)
1645        i = 0
1646        for id_veh, coord in zip(ids_veh, coords):
1647            # print '\n  id_veh,coord',id_veh,coord
1648            # print '    landuse.id_bookedveh_parking',landuse.id_bookedveh_parking
1649            #
1650
1651            # print '    inds_parking_avail',inds_parking_avail
1652            # print '    dists',np.sum((coord-coord_parking[inds_parking_avail])**2,1),np.argmin(np.sum((coord-coord_parking[inds_parking_avail])**2,1))
1653            ind_parking_closest = inds_parking_avail[np.argmin(np.sum((coord-coord_parking[inds_parking_avail])**2, 1))]
1654            # print '  ind_parking_closest,n_avail',ind_parking_closest,len(inds_parking_avail)
1655            inds_vehparking[i] = ind_parking_closest
1656            # print '  id_veh=%s,id_parking_closest=%s, dist =%.2fm'%(id_veh,self.get_ids([ind_parking_closest]),np.sqrt(np.sum((coord-coord_parking[ind_parking_closest])**2)))
1657            # ids_bookedveh[ind_parking_closest]=id_veh # occupy parking
1658
1659            inds_parking_avail.remove(ind_parking_closest)
1660            i += 1
1661
1662        # print '  inds_vehparking',  inds_vehparking
1663        # print '  ids_vehparking',  self.get_ids(inds_vehparking)
1664        # print '  ids_veh',ids_veh
1665        self.ids_bookedveh.value[inds_vehparking] = ids_veh
1666        # self.ids_bookedveh.[ids_parking] =ids_bookedveh
1667        return self.get_ids(inds_vehparking), inds_vehparking
1668
1669
1670class Landuse(cm.BaseObjman):
1671    def __init__(self, scenario=None, net=None,  **kwargs):
1672        self._init_objman(ident='landuse', parent=scenario, name='Landuse', **kwargs)
1673        attrsman = self.set_attrsman(cm.Attrsman(self))
1674
1675        if scenario is not None:
1676            net = scenario.net
1677        # self.net = attrsman.add(   cm.ObjConf( net, is_child = False ) )# link only
1678
1679        self.landusetypes = attrsman.add(cm.ObjConf(LanduseTypes(self)))
1680        self.zones = attrsman.add(cm.ObjConf(Zones(self, net.edges)))
1681        self.facilities = attrsman.add(cm.ObjConf(Facilities(self, self.landusetypes, self.zones, net=net)))
1682        self.parking = attrsman.add(cm.ObjConf(Parking(self, net.lanes)))
1683        self.maps = attrsman.add(cm.ObjConf(maps.Maps(self)))
1684
1685    def update_netoffset(self, deltaoffset):
1686        """
1687        Called when network offset has changed.
1688        Children may need to adjust theur coordinates.
1689        """
1690        self.zones.update_netoffset(deltaoffset)
1691        self.facilities.update_netoffset(deltaoffset)
1692        self.parking.update_netoffset(deltaoffset)
1693        self.maps.update_netoffset(deltaoffset)
1694
1695    def get_net(self):
1696        # parent of landuse must be scenario
1697        if self.parent is not None:
1698            return self.parent.net
1699        else:
1700            return None
1701
1702    def export_polyxml(self, filepath=None, encoding='UTF-8'):
1703        """
1704        Export landuse facilities to SUMO poly.xml file.
1705        """
1706        if len(self.facilities) == 0:
1707            return None
1708
1709        if filepath is None:
1710            if self.parent is not None:
1711                filepath = self.get_filepath()
1712            else:
1713                filepath = os.path.join(os.getcwd(), 'landuse.poly.xml')
1714
1715        print 'export_polyxml', filepath
1716        try:
1717            fd = open(filepath, 'w')
1718        except:
1719            print 'WARNING in export_poly_xml: could not open', filepath
1720            return None
1721
1722        #xmltag, xmltag_item, attrname_id = self.xmltag
1723        xmltag_poly = 'additional'
1724        fd.write('<?xml version="1.0" encoding="%s"?>\n' % encoding)
1725        fd.write(xm.begin(xmltag_poly))
1726        indent = 2
1727
1728        fd.write(xm.start('location', indent+2))
1729        # print '  groups:',self.parent.net.get_attrsman().get_groups()
1730        for attrconfig in self.parent.net.get_attrsman().get_group('location'):
1731            # print '    locationconfig',attrconfig.attrname
1732            attrconfig.write_xml(fd)
1733        fd.write(xm.stopit())
1734
1735        self.facilities.write_xml(fd, indent=indent+2, is_print_begin_end=False)
1736
1737        fd.write(xm.end(xmltag_poly))
1738        fd.close()
1739        return filepath
1740
1741    def get_filepath(self):
1742        return self.parent.get_rootfilepath() + '.poly.xml'
1743
1744    def import_polyxml(self, rootname=None, dirname='', filepath=None):
1745        if filepath is None:
1746            if rootname is not None:
1747                filepath = os.path.join(dirname, rootname+'.poly.xml')
1748            else:
1749                filepath = self.get_filepath()
1750
1751        if os.path.isfile(filepath):
1752            self.facilities.import_poly(filepath)
1753
1754        else:
1755            self.get_logger().w('import_xml: files not found:'+filepath, key='message')
1756
1757        #
1758        # here may be other relevant imports
1759        #
1760
1761
1762class FacilityGenerator(Process):
1763    def __init__(self, ident='facilitygenerator', facilities=None,  logger=None, **kwargs):
1764        print 'FacilityGenerator.__init__'
1765
1766        # TODO: let this be independent, link to it or child??
1767
1768        self._init_common(ident,
1769                          parent=facilities,
1770                          name='Facility Generator',
1771                          logger=logger,
1772                          info='Generates facilities (buildigs, factories, parks, etc.) in a given street network.',
1773                          )
1774
1775        attrsman = self.set_attrsman(cm.Attrsman(self))
1776
1777        # make for each possible pattern a field for prob
1778
1779        self.edgelength_min = attrsman.add(cm.AttrConf('edgelength_min', kwargs.get('edgelength_min', 50.0),
1780                                                       groupnames=['options'],
1781                                                       perm='rw',
1782                                                       name='Minimum edge length',
1783                                                       unit='m',
1784                                                       info="""Minimum edge length for which houses are generated.""",
1785                                                       ))
1786
1787        self.height_max = attrsman.add(cm.AttrConf('height_max', kwargs.get('height_max', 20.0),
1788                                                   groupnames=['options'],
1789                                                   perm='rw',
1790                                                   unit='m',
1791                                                   name='Max facility height',
1792                                                   info="""Maximum height of facilities.""",
1793                                                   ))
1794
1795        self.capacity_max = attrsman.add(cm.AttrConf('capacity_max', kwargs.get('capacity_max', 1000),
1796                                                     groupnames=['options'],
1797                                                     perm='rw',
1798                                                     name='Max. facility capacity',
1799                                                     info="""Maximum capacity of a facility. Capacity is the number of adulds living in a house or working in a factory.""",
1800                                                     ))
1801
1802        self.n_retry = attrsman.add(cm.AttrConf('n_retry', kwargs.get('n_retry', 5),
1803                                                groupnames=['options'],
1804                                                perm='rw',
1805                                                name='Retry number',
1806                                                info="""Number of times the algorithm is trying to fit a facility in a road-gap.""",
1807                                                ))
1808
1809        # self.id_facilitytype = attrsman.add(cm.AttrConf( 'id_facilitytype',kwargs.get('id_facilitytype',1),
1810        #                    groupnames = ['options'],
1811        #                    perm='rw',
1812        #                    choices = self.parent.facilities.facilitytypes.get_value().names.get_indexmap(),
1813        #                    name = 'Facility type',
1814        #                    info = """Facility type to be generated.""",
1815        #                    ))
1816
1817    def do(self):
1818        print self.get_name()+'.do'
1819        # links
1820        facilities = self.parent
1821        net = facilities.parent.get_net()
1822        edges = net.edges
1823        nodes = net.nodes
1824        #self._edges = edges
1825
1826        #self._segvertices = edges.get_segvertices_xy()
1827        x1, y1, x2, y2 = edges.get_segvertices_xy()
1828
1829        logger = self.get_logger()
1830        #logger.w('Provide vehicles...')
1831
1832        ids_edge = edges.select_ids((edges.widths_sidewalk.get_value() > 0)
1833                                    & (edges.lengths.get_value() > self.edgelength_min)
1834                                    )
1835        facilitytypes = facilities.facilitytypes.get_value()
1836
1837        # here we can make a selection
1838        facilitytypeobjs = facilitytypes.typeobjects[facilitytypes.get_ids()]
1839
1840        # print '  facilitytypes, facilitytypeobjs',facilitytypes,facilitytypeobjs
1841        n_factypes = len(facilitytypes)
1842        n_fac = 0
1843        # print '  eligible edges =',ids_edge
1844        for id_edge, edgelength, id_fromnode, id_tonode, shape, edgewidth\
1845                in zip(ids_edge, edges.lengths[ids_edge],
1846                       edges.ids_fromnode[ids_edge],
1847                       edges.ids_tonode[ids_edge],
1848                       edges.shapes[ids_edge],
1849                       edges.widths[ids_edge],
1850                       ):
1851            pos = 5.0
1852            # print '  Build at edge',id_edge,edgelength
1853
1854            # identify opposite edge, which needs to be excluded
1855            # from bulding overlapping check
1856            if (nodes.ids_incoming[id_fromnode] is not None)\
1857                    & (nodes.ids_outgoing[id_tonode] is not None):
1858                ids_incoming_fomnode = set(nodes.ids_incoming[id_fromnode])
1859                ids_outgoing_tonode = set(nodes.ids_outgoing[id_tonode])
1860
1861                id_edge_opp_set = ids_incoming_fomnode.intersection(ids_outgoing_tonode)
1862                if len(id_edge_opp_set) > 0:
1863                    id_edge_opp = id_edge_opp_set.pop()
1864                    inds_seg_opp = edges.get_inds_seg_from_id_edge(id_edge_opp)
1865                else:
1866                    # no edge in opposite direction
1867                    id_edge_opp = -1
1868                    inds_seg_opp = None
1869            else:
1870                id_edge_opp = -1
1871                inds_seg_opp = None
1872            #ids_tonode_outgoing = edges.ids_tonode[nodes.ids_outgoing[id_tonode]]
1873            # net.get_ids_edge_from_inds_seg(inds_seg)
1874            # net.get_inds_seg_from_id_edge(id_edge)
1875
1876            while pos < edgelength:
1877                facilitytype = facilitytypeobjs[0]  # could be according to statistics
1878                # print '    next position',pos
1879                n_trials = self.n_retry
1880                is_success = False
1881                while (n_trials > 0) & (not is_success):
1882                    length_fac = random.uniform(facilitytype.length_min, facilitytype.length_max)
1883                    width_fac = random.uniform(facilitytype.width_min, facilitytype.width_max)
1884
1885                    # fix from to positions
1886                    pos11 = pos
1887                    pos21 = pos + length_fac
1888                    if pos21 < edgelength:
1889                        # print '      try place',n_trials,facilitytype,'id_edge',id_edge,pos11,pos21,edgelength
1890
1891                        coord11, angle = get_coord_angle_on_polyline_from_pos(shape, pos11)
1892                        dxn = np.cos(angle-np.pi/2)
1893                        dyn = np.sin(angle-np.pi/2)
1894                        coord12 = [coord11[0]+width_fac*dxn, coord11[1]+width_fac*dyn, coord11[2]]
1895
1896                        coord21, angle = get_coord_angle_on_polyline_from_pos(shape, pos21)
1897                        dxn = np.cos(angle-np.pi/2)
1898                        dyn = np.sin(angle-np.pi/2)
1899                        coord22 = [coord21[0]+width_fac*dxn, coord21[1]+width_fac*dyn, coord21[2]]
1900
1901                        id_edge1 = edges.get_ids_edge_from_inds_seg(self.get_segind_closest_edge(
1902                            coord12, x1, y1, x2, y2, inds_seg_exclude=inds_seg_opp))
1903
1904                        #id_edge2 = edges.get_ids_edge_from_inds_seg(self.get_segind_closest_edge(coord22, x1,y1,x2,y2, inds_seg_exclude = inds_seg_opp))
1905                        # print '      id_edge,id_edge1,id_edge2',id_edge,id_edge1,id_edge2
1906                        # print '      shape =',np.array([coord11, coord12, coord22, coord21,], dtype = np.float32)
1907                        if id_edge1 == id_edge:
1908                            id_edge2 = edges.get_ids_edge_from_inds_seg(self.get_segind_closest_edge(
1909                                coord22, x1, y1, x2, y2, inds_seg_exclude=inds_seg_opp))
1910
1911                            if id_edge2 == id_edge:
1912                                id_fac = facilities.generate(facilitytype,
1913                                                             offset=coord11,  # offset
1914                                                             length=length_fac,
1915                                                             width=width_fac,
1916                                                             #bbox = [coord11, coord12, coord22, coord21,],
1917                                                             id_landusetype=None,
1918                                                             angle=angle,
1919                                                             pos_edge=pos11,
1920                                                             capacity=self.capacity_max,  # could be function of dist to center/pop
1921                                                             height_max=self.height_max,  # could be function of dist to center
1922                                                             id_edge=id_edge,
1923                                                             width_edge=edgewidth,
1924                                                             )
1925
1926                                if id_fac != -1:
1927                                    # print '    ****generation successful id_fac=',id_fac
1928                                    is_success = True
1929                                    n_fac += 1
1930
1931                    n_trials -= 1
1932
1933                pos = pos21
1934                # print '    update with pos',pos
1935                # generate a parallel shape with distance  width_fac
1936                #angles_perb = get_angles_perpendicular(shape)
1937                #dxn = np.cos(angles_perb)
1938                #dyn = np.sin(angles_perb)
1939                #shape2 = np.zeros(shape.shape, np.float32)
1940                #shape2[:,0] = dxn*width_fac + shape[:,0]
1941                #shape2[:,1] = dyn*width_fac + shape[:,1]
1942                #shape2[:,2] = shape[:,2]
1943
1944                # check if positions on parallel shape are closest to
1945                # this edge or closer to another edge
1946        print '  Done, generated %d facilities' % n_fac
1947        return True
1948
1949    def get_segind_closest_edge(self, p, x1, y1, x2, y2, inds_seg_exclude=None):
1950        d2 = get_dist_point_to_segs(p[0:2], x1, y1, x2, y2, is_ending=True)
1951        if inds_seg_exclude is not None:
1952            d2[inds_seg_exclude] = np.inf
1953        # print '  min(d2)=',np.min(d2),'argmin=',np.argmin(d2),self.get_ids(self._edgeinds[np.argmin(d2)])
1954        return np.argmin(d2)
1955
1956
1957class ParkingGenerator(Process):
1958    def __init__(self, ident='parkinggenerator', parking=None,  logger=None, **kwargs):
1959        print 'ParkingGenerator.__init__'
1960
1961        # TODO: let this be independent, link to it or child??
1962
1963        self._init_common(ident,
1964                          parent=parking,
1965                          name='On Road parking generator',
1966                          logger=logger,
1967                          info='Generates on road parking.',
1968                          )
1969
1970        attrsman = self.set_attrsman(cm.Attrsman(self))
1971        scenario = parking.parent.parent
1972
1973        self.id_mode = attrsman.add(cm.AttrConf('id_mode', kwargs.get('id_mode', MODES['passenger']),
1974                                                groupnames=['options'],
1975                                                choices=scenario.net.modes.names.get_indexmap(),
1976                                                perm='rw',
1977                                                name='Mode ID',
1978                                                info="""Mode of parked vehicles. This is to select lanes which must be accessible for this mode.""",
1979                                                ))
1980
1981        self.length_min = attrsman.add(cm.AttrConf('length_min', kwargs.get('length_min', 42.0),
1982                                                   groupnames=['options'],
1983                                                   perm='rw',
1984                                                   unit='m',
1985                                                   name='Min. edge length',
1986                                                   info="""Minimum edge length in order to qualify for parking.""",
1987                                                   ))
1988
1989        self.length_noparking = attrsman.add(cm.AttrConf('length_noparking', kwargs.get('length_noparking', 6.0),
1990                                                         groupnames=['options'],
1991                                                         perm='rw',
1992                                                         unit='m',
1993                                                         name='No parking length',
1994                                                         info="""Length from junction to the first or last parking on an edge.""",
1995                                                         ))
1996
1997        self.length_lot = attrsman.add(cm.AttrConf('length_lot', kwargs.get('length_lot', 6.0),
1998                                                   groupnames=['options'],
1999                                                   perm='rw',
2000                                                   unit='m',
2001                                                   name='Lot length',
2002                                                   info="""Length of a single parking lot.""",
2003                                                   ))
2004        self.angle = attrsman.add(cm.AttrConf('angle', kwargs.get('angle', 0.0),
2005                                              groupnames=['options'],
2006                                              perm='rw',
2007                                              name='Angle',
2008                                              info="""Angle of parking with respect ti lane direction. Currently only 0.0 is possible.""",
2009                                              ))
2010
2011        self.priority_max = attrsman.add(cm.AttrConf('priority_max', kwargs.get('priority_max', 7),
2012                                                     groupnames=['options'],
2013                                                     perm='rw',
2014                                                     name='Max. priority',
2015                                                     info="""Maximum edge priority where parkings will be created.""",
2016                                                     ))
2017
2018        self.n_freelanes_min = attrsman.add(cm.AttrConf('n_freelanes_min', kwargs.get('n_freelanes_min', 1),
2019                                                        groupnames=['options'],
2020                                                        perm='rw',
2021                                                        name='Min. free lanes',
2022                                                        info="""Minimum number of free lanes on the edge. These is the minimum number of lanes excluding the parking lane.""",
2023                                                        ))
2024        self.is_clear = attrsman.add(cm.AttrConf('is_clear', kwargs.get('is_clear', True),
2025                                                 groupnames=['options'],
2026                                                 perm='rw',
2027                                                 name='Clear',
2028                                                 info="""Clear precious parking areas.""",
2029                                                 ))
2030
2031    def do(self):
2032        print self.get_name()+'.do'
2033        # links
2034        # print '  self.id_mode',self.id_mode
2035        # print '  self.get_kwoptions()',self.get_kwoptions()
2036        self.parent.make_parking(**self.get_kwoptions())
2037        return True
2038
2039
2040class OsmPolyImporter(CmlMixin, Process):
2041    def __init__(self,  landuse=None,
2042                 osmfilepaths=None,
2043                 typefilepath=None,
2044                 polyfilepath=None,
2045                 projparams=None,
2046                 offset_x=None,
2047                 offset_y=None,
2048                 is_keep_full_type=True,
2049                 is_import_all_attributes=True,
2050                 is_use_name_for_id=False,
2051                 polytypefilepath='',
2052                 is_clean_osmfile=True,
2053                 logger=None, **kwargs):
2054        print 'OsmPolyImporter.__init__', landuse, landuse.parent.get_rootfilename()
2055        self._init_common('osmpolyimporter', name='OSM Poly import',
2056                          logger=logger,
2057                          info='Converts a OSM  file to a SUMO Poly file and read facilities into scenario.',
2058                          )
2059        if landuse is None:
2060            self._landuse = Landuse()
2061        else:
2062            self._landuse = landuse
2063
2064        self.init_cml('polyconvert')  # pass main shell command
2065
2066        if landuse.parent is not None:
2067            scenario = landuse.parent
2068            rootname = scenario.get_rootfilename()
2069            rootdirpath = scenario.get_workdirpath()
2070            if hasattr(scenario, 'net'):
2071                if projparams is None:
2072                    projparams = scenario.net.get_projparams()
2073                if (offset_x is None) & (offset_y is None):
2074                    offset_x, offset_y = scenario.net.get_offset()
2075        else:
2076            rootname = landuse.get_ident()
2077            rootdirpath = os.getcwd()
2078
2079        if polyfilepath is None:
2080            polyfilepath = os.path.join(rootdirpath, rootname+'.poly.xml')
2081
2082        if osmfilepaths is None:
2083            osmfilepaths = os.path.join(rootdirpath, rootname+'.osm.xml')
2084
2085        if typefilepath is None:
2086            typefilepath = os.path.join(os.path.dirname(os.path.abspath(__file__)),
2087                                        '..', '..', 'typemap', 'osmPolyconvert.typ.xml')
2088        attrsman = self.get_attrsman()
2089
2090        self.workdirpath = rootdirpath
2091
2092        self.rootname = rootname
2093
2094        self.add_option('osmfilepaths', osmfilepaths,
2095                        groupnames=['options'],
2096                        cml='--osm-files',
2097                        perm='rw',
2098                        name='OSM files',
2099                        wildcards='OSM XML files (*.osm)|*.osm*',
2100                        metatype='filepaths',
2101                        info='Openstreetmap files to be imported.',
2102                        )
2103
2104        self.is_clean_osmfile = attrsman.add(cm.AttrConf('is_clean_osmfile', is_clean_osmfile,
2105                                                         groupnames=['options'],
2106                                                         perm='rw',
2107                                                         name='Clean OSM files',
2108                                                         info='If set, OSM files are cleaned from strange characters prior to import (recommended).',
2109                                                         ))
2110
2111        self.add_option('polyfilepath', polyfilepath,
2112                        groupnames=['_private'],
2113                        cml='--output-file',
2114                        perm='r',
2115                        name='Poly file',
2116                        wildcards='Poly XML files (*.poly.xml)|*.poly.xml',
2117                        metatype='filepath',
2118                        info='SUMO Poly file in XML format.',
2119                        )
2120
2121        self.add_option('typefilepath', typefilepath,
2122                        groupnames=['options'],
2123                        cml='--type-file',
2124                        perm='rw',
2125                        name='Type file',
2126                        wildcards='Typemap XML files (*.typ.xml)|*.typ.xml',
2127                        metatype='filepath',
2128                        info="""Typemap XML files. In these file,
2129OSM building types are mapped to specific facility parameters, is not explicitely set by OSM attributes.""",
2130                        )
2131
2132        # --net-file <FILE> 	Loads SUMO-network FILE as reference to offset and projection
2133        self.add_option('projparams', projparams,
2134                        groupnames=['options'],
2135                        cml='--proj',
2136                        perm='rw',
2137                        name='projection',
2138                        info='Uses STR as proj.4 definition for projection. Default is the projection of the network, better do not touch!',
2139                        is_enabled=lambda self: self.projparams is not None,
2140                        )
2141
2142        self.add_option('offset_x', offset_x,
2143                        groupnames=['options', 'geometry'],
2144                        cml='--offset.x ',
2145                        perm='rw',
2146                        unit='m',
2147                        name='X-Offset',
2148                        info='Adds offset to net x-positions; default: 0.0',
2149                        is_enabled=lambda self: self.offset_x is not None,
2150                        )
2151        self.add_option('offset_y', offset_y,
2152                        groupnames=['options', 'geometry'],
2153                        cml='--offset.y ',
2154                        perm='rw',
2155                        unit='m',
2156                        name='Y-Offset',
2157                        info='Adds offset to net x-positions; default: 0.0',
2158                        is_enabled=lambda self: self.offset_y is not None,
2159                        )
2160
2161        self.add_option('is_keep_full_type', is_keep_full_type,
2162                        groupnames=['options'],
2163                        cml='--osm.keep-full-type',
2164                        perm='rw',
2165                        name='keep full OSM type',
2166                        info='The type will be made of the key-value - pair.',
2167                        )
2168
2169        self.add_option('is_import_all_attributes', is_keep_full_type,
2170                        groupnames=['options'],
2171                        cml='--all-attributes',
2172                        perm='rw',
2173                        name='import all attributes',
2174                        info='Imports all OSM attributes.',
2175                        )
2176
2177        self.add_option('is_use_name_for_id', is_use_name_for_id,
2178                        groupnames=['options'],
2179                        cml='--osm.use-name',
2180                        perm='rw',
2181                        name='use OSM name for id',
2182                        info=' 	The OSM id (not internal ID) will be set from the given OSM name attribute.',
2183                        )
2184
2185        self.add_option('polytypefilepath', polytypefilepath,
2186                        groupnames=[],  # ['_private'],#
2187                        cml='--type-file',
2188                        perm='rw',
2189                        name='Poly type file',
2190                        wildcards='Net XML files (*.xml)|*.xml',
2191                        metatype='filepath',
2192                        info='SUMO Poly type file in XML format.',
2193                        is_enabled=lambda self: self.polytypefilepath != '',
2194                        )
2195
2196    def update_params(self):
2197        """
2198        Make all parameters consistent.
2199        example: used by import OSM to calculate/update number of tiles
2200        from process dialog
2201        """
2202        pass
2203        #self.workdirpath = os.path.dirname(self.netfilepath)
2204        #bn =  os.path.basename(self.netfilepath).split('.')
2205        # if len(bn)>0:
2206        #    self.rootname = bn[0]
2207
2208    def do(self):
2209        self.update_params()
2210        cml = self.get_cml()
2211
2212        if self.is_clean_osmfile:
2213            for path in self.osmfilepaths.split(','):
2214                path_temp = path+'.clean'
2215                clean_osm(path, path_temp)
2216                #shutil.copy (path_temp, path)
2217                shutil.move(path_temp, path)
2218
2219        # print 'SumonetImporter.do',cml
2220        #import_xml(self, rootname, dirname, is_clean_nodes = True)
2221        self.run_cml(cml)
2222        if self.status == 'success':
2223            if os.path.isfile(self.polyfilepath):
2224                print '  OSM->poly.xml successful, start importing xml files'
2225                self._landuse.import_polyxml(self.rootname, self.workdirpath)
2226                print '  import poly in sumopy done.'
2227                return True
2228            return False
2229        else:
2230            return False
2231
2232    def get_landuse(self):
2233        # used to het landuse in case landuse has been created
2234        return self._landuse
2235
2236
2237if __name__ == '__main__':
2238    ###############################################################################
2239    # print 'sys.path',sys.path
2240    from agilepy.lib_wx.objpanel import objbrowser
2241    from agilepy.lib_base.logger import Logger
2242    #from coremodules.scenario import scenario
2243    from coremodules.network import network
2244    logger = Logger()
2245    NETPATH = os.path.join(SUMOPYDIR, 'coremodules', 'network', 'testnet')
2246    net = network.Network(logger=logger)
2247    rootname = 'facsp2'
2248    net.import_xml(rootname, NETPATH)
2249    # net.read_sumonodes(os.path.join(NETPATH,'facsp2.nod.xml'))
2250    # net.read_sumoedges(os.path.join(NETPATH,'facsp2.edg.xml'))
2251    landuse = Landuse(net=net, logger=logger)
2252
2253    # landuse.facilities.import_poly(os.path.join(NETPATH,'facsp2.poly.xml'))
2254    landuse.import_xml(rootname, NETPATH)
2255    objbrowser(landuse)
2256