def run(self): self.update_widget.setVisible(True) self.resize(280, 300) if self.choose_canvas.isChecked(): self.report.append( reporter('Chose to download network for canvas area')) e = self.iface.mapCanvas().extent() bbox = [e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum()] else: self.progress_label.setText('Establishing area for download') self.report.append(reporter('Chose to download network for place')) bbox, r = placegetter(self.place.text()) self.report.extend(r) if bbox is None: self.leave() return west, south, east, north = bbox[0], bbox[1], bbox[2], bbox[3] self.report.append( reporter( 'Downloading network for bounding box ({} {}, {}, {})'.format( west, south, east, north))) self.bbox = bbox surveybox = QgsRectangle(QgsPointXY(west, south), QgsPointXY(east, north)) geom = QgsGeometry().fromRect(surveybox) conv = QgsDistanceArea() area = conv.convertAreaMeasurement(conv.measureArea(geom), QgsUnitTypes.AreaSquareMeters) self.report.append( reporter( 'Area for which we will download a network: {:,} km.sq'.format( area / 1000000))) if area <= max_query_area_size: geometries = [[west, south, east, north]] else: parts = math.ceil(area / max_query_area_size) horizontal = math.ceil(math.sqrt(parts)) vertical = math.ceil(parts / horizontal) dx = east - west dy = north - south geometries = [] for i in range(horizontal): xmin = west + i * dx xmax = west + (i + 1) * dx for j in range(vertical): ymin = south + j * dy ymax = south + (j + 1) * dy box = [xmin, ymin, xmax, ymax] geometries.append(box) p = Parameters().parameters modes = [list(k.keys())[0] for k in p['network']['modes']] self.progress_label.setText('Downloading data') self.downloader = OSMDownloader(geometries, modes) self.run_download_thread()
def create_from_osm( self, west: float = None, south: float = None, east: float = None, north: float = None, place_name: str = None, modes=["car", "transit", "bicycle", "walk"], spatial_index=False, ) -> None: if self._check_if_exists(): raise FileExistsError("You can only import an OSM network into a brand new model file") self.create_empty_tables() curr = self.conn.cursor() curr.execute("""ALTER TABLE links ADD COLUMN osm_id integer""") curr.execute("""ALTER TABLE nodes ADD COLUMN osm_id integer""") self.conn.commit() if isinstance(modes, (tuple, list)): modes = list(modes) elif isinstance(modes, str): modes = [modes] else: raise ValueError("'modes' needs to be string or list/tuple of string") if place_name is None: if min(east, west) < -180 or max(east, west) > 180 or min(north, south) < -90 or max(north, south) > 90: raise ValueError("Coordinates out of bounds") bbox = [west, south, east, north] else: bbox, report = placegetter(place_name) west, south, east, north = bbox if bbox is None: msg = f'We could not find a reference for place name "{place_name}"' warn(msg) logger.warn(msg) return for i in report: if "PLACE FOUND" in i: logger.info(i) # Need to compute the size of the bounding box to not exceed it too much height = haversine((east + west) / 2, south, (east + west) / 2, north) width = haversine(east, (north + south) / 2, west, (north + south) / 2) area = height * width if area < max_query_area_size: polygons = [bbox] else: polygons = [] parts = math.ceil(area / max_query_area_size) horizontal = math.ceil(math.sqrt(parts)) vertical = math.ceil(parts / horizontal) dx = east - west dy = north - south for i in range(horizontal): xmin = max(-180, west + i * dx) xmax = min(180, west + (i + 1) * dx) for j in range(vertical): ymin = max(-90, south + j * dy) ymax = min(90, south + (j + 1) * dy) box = [xmin, ymin, xmax, ymax] polygons.append(box) logger.info("Downloading data") self.downloader = OSMDownloader(polygons, modes) self.downloader.doWork() logger.info("Building Network") self.builder = OSMBuilder(self.downloader.json, self.conn) self.builder.doWork() if spatial_index: logger.info("Adding spatial indices") self.add_spatial_index() self.add_triggers() logger.info("Network built successfully")
class Network(WorkerThread): req_link_flds = ["link_id", "a_node", "b_node", "direction", "distance", "modes", "link_type"] req_node_flds = ["node_id", "is_centroid"] protected_fields = ['ogc_fid', 'geometry'] def __init__(self, project): WorkerThread.__init__(self, None) self.conn = project.conn # type: sqlc self.graphs = {} # type: Dict[Graph] def _check_if_exists(self): curr = self.conn.cursor() curr.execute("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='links';") tbls = curr.fetchone()[0] return tbls > 0 # TODO: DOCUMENT THESE FUNCTIONS def skimmable_fields(self): curr = self.conn.cursor() curr.execute('PRAGMA table_info(links);') field_names = curr.fetchall() ignore_fields = ['ogc_fid', 'geometry'] + self.req_link_flds skimmable = ['INT', 'INTEGER', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT', 'UNSIGNED BIG INT', 'INT2', 'INT8', 'REAL', 'DOUBLE', 'DOUBLE PRECISION', 'FLOAT', 'DECIMAL', 'NUMERIC'] all_fields = [] for f in field_names: if f[1] in ignore_fields: continue for i in skimmable: if i in f[2].upper(): all_fields.append(f[1]) break all_fields.append('distance') return all_fields def modes(self): curr = self.conn.cursor() curr.execute("""select mode_id from modes""") return [x[0] for x in curr.fetchall()] def create_from_osm( self, west: float = None, south: float = None, east: float = None, north: float = None, place_name: str = None, modes=["car", "transit", "bicycle", "walk"], spatial_index=False, ) -> None: if self._check_if_exists(): raise FileExistsError("You can only import an OSM network into a brand new model file") self.create_empty_tables() curr = self.conn.cursor() curr.execute("""ALTER TABLE links ADD COLUMN osm_id integer""") curr.execute("""ALTER TABLE nodes ADD COLUMN osm_id integer""") self.conn.commit() if isinstance(modes, (tuple, list)): modes = list(modes) elif isinstance(modes, str): modes = [modes] else: raise ValueError("'modes' needs to be string or list/tuple of string") if place_name is None: if min(east, west) < -180 or max(east, west) > 180 or min(north, south) < -90 or max(north, south) > 90: raise ValueError("Coordinates out of bounds") bbox = [west, south, east, north] else: bbox, report = placegetter(place_name) west, south, east, north = bbox if bbox is None: msg = f'We could not find a reference for place name "{place_name}"' warn(msg) logger.warn(msg) return for i in report: if "PLACE FOUND" in i: logger.info(i) # Need to compute the size of the bounding box to not exceed it too much height = haversine((east + west) / 2, south, (east + west) / 2, north) width = haversine(east, (north + south) / 2, west, (north + south) / 2) area = height * width if area < max_query_area_size: polygons = [bbox] else: polygons = [] parts = math.ceil(area / max_query_area_size) horizontal = math.ceil(math.sqrt(parts)) vertical = math.ceil(parts / horizontal) dx = east - west dy = north - south for i in range(horizontal): xmin = max(-180, west + i * dx) xmax = min(180, west + (i + 1) * dx) for j in range(vertical): ymin = max(-90, south + j * dy) ymax = min(90, south + (j + 1) * dy) box = [xmin, ymin, xmax, ymax] polygons.append(box) logger.info("Downloading data") self.downloader = OSMDownloader(polygons, modes) self.downloader.doWork() logger.info("Building Network") self.builder = OSMBuilder(self.downloader.json, self.conn) self.builder.doWork() if spatial_index: logger.info("Adding spatial indices") self.add_spatial_index() self.add_triggers() logger.info("Network built successfully") def create_empty_tables(self) -> None: curr = self.conn.cursor() # Create the links table p = Parameters() fields = p.parameters["network"]["links"]["fields"] sql = """CREATE TABLE 'links' ( ogc_fid INTEGER PRIMARY KEY, link_id INTEGER UNIQUE, a_node INTEGER, b_node INTEGER, direction INTEGER NOT NULL DEFAULT 0, distance NUMERIC, modes TEXT NOT NULL, link_type TEXT NOT NULL DEFAULT 'link type not defined' {});""" flds = fields["one-way"] # returns first key in the dictionary def fkey(f): return list(f.keys())[0] owlf = ["{} {}".format(fkey(f), f[fkey(f)]["type"]) for f in flds if fkey(f).lower() not in self.req_link_flds] flds = fields["two-way"] twlf = [] for f in flds: nm = fkey(f) tp = f[nm]["type"] twlf.extend([f"{nm}_ab {tp}", f"{nm}_ba {tp}"]) link_fields = owlf + twlf if link_fields: sql = sql.format("," + ",".join(link_fields)) else: sql = sql.format("") curr.execute(sql) sql = """CREATE TABLE 'nodes' (ogc_fid INTEGER PRIMARY KEY, node_id INTEGER UNIQUE NOT NULL, is_centroid INTEGER NOT NULL DEFAULT 0 {});""" flds = p.parameters["network"]["nodes"]["fields"] ndflds = [f"{fkey(f)} {f[fkey(f)]['type']}" for f in flds if fkey(f).lower() not in self.req_node_flds] if ndflds: sql = sql.format("," + ",".join(ndflds)) else: sql = sql.format("") curr.execute(sql) curr.execute("""SELECT AddGeometryColumn( 'links', 'geometry', 4326, 'LINESTRING', 'XY' )""") curr.execute("""SELECT AddGeometryColumn( 'nodes', 'geometry', 4326, 'POINT', 'XY' )""") self.conn.commit() def build_graphs(self) -> None: curr = self.conn.cursor() curr.execute('PRAGMA table_info(links);') field_names = curr.fetchall() ignore_fields = ['ogc_fid', 'geometry'] all_fields = [f[1] for f in field_names if f[1] not in ignore_fields] raw_links = curr.execute(f"select {','.join(all_fields)} from links").fetchall() links = [] for l in raw_links: lk = list(map(lambda x: np.nan if x is None else x, l)) links.append(lk) # links = data = np.core.records.fromrecords(links, names=all_fields) valid_fields = [] removed_fields = [] for f in all_fields: if np.issubdtype(data[f].dtype, np.floating) or np.issubdtype(data[f].dtype, np.integer): valid_fields.append(f) else: removed_fields.append(f) if len(removed_fields) > 1: warn(f'Fields were removed form Graph for being non-numeric: {",".join(removed_fields)}') curr.execute('select node_id from nodes where is_centroid=1;') centroids = np.array([i[0] for i in curr.fetchall()]) modes = curr.execute('select mode_id from modes;').fetchall() modes = [m[0] for m in modes] for m in modes: w = np.core.defchararray.find(data['modes'], m) net = np.array(data[valid_fields], copy=True) net['b_node'][w < 0] = net['a_node'][w < 0] g = Graph() g.mode = m g.network = net g.network_ok = True g.status = 'OK' g.prepare_graph(centroids) g.set_blocked_centroid_flows(True) self.graphs[m] = g def set_time_field(self, time_field: str) -> None: for m, g in self.graphs.items(): # type: str, Graph if time_field not in list(g.graph.dtype.names): raise ValueError(f"{time_field} not available. Check if you have NULL values in the database") g.free_flow_time = time_field g.set_graph(time_field) self.graphs[m] = g def count_links(self) -> int: c = self.conn.cursor() c.execute("""select count(*) from links""") return c.fetchone()[0] def count_nodes(self) -> int: c = self.conn.cursor() c.execute("""select count(*) from nodes""") return c.fetchone()[0] def add_triggers(self): self.__add_network_triggers() self.__add_mode_triggers() def __add_network_triggers(self) -> None: logger.info("Adding network triggers") pth = os.path.dirname(os.path.realpath(__file__)) qry_file = os.path.join(pth, "database_triggers", "network_triggers.sql") self.__add_trigger_from_file(qry_file) def __add_mode_triggers(self) -> None: logger.info("Adding mode table triggers") pth = os.path.dirname(os.path.realpath(__file__)) qry_file = os.path.join(pth, "database_triggers", "modes_table_triggers.sql") self.__add_trigger_from_file(qry_file) def __add_trigger_from_file(self, qry_file: str): curr = self.conn.cursor() sql_file = open(qry_file, "r") query_list = sql_file.read() sql_file.close() # Run one query/command at a time for cmd in query_list.split("#"): try: curr.execute(cmd) except Exception as e: msg = f"Error creating trigger: {e.args}" logger.error(msg) logger.info(cmd) self.conn.commit() def add_spatial_index(self) -> None: curr = self.conn.cursor() curr.execute("""SELECT CreateSpatialIndex( 'links' , 'geometry' );""") curr.execute("""SELECT CreateSpatialIndex( 'nodes' , 'geometry' );""") self.conn.commit()
class ProjectFromOSMDialog(QtWidgets.QDialog, FORM_CLASS): def __init__(self, iface): QtWidgets.QDialog.__init__(self) self.iface = iface self.setupUi(self) self.path = standard_path() self.error = None self.report = [] self.worker_thread = None self.running = False self.bbox = None self.json = [] self.project = None self.logger = logging.getLogger("aequilibrae") self._run_layout = QGridLayout() # Area to import network for self.choose_place = QRadioButton() self.choose_place.setText("Place name") self.choose_place.toggled.connect(self.change_place_type) self.choose_place.setChecked(False) self.choose_canvas = QRadioButton() self.choose_canvas.setText("Current map canvas area") self.choose_canvas.setChecked(True) self.place = QLineEdit() self.place.setVisible(False) self.source_type_frame = QVBoxLayout() self.source_type_frame.setAlignment(Qt.AlignLeft) self.source_type_frame.addWidget(self.choose_place) self.source_type_frame.addWidget(self.choose_canvas) self.source_type_frame.addWidget(self.place) self.source_type_widget = QGroupBox('Target') self.source_type_widget.setLayout(self.source_type_frame) # Buttons and output self.but_choose_output = QPushButton() self.but_choose_output.setText("Choose file output") self.but_choose_output.clicked.connect(self.choose_output) self.output_path = QLineEdit() self.but_run = QPushButton() self.but_run.setText("Import network and create project") self.but_run.clicked.connect(self.run) self.buttons_frame = QVBoxLayout() self.buttons_frame.addWidget(self.but_choose_output) self.buttons_frame.addWidget(self.output_path) self.buttons_frame.addWidget(self.but_run) self.buttons_widget = QWidget() self.buttons_widget.setLayout(self.buttons_frame) self.progressbar = QProgressBar() self.progress_label = QLabel() self.update_widget = QWidget() self.update_frame = QVBoxLayout() self.update_frame.addWidget(self.progressbar) self.update_frame.addWidget(self.progress_label) self.update_widget.setLayout(self.update_frame) self.update_widget.setVisible(False) self._run_layout.addWidget(self.source_type_widget) self._run_layout.addWidget(self.buttons_widget) self._run_layout.addWidget(self.update_widget) self.setLayout(self._run_layout) self.resize(280, 250) def choose_output(self): new_name, file_type = GetOutputFileName(self, '', ["SQLite database(*.sqlite)"], ".sqlite", self.path) if new_name is not None: self.output_path.setText(new_name) def run(self): self.update_widget.setVisible(True) self.resize(280, 300) if self.choose_canvas.isChecked(): self.report.append( reporter('Chose to download network for canvas area')) e = self.iface.mapCanvas().extent() bbox = [e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum()] else: self.progress_label.setText('Establishing area for download') self.report.append(reporter('Chose to download network for place')) bbox, r = placegetter(self.place.text()) self.report.extend(r) if bbox is None: self.leave() return west, south, east, north = bbox[0], bbox[1], bbox[2], bbox[3] self.report.append( reporter( 'Downloading network for bounding box ({} {}, {}, {})'.format( west, south, east, north))) self.bbox = bbox surveybox = QgsRectangle(QgsPointXY(west, south), QgsPointXY(east, north)) geom = QgsGeometry().fromRect(surveybox) conv = QgsDistanceArea() area = conv.convertAreaMeasurement(conv.measureArea(geom), QgsUnitTypes.AreaSquareMeters) self.report.append( reporter( 'Area for which we will download a network: {:,} km.sq'.format( area / 1000000))) if area <= max_query_area_size: geometries = [[west, south, east, north]] else: parts = math.ceil(area / max_query_area_size) horizontal = math.ceil(math.sqrt(parts)) vertical = math.ceil(parts / horizontal) dx = east - west dy = north - south geometries = [] for i in range(horizontal): xmin = west + i * dx xmax = west + (i + 1) * dx for j in range(vertical): ymin = south + j * dy ymax = south + (j + 1) * dy box = [xmin, ymin, xmax, ymax] geometries.append(box) p = Parameters().parameters modes = [list(k.keys())[0] for k in p['network']['modes']] self.progress_label.setText('Downloading data') self.downloader = OSMDownloader(geometries, modes) self.run_download_thread() def final_steps(self): self.project = Project(self.output_path.text(), True) self.project.network.create_empty_tables() curr = self.project.conn.cursor() curr.execute("""ALTER TABLE links ADD COLUMN osm_id integer""") curr.execute("""ALTER TABLE nodes ADD COLUMN osm_id integer""") self.project.conn.commit() self.project.conn.close() self.builder = OSMBuilder(self.downloader.json, self.project.source) self.run_thread() def run_download_thread(self): self.downloader.downloading.connect(self.signal_downloader_handler) self.downloader.start() self.exec_() def run_thread(self): self.builder.building.connect(self.signal_handler) self.builder.start() self.exec_() def change_place_type(self): if self.choose_place.isChecked(): self.place.setVisible(True) else: self.place.setVisible(False) def leave(self): self.close() dlg2 = ReportDialog(self.iface, self.report) dlg2.show() dlg2.exec_() def signal_downloader_handler(self, val): if val[0] == "Value": self.progressbar.setValue(val[1]) elif val[0] == "maxValue": self.progressbar.setRange(0, val[1]) elif val[0] == "text": self.progress_label.setText(val[1]) elif val[0] == "FinishedDownloading": self.final_steps() def signal_handler(self, val): if val[0] == "Value": self.progressbar.setValue(val[1]) elif val[0] == "maxValue": self.progressbar.setRange(0, val[1]) elif val[0] == "text": self.progress_label.setText(val[1]) elif val[0] == "finished_threaded_procedure": self.project = Project(self.output_path.text()) self.progress_label.setText('Adding spatial indices') self.project.network.add_spatial_index() self.project.network.add_triggers() l = self.project.network.count_links() n = self.project.network.count_nodes() self.report.append(reporter(f'{l:,} links generated')) self.report.append(reporter(f'{n:,} nodes generated')) self.leave()
class Network: """ Network class. Member of an AequilibraE Project """ req_link_flds = req_link_flds req_node_flds = req_node_flds protected_fields = protected_fields link_types: LinkTypes = None def __init__(self, project) -> None: self.conn = project.conn # type: sqlc self.source = project.source # type: sqlc self.graphs = {} # type: Dict[Graph] self.modes = Modes(self) self.link_types = LinkTypes(self) self.links = Links() self.nodes = Nodes() def skimmable_fields(self): """ Returns a list of all fields that can be skimmed Returns: :obj:`list`: List of all fields that can be skimmed """ curr = self.conn.cursor() curr.execute("PRAGMA table_info(links);") field_names = curr.fetchall() ignore_fields = ["ogc_fid", "geometry"] + self.req_link_flds skimmable = [ "INT", "INTEGER", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT", "UNSIGNED BIG INT", "INT2", "INT8", "REAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT", "DECIMAL", "NUMERIC", ] all_fields = [] for f in field_names: if f[1] in ignore_fields: continue for i in skimmable: if i in f[2].upper(): all_fields.append(f[1]) break all_fields.append("distance") real_fields = [] for f in all_fields: if f[-2:] == "ab": if f[:-2] + "ba" in all_fields: real_fields.append(f[:-3]) elif f[-3:] != "_ba": real_fields.append(f) return real_fields def list_modes(self): """ Returns a list of all the modes in this model Returns: :obj:`list`: List of all modes """ curr = self.conn.cursor() curr.execute("""select mode_id from modes""") return [x[0] for x in curr.fetchall()] def create_from_osm( self, west: float = None, south: float = None, east: float = None, north: float = None, place_name: str = None, modes=["car", "transit", "bicycle", "walk"], ) -> None: """ Downloads the network from Open-Street Maps Args: *west* (:obj:`float`, Optional): West most coordinate of the download bounding box *south* (:obj:`float`, Optional): South most coordinate of the download bounding box *east* (:obj:`float`, Optional): East most coordinate of the download bounding box *place_name* (:obj:`str`, Optional): If not downloading with East-West-North-South boundingbox, this is required *modes* (:obj:`list`, Optional): List of all modes to be downloaded. Defaults to the modes in the parameter file p = Project() p.new(nm) :: from aequilibrae import Project, Parameters p = Project() p.new('path/to/project') # We now choose a different overpass endpoint (say a deployment in your local network) par = Parameters() par.parameters['osm']['overpass_endpoint'] = "http://192.168.1.234:5678/api" # Because we have our own server, we can set a bigger area for download (in M2) par.parameters['osm']['max_query_area_size'] = 10000000000 # And have no pause between successive queries par.parameters['osm']['sleeptime'] = 0 # Save the parameters to disk par.write_back() # And do the import p.network.create_from_osm(place_name=my_beautiful_hometown) p.close() """ if self.count_links() > 0: raise FileExistsError( "You can only import an OSM network into a brand new model file" ) curr = self.conn.cursor() curr.execute("""ALTER TABLE links ADD COLUMN osm_id integer""") curr.execute("""ALTER TABLE nodes ADD COLUMN osm_id integer""") self.conn.commit() if isinstance(modes, (tuple, list)): modes = list(modes) elif isinstance(modes, str): modes = [modes] else: raise ValueError( "'modes' needs to be string or list/tuple of string") if place_name is None: if min(east, west) < -180 or max(east, west) > 180 or min( north, south) < -90 or max(north, south) > 90: raise ValueError("Coordinates out of bounds") bbox = [west, south, east, north] else: bbox, report = placegetter(place_name) west, south, east, north = bbox if bbox is None: msg = f'We could not find a reference for place name "{place_name}"' warn(msg) logger.warning(msg) return for i in report: if "PLACE FOUND" in i: logger.info(i) # Need to compute the size of the bounding box to not exceed it too much height = haversine((east + west) / 2, south, (east + west) / 2, north) width = haversine(east, (north + south) / 2, west, (north + south) / 2) area = height * width par = Parameters().parameters["osm"] max_query_area_size = par["max_query_area_size"] if area < max_query_area_size: polygons = [bbox] else: polygons = [] parts = math.ceil(area / max_query_area_size) horizontal = math.ceil(math.sqrt(parts)) vertical = math.ceil(parts / horizontal) dx = (east - west) / horizontal dy = (north - south) / vertical for i in range(horizontal): xmin = max(-180, west + i * dx) xmax = min(180, west + (i + 1) * dx) for j in range(vertical): ymin = max(-90, south + j * dy) ymax = min(90, south + (j + 1) * dy) box = [xmin, ymin, xmax, ymax] polygons.append(box) logger.info("Downloading data") self.downloader = OSMDownloader(polygons, modes) self.downloader.doWork() logger.info("Building Network") self.builder = OSMBuilder(self.downloader.json, self.source) self.builder.doWork() logger.info("Network built successfully") def build_graphs(self, fields: list = None, modes: list = None) -> None: """Builds graphs for all modes currently available in the model When called, it overwrites all graphs previously created and stored in the networks' dictionary of graphs Args: *fields* (:obj:`list`, optional): When working with very large graphs with large number of fields in the database, it may be useful to specify which fields to use *modes* (:obj:`list`, optional): When working with very large graphs with large number of fields in the database, it may be useful to generate only those we need To use the *fields* parameter, a minimalistic option is the following :: p = Project() p.open(nm) fields = ['distance'] p.network.build_graphs(fields, modes = ['c', 'w']) """ curr = self.conn.cursor() if fields is None: curr.execute("PRAGMA table_info(links);") field_names = curr.fetchall() ignore_fields = ["ogc_fid", "geometry"] all_fields = [ f[1] for f in field_names if f[1] not in ignore_fields ] else: fields.extend( ["link_id", "a_node", "b_node", "direction", "modes"]) all_fields = list(set(fields)) if modes is None: modes = curr.execute("select mode_id from modes;").fetchall() modes = [m[0] for m in modes] elif isinstance(modes, str): modes = [modes] sql = f"select {','.join(all_fields)} from links" df = pd.read_sql(sql, self.conn).fillna(value=np.nan) valid_fields = list(df.select_dtypes(np.number).columns) + ["modes"] curr.execute( "select node_id from nodes where is_centroid=1 order by node_id;") centroids = np.array([i[0] for i in curr.fetchall()], np.uint32) data = df[valid_fields] for m in modes: net = pd.DataFrame(data, copy=True) net.loc[~net.modes.str.contains(m), "b_node"] = net.loc[~net.modes.str.contains(m), "a_node"] g = Graph() g.mode = m g.network = net g.prepare_graph(centroids) g.set_blocked_centroid_flows(True) self.graphs[m] = g def set_time_field(self, time_field: str) -> None: """ Set the time field for all graphs built in the model Args: *time_field* (:obj:`str`): Network field with travel time information """ for m, g in self.graphs.items(): # type: str, Graph if time_field not in list(g.graph.columns): raise ValueError( f"{time_field} not available. Check if you have NULL values in the database" ) g.free_flow_time = time_field g.set_graph(time_field) self.graphs[m] = g def count_links(self) -> int: """ Returns the number of links in the model Returns: :obj:`int`: Number of links """ return self.__count_items("link_id", "links", "link_id>=0") def count_centroids(self) -> int: """ Returns the number of centroids in the model Returns: :obj:`int`: Number of centroids """ return self.__count_items("node_id", "nodes", "is_centroid=1") def count_nodes(self) -> int: """ Returns the number of nodes in the model Returns: :obj:`int`: Number of nodes """ return self.__count_items("node_id", "nodes", "node_id>=0") def extent(self): """Queries the extent of the network included in the model Returns: *model extent* (:obj:`Polygon`): Shapely polygon with the bounding box of the model network. """ curr = self.conn.cursor() curr.execute('Select ST_asBinary(GetLayerExtent("Links"))') poly = shapely.wkb.loads(curr.fetchone()[0]) return poly def convex_hull(self) -> Polygon: """ Queries the model for the convex hull of the entire network Returns: *model coverage* (:obj:`Polygon`): Shapely (Multi)polygon of the model network. """ curr = self.conn.cursor() curr.execute( 'Select ST_asBinary("geometry") from Links where ST_Length("geometry") > 0;' ) links = [shapely.wkb.loads(x[0]) for x in curr.fetchall()] return unary_union(links).convex_hull def __count_items(self, field: str, table: str, condition: str) -> int: c = self.conn.cursor() c.execute(f"""select count({field}) from {table} where {condition};""") return c.fetchone()[0]
def create_from_osm( self, west: float = None, south: float = None, east: float = None, north: float = None, place_name: str = None, modes=["car", "transit", "bicycle", "walk"], ) -> None: """ Downloads the network from Open-Street Maps Args: *west* (:obj:`float`, Optional): West most coordinate of the download bounding box *south* (:obj:`float`, Optional): South most coordinate of the download bounding box *east* (:obj:`float`, Optional): East most coordinate of the download bounding box *place_name* (:obj:`str`, Optional): If not downloading with East-West-North-South boundingbox, this is required *modes* (:obj:`list`, Optional): List of all modes to be downloaded. Defaults to the modes in the parameter file p = Project() p.new(nm) :: from aequilibrae import Project, Parameters p = Project() p.new('path/to/project') # We now choose a different overpass endpoint (say a deployment in your local network) par = Parameters() par.parameters['osm']['overpass_endpoint'] = "http://192.168.1.234:5678/api" # Because we have our own server, we can set a bigger area for download (in M2) par.parameters['osm']['max_query_area_size'] = 10000000000 # And have no pause between successive queries par.parameters['osm']['sleeptime'] = 0 # Save the parameters to disk par.write_back() # And do the import p.network.create_from_osm(place_name=my_beautiful_hometown) p.close() """ if self.count_links() > 0: raise FileExistsError( "You can only import an OSM network into a brand new model file" ) curr = self.conn.cursor() curr.execute("""ALTER TABLE links ADD COLUMN osm_id integer""") curr.execute("""ALTER TABLE nodes ADD COLUMN osm_id integer""") self.conn.commit() if isinstance(modes, (tuple, list)): modes = list(modes) elif isinstance(modes, str): modes = [modes] else: raise ValueError( "'modes' needs to be string or list/tuple of string") if place_name is None: if min(east, west) < -180 or max(east, west) > 180 or min( north, south) < -90 or max(north, south) > 90: raise ValueError("Coordinates out of bounds") bbox = [west, south, east, north] else: bbox, report = placegetter(place_name) west, south, east, north = bbox if bbox is None: msg = f'We could not find a reference for place name "{place_name}"' warn(msg) logger.warning(msg) return for i in report: if "PLACE FOUND" in i: logger.info(i) # Need to compute the size of the bounding box to not exceed it too much height = haversine((east + west) / 2, south, (east + west) / 2, north) width = haversine(east, (north + south) / 2, west, (north + south) / 2) area = height * width par = Parameters().parameters["osm"] max_query_area_size = par["max_query_area_size"] if area < max_query_area_size: polygons = [bbox] else: polygons = [] parts = math.ceil(area / max_query_area_size) horizontal = math.ceil(math.sqrt(parts)) vertical = math.ceil(parts / horizontal) dx = (east - west) / horizontal dy = (north - south) / vertical for i in range(horizontal): xmin = max(-180, west + i * dx) xmax = min(180, west + (i + 1) * dx) for j in range(vertical): ymin = max(-90, south + j * dy) ymax = min(90, south + (j + 1) * dy) box = [xmin, ymin, xmax, ymax] polygons.append(box) logger.info("Downloading data") self.downloader = OSMDownloader(polygons, modes) self.downloader.doWork() logger.info("Building Network") self.builder = OSMBuilder(self.downloader.json, self.source) self.builder.doWork() logger.info("Network built successfully")