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()
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()