Source code for tracklib.io.NetworkReader

"""Read network files"""

import csv
import json
import os.path
import progressbar
import requests
from xml.dom import minidom

from tracklib.core.ObsTime import ObsTime
from tracklib.core.ObsCoords import ENUCoords, ECEFCoords, GeoCoords
from tracklib.core.Obs import Obs
from tracklib.core.Track import Track
from tracklib.core.Network import Network, Node, Edge
from tracklib.core.SpatialIndex import SpatialIndex
from tracklib.core.Bbox import Bbox
from tracklib.io.NetworkFormat import NetworkFormat
import tracklib.algo.Cinematics as Cinematics


class NetworkReader:
    """
    This class offers static methods to load network from file or from a wfs service.
    """

[docs] @staticmethod def readFromFile(path:str, formatfile:str="DEFAULT", verbose=True)->Network: """ Read a network from CSV file with geometry structured in coordinates. Before to load network data, you need to have a format defined in **network_file_format** (in tracklib/resources directory) which describes metadata of the file. If it doesn't exists, you need to create one format. For example, let's define a format call 'VTT' in the file *network_file_format* which corresponds to a network associated with mountain bike tracks. So, you add a new line like this: *VTT, 1, 2, 3, 0, -1, 4, c, 1, True, UTF-8, GEO* where: - the first value 'VTT' represents the format name, - the second value '1' represents the index of column containing edge identifier in the CSV file - the third value '2' represents index of column containing source node identifier in the CSV file - the fourth value '3' represents index of column containing target node identifier in the CSV file - the fifth value '0' represents index of column containing geometry of edge in wkt format in the CSV file. You can specify also optional parameters: - a path cost (arbitrarily set to be proportional to the length of the WKT if unlined (-1)) - an orientation index. An integer arbitrarily set to: 0 to indicate two-way, 1 direct way and -1 indirect way - the separating characters (can be multiple characters). Can be: c (comma), b (blankspace), s (semi-column) - the number of heading line in format - true to manage double quote - the encoding like utf-8 - srid: coordinate system of points (ENU, Geo or ECEF) Parameters ----------- :param path: file or directory :param formatfile: name of format which describes metadata of the file :return: network. """ if not os.path.isfile(path): print ('Error: file not exists') return None network = Network() fmt = NetworkFormat(formatfile) num_lines = sum(1 for line in open(path)) if verbose: print("Loading network...") with open(path, encoding="utf-8") as csvfile: spamreader = csv.reader(csvfile, delimiter=fmt.separator, doublequote=True) # Header cpt = 0 for row in spamreader: cpt += 1 if cpt >= fmt.h: break if verbose: spamreader = progressbar.progressbar(spamreader, max_value=num_lines) for row in spamreader: (edge, noeudIni, noeudFin) = readLineAndAddToNetwork(row, fmt) network.addEdge(edge, noeudIni, noeudFin) csvfile.close() # Return network loaded return network
counter = 0 NB_PER_PAGE = 1000 URL_SERVER = "https://wxs.ign.fr/choisirgeoportail/geoportail/wfs?" URL_SERVER += "service=WFS&version=2.0.0&request=GetFeature&" URL_SERVER += "typeName=BDTOPO_V3:troncon_de_route&" # URL_SERVER += "srsName=EPSG:2154&" URL_SERVER += "outputFormat=json&" # URL_SERVER += "BBOX=44.538617443499014,5.808794912294471,45.05505710140573,6.644301708889899" # URL_SERVER += "&count=3&startIndex=0" resource_path = os.path.join(os.path.split(__file__)[0], "../..") PROXY_FILE_FORMAT = os.path.join(resource_path, "resources/proxy") # self.id = id # self.coords = coords # self.nature = nature # self.sens = sens # self.fictif = fictif # self.pos = pos # =========================== # tolerance # ===========================
[docs] @staticmethod def requestFromIgnGeoportail( bbox:Bbox, proj=None, margin=0.0, tolerance=0.1, spatialIndex=True, nomproxy=None ) -> Network: """ Parameters ----------- :param bbox: The bounding box of the selected area (The bounding box must be expressed in WGS84). :param proj: projection of results, optional. Only 'EPSG:4326' is implemented :param margin: f :param tolerance: parameter specifies the maximum distance accepted for merging two nodes (edge ends) :param nomproxy: name of proxy which describes parameter connexion defined in the tracklib/resources/proxy file """ xmin = bbox.getXmin() xmax = bbox.getXmax() ymin = bbox.getYmin() ymax = bbox.getYmax() dx = (xmax - xmin) / 2 dy = (ymax - ymin) / 2 # Adding margin emprise = ( xmin - margin * dx, xmax + margin * dx, ymin - margin * dy, ymax + margin * dy, ) network = Network() cptNode = 0 if proj == None: proj = "EPSG:4326" fmt = NetworkFormat() fmt.createFromDict( { "name": "WFS", "pos_edge_id": 0, "pos_source": 5, "pos_target": 6, "pos_wkt": 1, "pos_poids": 3, "pos_sens": 4, "srid": "GEO", } ) # PROXY proxyDict = {} if nomproxy != None: with open(NetworkReader.PROXY_FILE_FORMAT) as ffmt: line = ffmt.readline().strip() while line: if line[0] == "#": line = ffmt.readline().strip() continue tab = line.split(",") if tab[0].strip() == nomproxy: FIELDS = tab proxyDict[FIELDS[1].lower()] = FIELDS[2] line = ffmt.readline().strip() ffmt.close() nbRoute = NetworkReader.__getNbRouteEmprise(emprise, proxyDict) if nbRoute == 0: return None nbiter = int(nbRoute / NetworkReader.NB_PER_PAGE) + 1 offset = 0 for j in range(nbiter): print("PAGE " + str(j + 1) + "/" + str(nbiter)) URL_FEAT = NetworkReader.URL_SERVER URL_FEAT += "BBOX=" + str(emprise[2]) + "," + str(emprise[0]) URL_FEAT += "," + str(emprise[3]) + "," + str(emprise[1]) URL_FEAT += "&count=" + str(NetworkReader.NB_PER_PAGE) URL_FEAT += "&startIndex=" + str(offset) URL_FEAT += "&RESULTTYPE=results" URL_FEAT += "&srsname=" + proj # print (URL_FEAT) response = requests.get(URL_FEAT, proxies=proxyDict) data = json.loads(response.text) features = data["features"] for feature in progressbar.progressbar(features): row = [] idd = feature["id"] # nature = feature['properties']['nature'] fictif = feature["properties"]["fictif"] if fictif == "True": continue row.append(idd) # TODO # pos = feature['properties']['position_par_rapport_au_sol'] TAB_OBS = [] coords = feature["geometry"]["coordinates"] if feature["geometry"]["type"] == "LineString": # print (str(len(coords))) # geom = coords # print (coords) typeCoord = "GEOCOORDS" if proj == "EPSG:2154": typeCoord = "ENUCoords" TAB_OBS = tabCoordsLineStringToObs(coords, typeCoord) if len(TAB_OBS) < 2: continue track = Track(TAB_OBS) row.append(track.toWKT()) row.append("ENU") row.append(track.length()) # Orientation sens = feature["properties"]["sens_de_circulation"] orientation = Edge.DOUBLE_SENS if sens == None or sens == "": orientation = Edge.DOUBLE_SENS elif sens == "Double sens" or sens == "Sans objet": orientation = Edge.DOUBLE_SENS elif sens == "Direct" or sens == "Sens direct": orientation = Edge.SENS_DIRECT elif sens == "Indirect" or sens == "Sens inverse": orientation = Edge.SENS_INVERSE else: print(sens) row.append(orientation) # Source node idNoeudIni = str(cptNode) p1 = track.getFirstObs().position candidates = selectNodes(network, Node("0", p1), tolerance) if len(candidates) > 0: c = candidates[0] idNoeudIni = c.id else: cptNode += 1 # Target node idNoeudFin = str(cptNode) p2 = track.getLastObs().position candidates = selectNodes(network, Node("0", p2), tolerance) if len(candidates) > 0: c = candidates[0] idNoeudFin = c.id else: cptNode += 1 row.append(idNoeudIni) row.append(idNoeudFin) (edge, noeudIni, noeudFin) = readLineAndAddToNetwork(row, fmt) network.addEdge(edge, noeudIni, noeudFin) # offset = offset + NetworkReader.NB_PER_PAGE if spatialIndex: network.spatial_index = SpatialIndex( network, resolution=(dx / 1e3, dy / 1e3), margin=0.4 ) return network
# -------------------------------------------------------------------------- # Function to count road network features in bbox # -------------------------------------------------------------------------- # Input : # - v: a float value # -------------------------------------------------------------------------- # Output : number # -------------------------------------------------------------------------- @staticmethod def __getNbRouteEmprise(bbox, proxyDict): """TODO""" nb = 0 URL_HITS = NetworkReader.URL_SERVER URL_HITS += "BBOX=" + str(bbox[2]) + "," + str(bbox[0]) URL_HITS += "," + str(bbox[3]) + "," + str(bbox[1]) URL_HITS += "&resulttype=hits" #print (URL_HITS) try: res = requests.get(URL_HITS, proxies=proxyDict) status = res.raise_for_status() print (status, type(status)) except requests.exceptions.RequestException: print("ERROR: Failed to establish connection") return 0 # x = requests.get(url, proxies = { "https" : "https://1.1.0.1:80"}) # print ('---------' + status, type(status)) dom = minidom.parseString(res.text) result = dom.getElementsByTagName("wfs:FeatureCollection") nb = int(result[0].attributes["numberMatched"].value) return nb def readLineAndAddToNetwork(row, fmt): """TODO""" edge_id = str(row[fmt.pos_edge_id]) if fmt.pos_edge_id < 0: edge_id = NetworkReader.counter NetworkReader.counter = NetworkReader.counter + 1 geom = str(row[fmt.pos_wkt]) TAB_OBS = wktLineStringToObs(geom, fmt.srid.upper()) # Au moins 2 points if len(TAB_OBS) < 2: return track = Track(TAB_OBS) Cinematics.computeAbsCurv(track) edge = Edge(edge_id, track) # Orientation orientation = int(row[fmt.pos_sens]) if orientation not in [Edge.DOUBLE_SENS, Edge.SENS_DIRECT, Edge.SENS_INVERSE]: orientation = Edge.DOUBLE_SENS edge.orientation = orientation # Poids if fmt.pos_poids == -1: poids = track.length() else: poids = float(row[fmt.pos_poids]) edge.weight = poids # Source node source = str(row[fmt.pos_source]) noeudIni = Node(source, track.getFirstObs().position) # Target node target = str(row[fmt.pos_target]) noeudFin = Node(target, track.getLastObs().position) # Return return (edge, noeudIni, noeudFin) # network.addEdge(edge, noeudIni, noeudFin) def wktLineStringToObs(wkt, srid): """ Une polyligne de n points est modélisée par une Track (timestamp = 1970/01/01 00 :00 :00) Cas LINESTRING() """ # Creation d'une liste vide TAB_OBS = list() # Separation de la chaine coords_string = wkt.split("(") coords_string = coords_string[1] coords_string = coords_string.split(")")[0] coords = coords_string.split(",") for i in range(0, len(coords)): sl = coords[i].strip().split(" ") x = float(sl[0]) y = float(sl[1]) if len(sl) == 3: z = float(sl[2]) else: z = 0.0 if not srid.upper() in [ "ENUCOORDS", "ENU", "GEOCOORDS", "GEO", "ECEFCOORDS", "ECEF", ]: print("Error: unknown coordinate type [" + str(srid) + "]") exit() if srid.upper() in ["ENUCOORDS", "ENU"]: point = ENUCoords(x, y, z) if srid.upper() in ["GEOCOORDS", "GEO"]: point = GeoCoords(x, y, z) if srid.upper() in ["ECEFCOORDS", "ECEF"]: point = ECEFCoords(x, y, z) TAB_OBS.append(Obs(point, ObsTime())) return TAB_OBS def selectNodes(network, node, distance): """Selection des autres noeuds dans le cercle dont node.coord est le centre, de rayon distance :param node: le centre du cercle de recherche. :param distance: le rayon du cercle de recherche. :return: tableau de NODES liste des noeuds dans le cercle """ NODES = [] if network.spatial_index is None: for key in network.getIndexNodes(): n = network.NODES[key] if n.coord.distance2DTo(node.coord) <= distance: NODES.append(n) else: vicinity_edges = network.spatial_index.neighborhood(node.coord, unit=1) for e in vicinity_edges: source = network.EDGES[network.getEdgeId(e)].source target = network.EDGES[network.getEdgeId(e)].target if source.coord.distance2DTo(node.coord) <= distance: NODES.append(source) if target.coord.distance2DTo(node.coord) <= distance: NODES.append(target) return NODES def tabCoordsLineStringToObs(coords, srid): """TODO""" # Creation d'une liste vide TAB_OBS = list() for i in range(0, len(coords)): sl = coords[i] x = float(sl[0]) y = float(sl[1]) if len(sl) == 3: z = float(sl[2]) else: z = 0.0 if not srid.upper() in [ "ENUCOORDS", "ENU", "GEOCOORDS", "GEO", "ECEFCOORDS", "ECEF", ]: print("Error: unknown coordinate type [" + str(srid) + "]") exit() if srid.upper() in ["ENUCOORDS", "ENU"]: point = ENUCoords(x, y, z) if srid.upper() in ["GEOCOORDS", "GEO"]: point = GeoCoords(x, y, z) if srid.upper() in ["ECEFCOORDS", "ECEF"]: point = ECEFCoords(x, y, z) TAB_OBS.append(Obs(point, ObsTime())) return TAB_OBS