Esempio n. 1
0
 def get_partitions(self):
     """ Returns the instance of an L{app.partition.PartitionFinder} object that stores the partitions of the OSM data representation.
     
     @returns: an instance of an L{app.partition.PartitionFinder} object        
     @rtype: L{app.partition.PartitionFinder}
     """
     if not self.__partitions:
         self.__partitions = PartitionFinder(self)
     return self.__partitions
Esempio n. 2
0
 def recalculate_partitions(self):
     """ Initialized the recalculation of the partitions of the OSM data representation.
     
     Resets all partition information and starts a new partition finding process.
     """
     if not self.__partitions:
         self.__partitions = PartitionFinder(self)
     else:
         self.__partitions.reset_partitions()
         self.__partitions.find_partitions()
Esempio n. 3
0
class OSM_objects(object):
    """ The class OSM_objects is the OSM data representation of the MoSP-GeoTool.
    
    The class provides methods and data structures to create, delete and store OSM objects.
    """
    def __init__(self, infile):
        """
        
        @param infile: path to the OSM-file
        """
        
        self.__infile = infile	#: path to the OSM-file
        
        # raw data received from the callback functions
        self.__imported_bounds = [] #: stores the bounding box as given in the OSM file as a list [min_lon, min_lat, max_lon, max_lat]
        self.__nodes = []
        self.__ways = []
        self.__relations = {}
        

        self.__parser_object = OSMParser(bounds_callback=self.__receive_bounds,
                                         nodes_callback=self.__receive_nodes,
                                         ways_callback=self.__receive_ways,
                                         relations_callback=self.__receive_relations) #: stores the instance of an OSMParser-object

        self.__min_lon = self.__min_lat = 1e400
        self.__max_lon = self.__max_lat = -1e400
        self.__calculated_bounds = [] #: stores the bounding box as it is calculated during the creation of the L{geo.osm_import.Way} objects as a list [min_lon, min_lat, max_lon, max_lat]
        
        #self.__node_tree = None
        self.__node_avl = AVLTree() #: instance of AVL-tree-object that stores L{Node} objects
        
        self.__street_tree = index.Index(properties=index.Property()) #: instance of R-tree-object that stores L{geo.osm_import.Way} objects that are tagged as streets
        self.__building_tree = index.Index(properties=index.Property()) #: instance of R-tree-object that stores L{geo.osm_import.Way} objects that are tagged as buildings
        self.__way_delete = [] # way objects that are tagged as deleted, only needed for a complete export
        self.__other_ways = [] # all other way objects that aren't streets, buildings or deleted, only needed for a complete export
        self.__way_avl = AVLTree() #: instance of AVL-tree-object that stores L{Way} objects
        
        self.__poi = set() #: stores a set of L{Node} objects that are selected as POI
        self.__generalized = set() #: stores the tolerance values of previously performed generalizations as a set 
        self.__partitions = None #: stores an instance of an L{app.partition.PartitionFinder} object
        

    #def getNodeTree(self):
    #    return self.__node_tree
    #node_tree = property(getNodeTree)

    def get_node_objects(self):
        """ Returns a list of all L{Node} objects
        
        @returns: a list of all L{Node} objects
        @rtype: C{list} of L{Node}
        """
        return [node for node in self.__node_avl.values()]
    node_objects = property(get_node_objects, None, None, 'read-only property for a list of all L{Node} objects')

    def getStreetTree(self):
        """ Returns the R-tree-object that stores the streets
        
        @returns: the R-tree-object that stores the streets
        @rtype: C{rtree.index.Index}
         """
        return self.__street_tree
    street_tree = property(getStreetTree, None, None, 'read-only property for the R-tree-object that stores the streets')

    def getBuildingTree(self):
        """ Returns the R-tree-object that stores the buildings
        
        @returns: the R-tree-object that stores the buildings
        @rtype: C{rtree.index.Index}
        """
        return self.__building_tree
    building_tree = property(getBuildingTree, None, None, 'read-only property for the R-tree-object that stores the buildings')
    
    def get_other_ways(self):
        """ Returns a list of L{Way} objects that are neither streets nor buildings
        
        @returns: a list of L{Way} objects that are neither streets nor buildings
        @rtype: C{list} of L{Way}
        """
        return self.__other_ways
    
    def get_relations(self):
        """ Returns a dictionary with osm_id as key and raw representation of a relation as it was imported as value
        
        @returns: {osm_id1:relation1, osm_id2:relation2, ...}
        @rtype: C{dictionary}
        """
        return self.__relations
    
    def getWayByID(self, index):
        """ Gets for an given OSM id the corresponding L{Way} object
        
        @type index: C{int}
        @param index: OSM id
        @returns: corresponding L{Way} object, C{None} if there is no such object
        @rtype: L{Way}
        """
        return self.__way_avl.get(index)
    
    def getWayDelete(self):
        """ Gets the OSM ways that are marked as 'deleted' in the original OSM file
        
        @returns: a list of OSM ids that belong to deleted ways
        @rtype: C{list} of C{int}
        """
        return self.__way_delete
    way_delete = property(getWayDelete, None, None, 'read-only property for the OSM ways that are marked as "deleted" in the original OSM file')
    
    def getBox(self):
        """ Returns the calculated bounding box of the OSM data representation
        
        @returns: the calculated bounding box of the OSM data representation
        @rtype: C{[min_lon, min_lat, max_lon, max_lat]}
        """
        return self.__calculated_bounds
    box = property(getBox, None, None, 'read-only property for the calculated bounding box of the OSM data representation')
    
    def getBounds(self):
        """ Returns the imported bounding box of the OSM data representation
        
        @returns: the imported bounding box of the OSM data representation
        @rtype: C{[min_lon, min_lat, max_lon, max_lat]}
        """
        return self.__imported_bounds
    bounds = property(getBounds, None, None, 'read-only property for the imported bounding box of the OSM data representation')

    def add_poi(self, poi_node):
        """ Adds a L{Node} object to the set of points of interest
        
        @type poi_node: L{Node}
        @param poi_node: L{Node}
        """
        self.__poi.add(poi_node)
    def get_poi(self):
        """ Returns the set of points of interests
        
        @returns: the set of points of interests
        @rtype: C{set} of L{Node}
        """
        return self.__poi

    def get_generalized(self):
        """ Returns the tolerance values of previously performed generalizations as a set
        
        @returns: the tolerance values of previously performed generalizations as a set
        @rtype: C{set} of C{int}
        """
        return self.__generalized
    generalized = property(get_generalized, None, None, 'read-only property for the tolerance values of previously performed generalizations')
    
    def reset_generalized(self):
        """
        Clears the set of previously performed generalizations
        """
        self.__generalized = set()
        
    def get_utm_projection(self):
        """ Returns an instance of a pyproj.Proj object which uses the UTM projection
        
        @returns: an instance of a pyproj.Proj object which uses the UTM projection
        @rtype: C{pyproj.Proj}
        """
        return self.__utm_projection
    
    def get_osm_projection(self):
        """ Returns an instance of a C{pyproj.Proj} object which uses epsg:3857-projection (the projection of OSM tiles)
        
        @returns: an instance of a C{pyproj.Proj} object which uses epsg:3857-projection
        @rtype: C{pyproj.Proj}
        """
        return self.__osm_projection
    
    def get_adjacent_streets(self, node, threshold):
        """ Returns a list of street objects within the threshold distance to the given node.
        
        A rectangular box with the node as the center is created. The threshold in meters is the distance from the center to the top/left/bottom/right. All street object that lie within this box are returned.
        
        @type node: L{Node}
        @param node: OSM Node object
        @type threshold: C{int}
        @param threshold: distatance in meters
        @returns: a list of street objects
        @rtype: C{list} of L{Way}
        
        """
        box = create_node_box(node, threshold)
        streets = [self.getWayByID(index) for index in self.street_tree.intersection(box, "raw")]
        return streets
        
    def get_partitions(self):
        """ Returns the instance of an L{app.partition.PartitionFinder} object that stores the partitions of the OSM data representation.
        
        @returns: an instance of an L{app.partition.PartitionFinder} object        
        @rtype: L{app.partition.PartitionFinder}
        """
        if not self.__partitions:
            self.__partitions = PartitionFinder(self)
        return self.__partitions

    def recalculate_partitions(self):
        """ Initialized the recalculation of the partitions of the OSM data representation.
        
        Resets all partition information and starts a new partition finding process.
        """
        if not self.__partitions:
            self.__partitions = PartitionFinder(self)
        else:
            self.__partitions.reset_partitions()
            self.__partitions.find_partitions()



    ########################################
    # callback functions of the OSM parser #
    ########################################

    def __receive_bounds(self, bounds):
        """ Callback function of the OSM parser for the bounding box

	Imports the bounding box of the parsed OSM file. 

        @type bounds: [min_lon, min_lat, max_lon, max_lat]
        @param bounds: Bounding box of the OSM file
        """
        self.__imported_bounds = bounds

    def __receive_nodes(self, nodes):
        """ Callback function of the OSM parser for the OSM nodes
        
        Imports the nodes of the parsed OSM file. 'nodes' is a list of node parameters.
        	- node parameter: (osm_id, tags, (lon, lat), attr)
        	- tags: {tag_key1:tag_value1, tag_key2:tag_value2, ...}
        	- attr: {attr_name1:attr_val1, attr_name2:attr_val2, ...}
        
        The method also determines the min/max coordinates of the calculated bounding box. 
        
        @param nodes: list of node parameters
        """
        for node in nodes:
            self.__nodes.append(node)
            osm_id, tags, (lon, lat), attr = node
            
            # find the min/max coordinates to calculate the bounding box
            if lon < self.__min_lon: self.__min_lon = lon
            if lon > self.__max_lon: self.__max_lon = lon
            if lat < self.__min_lat: self.__min_lat = lat
            if lat > self.__max_lat: self.__max_lat = lat  
                
    def __receive_ways(self, ways):
        """ Callback function of the OSM parser for the OSM ways
        
        Imports the ways of the parsed OSM file. 'ways' is a list of way parameters.
        	- way parameter: (osm_id, tags, refs, attr)
        	- tags: {tag_key1:tag_value1, tag_key2:tag_value2, ...}
        	- refs: [ref_id1, ref_id2, ...]
        	- attr: {attr_name1:attr_val1, attr_name2:attr_val2, ...}
        
        @param ways: list of way parameters
        """
        self.__ways.extend(ways)

    def __receive_relations(self, relations):
        """ Callback function of the OSM parser for the OSM relations
 
        Imports the relations of the parsed OSM file. 'relations' is a list of relation parameters.
        	- relation parameter: (osm_id, tags, members, attr)
        	- tags: {tag_key1:tag_value1, tag_key2:tag_value2, ...}
        	- members = [(refID1, type1, role1), (refID2, type2, role2), ...]
        	- attr: {attr_name1:attr_val1, attr_name2:attr_val2, ...}
        
        @param relations: list of relation parameters
        """
        for relation in relations:
            osm_id = relation[0]
            self.__relations.setdefault(osm_id, relation)



    def __create_nodes(self):
        """ Creates the L{Node} objects from the imported node parameters        
        """
        for osm_id, tags, coord, attr in self.__nodes:
            nd = Node(osm_id=osm_id, lon=coord[0], lat=coord[1], tags=tags, attr=attr, osm_object=self)
        
            # insert the created node object into the avl tree
            # osm_id as tree node key and the node object as tree node item
            # used for look up of a node object by its osm_id
            self.__node_avl.insert(osm_id, nd)

    def __create_ways(self):
        """ Creates the L{Way} objects from the imported way parameters
        """
        for osm_id, tags, nodes, attr in self.__ways:
            way = Way(osm_id, nodes, tags, attr, self.__node_avl)
            
            # don't insert ways which are marked as 'deleted' into the r-tree
            # they don't have nodes/coordinates
            if not attr.get('action') == 'delete':
            
                # insert the streets into the R-tree
                if 'highway' in tags:
                    
                    # only the osm_id is inserted
                    # if we insert a way object a copy of the object would be inserted
                    # but we need references!
                    # --> the AVL tree __way_avl is used to look up the way object by its osm_id
                    self.__street_tree.insert(osm_id, way.box, osm_id)
            
                # insert the buildings into th R-tree
                elif tags.get('building') == 'yes':
                    self.__building_tree.insert(osm_id, way.box, osm_id)
            
                # store the ids of the other ways (needed for complete export)
                else:
                    self.__other_ways.append(osm_id)
            
            else:
                # keep the deleted ways for a complete export
                self.__way_delete.append(osm_id)
            
            # insert the created way object into the avl tree
            # osm_id as tree node key and the way object as tree node item
            # used for look up of a way object by its osm_id
            self.__way_avl.insert(osm_id, way)

    def __create_bounds(self):
        """ Creates the calculated bounding box and the C{pyproj.Proj} objects for UTM- and epsg:3857-projection
        """
        self.__calculated_bounds.extend([self.__min_lon, self.__min_lat,
                                         self.__max_lon, self.__max_lat])

        # if there is no bound/bounds element in the imported OSM file
        # use the calculated box as imported box
        if not self.__imported_bounds:
            self.__imported_bounds = self.__calculated_bounds
        
        # determine the utm zone by the left side of the bounding box 
        utm_zone = long_to_zone(self.__min_lon)
        self.__utm_projection = Proj(proj='utm', zone=utm_zone, ellps='WGS84') #: Stores an instance of a C{pyproj.Proj} object which uses UTM projection, the UTM zone is determined by the left side of the calculated bounding box.
        self.__osm_projection = Proj(init='epsg:3857') #: Stores an instance of a C{pyproj.Proj} object which uses epsg:3857-projection (the projection of OSM tiles).

    def insert_new_node(self, lat, lon, tags, attr):
        """ Inserts a new L{Node} object into the OSM data representation
        
        @param lat: Geographic latitude of the new node
        @param lon: Geographic longitude of the new node
        @param tags: dictionary of OSM tag key/value pairs
        @param attr: dictionary of OSM attribute key/value pairs
        """
        osm_id = self.find_new_key()
        attr.setdefault('version', '1')
        nd = Node(osm_id=osm_id, lon=lon, lat=lat, tags=tags, attr=attr, osm_object=self)
        self.__node_avl.insert(osm_id, nd)
        return nd

    def append_new_street(self, tags, nodes, attr):
        """ Inserts a new L{Way} object into the OSM data representation
        
        @param tags: dictionary of OSM tag key/value pairs
        @param nodes: list of osm_ids of the referencing nodes
        @param attr: dictionary of OSM attribute key/value pairs
        """
        osm_id = self.find_new_key()
        attr.setdefault('version', '1')
        way = Way(osm_id, nodes, tags, attr, self.__node_avl)
        bounding_box = way.box
        self.__street_tree.insert(osm_id, bounding_box, osm_id)
        self.__way_avl.insert(osm_id, way)
        return way

    def delete_way(self, way):
        """ Removes a L{Way} object from the OSM data representation
        
        @type way: L{Way}
        @param way: OSM Way object
        """
        self.__street_tree.delete(way.getID(), way.box)
        self.__way_avl.remove(way.getID())

    def remove_unused_nodes(self):
        """ Removes all L{Node} that are not referenced by any way or relation
        and that don't have tags from the OSM data representation 
        """
        all_nodes = set()
        referenced_nodes = set()
        tagged_nodes = set()
        
        for node in self.node_objects:
            all_nodes.add(node)
            if node.getTags():
                tagged_nodes.add(node)
        
        streets = [self.getWayByID(index) for index in self.street_tree.intersection(self.box, "raw")]
        buildings = [self.getWayByID(index) for index in self.building_tree.intersection(self.box, "raw")]
    
        # look in the streets for references
        for street in streets:
            for node in street.nodes:
                referenced_nodes.add(node)
    
        # look in the buildings for references
        for building in buildings:
            for node in building.nodes:
                referenced_nodes.add(node)
        
        # look in all other way objects for references
        for way_id in self.get_other_ways():
            for node in self.getWayByID(way_id).nodes:
                referenced_nodes.add(node)
        
        # look in the relations for references
        for relation in self.get_relations().itervalues():
            rel_id, rel_tags, rel_members, rel_attributes = relation
            for memb_ref, memb_type, memb_role in rel_members:
                if memb_type == 'node':
                    referenced_nodes.add(self.__node_avl.get(memb_ref))
                    
        print 'total nodes #: %i' % len(all_nodes)
        
        # use the difference of the sets to determine the unused nodes
        unused_nodes = (all_nodes - tagged_nodes) - referenced_nodes
        
        print '%i unused nodes are removed' % len(unused_nodes)
        
        # finally, remove the unused nodes
        for node in unused_nodes:
            self.__node_avl.remove(node.node_id)
    
    def find_new_key(self):
        """ Finds a new unused OSM ID
        
        If there are only positive OSM IDs the new ID will be '-1'.
        If there are already negative OSM IDs the new ID will be the smallest ID so far minus 1
        
        @returns: the new OSM id
        @rtype: C{int} 
        """
        if not self.__relations:
            keys = [self.__node_avl.min_key(),
                    self.__node_avl.max_key(),
                    self.__way_avl.min_key(),
                    self.__way_avl.max_key()]
        else:
            keys = [self.__node_avl.min_key(),
                    self.__node_avl.max_key(),
                    self.__way_avl.min_key(),
                    self.__way_avl.max_key(),
                    min(self.__relations.keys()),
                    max(self.__relations.keys())]
        if min(keys) >= 0:
            osm_id = -1
        else:
            osm_id = min(keys) - 1
        return osm_id



    def parse(self):
        """ Initializes the parsing of the OSM file.
        
        Starts the methods to parse the OSM file, to calculate the bounding box and to create the L{geo.osm_import.Node} and L{geo.osm_import.Way} objects
        """
        # TODO: remove profiling message
        
        self.__parser_object.parse(self.__infile)
        
        self.__create_bounds()
        
        #starttime = datetime.datetime.today()
        #print 'start: %s' % starttime
        self.__create_nodes()
        #finishedtime = datetime.datetime.today()
        #print 'finished: %s' % finishedtime
        #print finishedtime - starttime
        
        self.__create_ways()