├── README.md ├── imgs ├── bbdd_taller.png ├── conexion_postgis.png ├── ejercicio_1_pgadmin.png ├── ejercicio_1_qgis.png ├── ejercicio_1_qgis_2.png ├── pgadmin_geometry_viewer.png ├── qgis_iso_1.png ├── qgis_iso_2.png ├── qgis_iso_3.png ├── qgis_iso_4.png ├── qgis_iso_5.png ├── qgis_iso_6.png └── qgis_iso_7.png ├── importacion_de_archivos_usando_osm2pgrouting.txt ├── iscronas.qml └── mapconfig_for_cars_mod.xml /README.md: -------------------------------------------------------------------------------- 1 | # **Taller pgRouting - RNU QGIS México 2019** 2 | 3 | ![Logo de pgRouting](https://docs.pgrouting.org/2.6/en/_static/pgrouting.png) 4 | 5 | *Este taller está basado en los [Workshops de pgRouting de los FOSS4G](https://workshop.pgrouting.org/) sin los cuales hubiera sido imposible aprender a usar pgRouting. Gracias!* 6 | ## **1. ¿Qué es pgRouting?** 7 | 8 | pgRouting es una extensión para PostgreSQL/PostGIS que añade funcionalidades para ánalisis de redes y planificación de rutas. Esto nos permite realizar cálculos de rutas óptimas, áreas de servicio e iscocronas (con la ayuda de QGIS), desde la propia base de datos con los beneficios que esto conlleva: 9 | * Los datos pueden ser modificados desde diversos tipos de clientes: 10 | * SIG de Escritorio (QGIS, gvSIG, uDig, etc) 11 | * Aplicaciones web 12 | * Dispositivos móviles 13 | * Las modificaciones de los datos pueden reflejarse de forma inmediata en el motor de enrutamiento. 14 | * Los parámetros de costo de desplazamiento por la red puede calcularse de forma dinámica usando SQL, permitiendo utilizar atributos de diferentes campos o tablas (por ejemplo, la velocidad máxima permitida en una carretera). 15 | 16 | La librería pgRouting contiene los siguientes algoritmos: 17 | * [Algoritmo de Dijkstra](https://es.wikipedia.org/wiki/Algoritmo_de_Dijkstra) 18 | * [Algoritmo de Johnson](https://es.wikipedia.org/wiki/Algoritmo_de_Johnson) 19 | * [Algoritmo de Floyd-Warshall](https://es.wikipedia.org/wiki/Algoritmo_de_Floyd-Warshall) 20 | * [Algoritmo A*](https://es.wikipedia.org/wiki/Algoritmo_de_b%C3%BAsqueda_A*) 21 | * [Algoritmos bidireccionales](https://en.wikipedia.org/wiki/Bidirectional_search): Dijkstra y A* bidireccionales 22 | * [Problema del viajante](https://es.wikipedia.org/wiki/Problema_del_viajante) 23 | * Distancia manejando 24 | * Camino más corto con restricción de giros 25 | * Etc. 26 | 27 | pgRouting es una librería de código abierto disponible con la licencia GPLv2 y soportada y mantenida por 28 | [Georepublic](http://georepublic.info/), [iMaptools](http://imaptools.com/) y una amplica comunidad de usarios. 29 | 30 | Una de las principales desarrolladoras es [Vicky Vergara](https://twitter.com/VickyVvergara) 31 | 32 | ## **2. Estructura de datos** 33 | 34 | Los archivos para el taller los podemos descargar desde [aquí](https://drive.google.com/drive/folders/1t9sBO8x97giIzbaBvq3jbdqyqbU7W_yC?usp=sharing) 35 | 36 | La estructura básica que necesitamos para empezar a trabajar con PgRouting es una capa de líneas (o tabla de base de datos) con una buena calidad topológica (que no tenga lineas desconectadas). Si queremos hacer cálculos en función del tiempo de desplazamiento necestaremos además un campo que contenga la velocidad máxima permitida en la vía y longitud de la linea (en metros). Si además queremos tener en cuenta el sentido de circulación necesitamos un atributo que nos indique el sentido de circulación o si la vía es de doble sentido. 37 | 38 | Además de la "capa" de líneas necesitamos una capa de nodos de la red. Estos nodos definen las conexiones entre calles y carreteras. La capa de lineas tiene que contener para cada segmento de la red cuál es el nodo de origen y el nodo de destino que conecta. Así que finalmente la capa de lineas tiene que contener dos atributos más, nodo de origen y nodo de estino (source y target). 39 | 40 | ### OpenStreetMap 41 | 42 | Una de las fuentes de datos con la que podemos trabajar es OpenStreetMap. Para ello necesitamos dos cosas: 43 | * Descargar los datos de OSM desde: 44 | * https://www.openstreetmap.org (nos ubicamos el en el área de interés y luego hacemos click en Overpass API) 45 | * Descargamos los conglomerados a nivel subregión, país o ciudad desde https://download.geofabrik.de 46 | * Instalar [osm2pgrouting](https://github.com/pgRouting/osm2pgrouting) 47 | 48 | 49 | osm2pgrouting nos va a permitir generar toda la estructura de base de datos que necesitamos para pgRouting de forma sencilla y rápida. El único incoveniente es que si queremos procesar conjuntos de datos muy grandes (por ejemplo, todo un estado de México o el país completo) vamos a necesitar hacerlo por partes (eliminando el parámetro --clean) o contar con un servidor con una cantidad enorme de memoria RAM. 50 | 51 | El ejemplo que aparece a continuación exportaría a nuestra base de datos PostgreSQL de nombre ruteo, el archivo que descargamos "tu_archivo_osm_xml.osm" usando la configuración en el archivo de configuración mapconfig.xml. 52 | 53 | ```bash 54 | osm2pgrouting --f tu_archivo_osm_xml.osm --conf mapconfig.xml --dbname ruteo --username postgres --clean 55 | ``` 56 | 57 | A continuación se muestra la ayuda de osm2pgrouting: 58 | 59 | ```bash 60 | osm2pgrouting --help 61 | Allowed options: 62 | 63 | Help: 64 | --help Produce help message for this version. 65 | -v [ --version ] Print version string 66 | 67 | General: 68 | -f [ --file ] arg REQUIRED: Name of the osm file. 69 | -c [ --conf ] arg (=/usr/share/osm2pgrouting/mapconfig.xml) 70 | Name of the configuration xml file. 71 | --schema arg Database sch2pgrouting -f map.osm -c mapconfig_for_cars_mod.xml -d gislocal -p 5433 --schema ruteoamg -U gisadmin -W privacidad% --clean --chunk 20000ema to put tables. 72 | blank: defaults ´to default schema 73 | dictated by PostgreSQL 74 | search_path. 75 | --prefix arg Prefix added at the beginning of the 76 | table names. 77 | --suffix arg Suffix added at the end of the table 78 | names. 79 | --addnodes Import the osm_nodes, osm_ways & 80 | osm_relations tables. 81 | --attributes Include attributes information. 82 | --tags Include tag information. 83 | --chunk arg (=20000) Exporting chunk size. 84 | --clean Drop previously created tables. 85 | --no-index Do not create indexes (Use when indexes 86 | are already created) 87 | 88 | Database options: 89 | -d [ --dbname ] arg Name of your database (Required). 90 | -U [ --username ] arg Name of the user, which have write access to 91 | the database. 92 | -h [ --host ] arg (=localhost) Host of your postgresql database. 93 | -p [ --port ] arg (=5432) db_port of your database. 94 | -W [ --password ] arg Password for database access. 95 | ``` 96 | 97 | El parámetro --conf nos permite utilizar un archivo de configuración para osm2pgrouting que va a definir que tipos de carreteras o vías queremos utilizar y cual es la velocidad de desplazamiento en cada tipo de vía. Por defecto vamos a encontrar 3 configuraciones en la carpeta /usr/share/osm2pgrouting/: 98 | * Para bicicletas 99 | * Para automóviles 100 | * Para peatones 101 | 102 | Si queremos podemos modificar los archivos de coniguración para que se adapte a nuestras necesidades, qué tipo de vías vamos a importar (definido por los tipos de vías de OSM) y la velocidad máxima a utilizar paraa cada tipo de vía. 103 | 104 | #### Beneficios e incovenientes de Utilizar OSM 105 | **Pros:** 106 | 1. Los datos están más actualizados que otras fuentes de información en México y se manejan como un único conjunto de datos (no son dos capas de información separadas como en el caso de INEGI, una para carreteras y otra para calles). 107 | 2. La información está en algunos casos más actualizada. 108 | 3. Las líneas cuentan con todos los atributos necesarios para pgRouting 109 | 4. Los nodos de conexión de la red siguen reglas precisas de manera que aunque dos lineas se crucen puede no existir un nodo de conexión porque sea un paso a desnivel, un tunel o un puente. 110 | 5. En general facilita enormemente la creación de una red "ruteable" 111 | 112 | **Contras:** 113 | 1. Puede que los datos en tu región no estén tan completos o actualizados como desearías 114 | 2. La herramienta osm2pgrouting puede consumir enormes cantidades de memoría por lo que necesitamos hacer importaciones "incrementales" si queremos trabajar en regiones muy grandes (también podemos utilizar un servidor en la nube con mucha RAM para hacer el proceso y una vez creada la red descargarla a un servidor con menos memoria RAM o un PC). 115 | 116 | ### INEGI 117 | 118 | La [Red Nacional de Caminos de INEGI](https://www.inegi.org.mx/app/biblioteca/ficha.html?upc=889463674641) soporta el estándar internacional ISO 14825:2011 Intelligent Transport Systems_Geographic Data Files_GDF5.0, la cual integra los elementos necesarios para ruteo, ya tiene el formato necesario para pgRouting: 119 | * Capa red_vial: Contiene las carreteras. Tiene como atributos VELOCIDAD, UNION_INI (nodo de inicio) y UNION_FIN (nodo de destino) y LONGITUD. 120 | * Capa union: Puntos que representan los nodos (uniones) de los segmentos de la red de caminos.Tiene el atributo ID_UNION que es el identificador que está almacenado en los campos UNION_INI y UNION_FIN de la capa. red_vial 121 | * Localidad: Localidades de México (puntual). Muchas de las localidades tiene las mismas coordenadas que los puntos de la capa de unión. 122 | 123 | **Pros:** 124 | * Ya viene preparada para ruteo con lo que simplifca mucho la creación de la red 125 | 126 | **Contras:** 127 | * La capa red_vial no incluye la mayoría de las calles de los núcleos urbanos, solo algunas de las vías principales que las cruzan. 128 | 129 | ## **Conexión a la base de datos** 130 | 131 | Para agilizar el taller no vamos a realizar la instalación de PostgreSQL, PostGIS y pgRouting. Utilizaremos una base de datos en un servidor remoto. Para conectar al servidor vamos a usar pgAdmin4. 132 | 133 | IP del Servidor: xx.xx.xx.xx 134 | 135 | Los datos están en los schemas `ruteoinegi` y `ruteoamg`. 136 | 137 | Pueden descargar la base de datos completa desde [aquí](https://drive.google.com/drive/folders/1t9sBO8x97giIzbaBvq3jbdqyqbU7W_yC?usp=sharing), es un dump con todos los datos del taller. 138 | 139 | ## **3. Algoritmos de pgRouting** 140 | 141 | *NOTA:* 142 | * Muchas de las funciones de pgRouting incluyen parámetros del tipo sql::text, es decir una consulta sql como una cadena de texto. Esto puede parecer algo confuso al principio pero permite gran flexibilidad ya que el usuario puede pasar cualquier sentencia `SELECT` como argumento para una función siempre que el resultado de dicho `SELECT` contenga el número de atributos requerido y los nombres de atributos correctos. 143 | * La mayoría de los algoritmos de pgRouting no necesitan la geometría de la red. 144 | * La mayoría de los algoritmos de pgRouting no retornan una geometría, si no una lista ordenada de nodos o segmentos. 145 | 146 | ### 3.1 pgr_dijkstra 147 | 148 | El algoritmo de Dijkstra fue el primer algorimto implementado en pgRouting. Solo requiere los atributos , `ìd` , `source` y `target` y `cost`. Podemos especificar si el grafo es dirigido o no dirigido (tendrá en cuenta el sentido de las vias o no). 149 | 150 | **Resumen de signaturas** 151 | ```sql 152 | pgr_dijkstra(edges_sql, start_vid, end_vid) 153 | pgr_dijkstra(edges_sql, start_vid, end_vid [, directed]) 154 | pgr_dijkstra(edges_sql, start_vid, end_vids [, directed]) 155 | pgr_dijkstra(edges_sql, start_vids, end_vid [, directed]) 156 | pgr_dijkstra(edges_sql, start_vids, end_vids [, directed]) 157 | 158 | RETURNS SET OF (seq, path_seq [, start_vid] [, end_vid], node, edge, cost, agg_cost) 159 | OR EMPTY SET 160 | ``` 161 | 162 | La primera opción es un origen y un destino, la segunda es igual pero dirigida, la tercera un origen y muchos destinos (1:n), la cuarta muchos orígenes y un destino (n:1) y la última es muchos origens y muchos destinos (n:m). 163 | 164 | #### 3.1.1 Ejercicio 1 - Un origen y un destino 165 | Como comentamos en la nota anterior uno de los parámetros es un `SELECT` que se lo pasamos a la función como una cadena de texto. También hay que dar un alias al parámetro length_m para que la función entienda que es el costo. El costo puede ser cualquier atributo, en este caso usamos el atributo length_m pero podría ser tiempo en segundos (la ruta más rápida) o cualquier atributo que represente el costo de desplazamiento por la red. Después del `SELECT` tenemos los IDs de los nodos entre los que queremos calcular la ruta más corta. Finalmente tenemos el parámetro `directed` que nos indica si queremos que la ruta tenga en cuenta el sentido de circulación o no. 166 | 167 | ```sql 168 | SELECT * FROM pgr_dijkstra('SELECT gid as id, 169 | source, 170 | target, 171 | length_m AS cost 172 | FROM ruteoamg.ways', 173 | 79012, 35280, 174 | directed := false) 175 | ``` 176 | El resultado de la consulta es una tabla con las columnas: 177 | * `seq`: Identificador único 178 | * `path_seq`: Secuencia dentro de la ruta 179 | * `start_vid`: Nodo de inicio de la ruta. 180 | * `node`: Nodo por el que pasa la ruta 181 | * `edge`: Segmento de la red 182 | * `cost`: Costo de desplazamiento por el segmento 183 | * `agg_cost`: Costo agregado para cada paso de la ruta 184 | 185 | Como la función pgr_dijkstra no retorna las geometrías vamos a generar una consulta que nos permita obtener las geometrías de la red para poder visualizar la ruta. En este caso estamos trayendo las geometrías de los segmentos de la red que están en la tabla ruteoamg.ways mediante un join entre el id del segmento, edge, retornado por la función pgr_dijkstra y la llave primaria de la tabla ruteoamg.ways, gid. 186 | 187 | ```sql 188 | WITH ruta as (SELECT * FROM pgr_dijkstra( 189 | 'SELECT gid as id, 190 | source, 191 | target, 192 | length_m AS cost 193 | FROM ruteoamg.ways', 194 | 79012, 35280, 195 | directed := false)) 196 | SELECT ruta.*, b.the_geom 197 | FROM ruta 198 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid ; 199 | ``` 200 | Podemos visualizar el resultado de la consulta directamente en pgAdmin4 haciendo clic en el botón con un ojo. También podriamo visualizarlo en QGIS. 201 | 202 | ![PgAdmin4 geometry viewer](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/pgadmin_geometry_viewer.png) 203 | 204 | ![PgAdmin4 geometry viewer](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/ejercicio_1_pgadmin.png) 205 | 206 | 207 | #### 3.1.2 Ejercicio 2 - Varios orígenes y un destino 208 | En este caso vamos especificar varios orígenes y un destino: 209 | * Los orígenes son los nodos: 3443 y 29539 210 | * Para el costo vamos a volver a utilizar el atributo length_m 211 | 212 | Para pasarle los origenes a la función pgr_dijkstra tenemos que usar un `ARRAY` (arreglo: conjunto de elementos del mismo tipo). 213 | 214 | ```sql 215 | SELECT * FROM pgr_dijkstra('SELECT gid as id, 216 | source, 217 | target, 218 | length_m AS cost 219 | FROM ruteoamg.ways', 220 | ARRAY[3443, 29539], 35280, 221 | directed := false) 222 | ``` 223 | ```sql 224 | WITH ruta as (SELECT * FROM pgr_dijkstra( 225 | 'SELECT gid as id, 226 | source, 227 | target, 228 | length_m AS cost 229 | FROM ruteoamg.ways', 230 | ARRAY[3443, 29539], 35280, 231 | directed := false)) 232 | SELECT ruta.*, b.the_geom 233 | FROM ruta 234 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 235 | ``` 236 | 237 | #### 3.1.3 Ejercicio 3 - Un solo origen varios destinos 238 | 239 | En este caso vamos especificar un origen y varios destinos: 240 | * El origen es el nodos: 67272 241 | * Los nodos de destino son: 1017 y 55134 242 | * Para el costo vamos a utilizar el tiempo de desplazamiento suponiendo una velocidad constante de 20 km/h (5.56 m/s).`v = 5.56 m/s` y `t=d/v` 243 | 244 | ```sql 245 | SELECT * FROM pgr_dijkstra('SELECT gid as id, 246 | source, 247 | target, 248 | length_m / 5.56 AS cost 249 | FROM ruteoamg.ways', 250 | 67272, ARRAY[1017, 55134], 251 | directed := false) 252 | ``` 253 | ```sql 254 | WITH ruta as (SELECT * FROM pgr_dijkstra('SELECT gid as id, 255 | source, 256 | target, 257 | length_m / 5.56 AS cost 258 | FROM ruteoamg.ways', 259 | 67272, ARRAY[1017, 55134], 260 | directed := false)) 261 | SELECT ruta.*, b.the_geom 262 | FROM ruta 263 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 264 | ``` 265 | 266 | ¿Qué ocurre si cambiamos el parámetro de dirigido a no dirigido (`directed:=false`)?¿En qué unidades estamos midiendo el costo de desplazamiento en este último ejercicio? 267 | 268 | #### 3.1.4 Ejercicio 4 - Varios orígenes y varios destinos 269 | 270 | En este caso vamos especificar varios origenes y varios destinos: 271 | * El origen es el nodos: 67272 y 133535 (Hoteles recomendados) 272 | * Los nodos de destino son: 1017 y 55134 (Sedes del evento QGIS RNU 2019 - CUCSH y CUAAD) 273 | * Para el costo vamos a utilizar el tiempo de desplazamiento suponiendo una velocidad constante de 20 km/h (5.56 m/s) `v = 5.56 m/s` y `t=d/v`, pero en este caso vamos a obtener el tiempo en minutos. 274 | 275 | ```sql 276 | SELECT * FROM pgr_dijkstra('SELECT gid as id, 277 | source, 278 | target, 279 | length_m / 5.56 / 60 AS cost 280 | FROM ruteoamg.ways', 281 | ARRAY[67272, 133535], ARRAY[1017, 55134], 282 | directed := false) 283 | ``` 284 | ```sql 285 | WITH ruta as (SELECT * FROM pgr_dijkstra('SELECT gid as id, 286 | source, 287 | target, 288 | length_m / 5.56 / 60 AS cost 289 | FROM ruteoamg.ways', 290 | ARRAY[67272, 133535], ARRAY[1017, 55134], 291 | directed := false)) 292 | SELECT ruta.*, b.the_geom 293 | FROM ruta 294 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 295 | ``` 296 | 297 | *NOTA*: Si inspeccionamos el resultado de la consulta, veremos que hay algunas filas que tiene edge=-1, estas filas nos indican el costo total de cada ruta: 298 | * De 67272 a 1017 tardaremos 9.33 minutos. 299 | * De 67272 a 55134 tardaremos 27.91 minutos. 300 | * De 133535 a 1017 tardaremos 14.60 minutos. 301 | * De 133535 a 32.70 tardaremos 32.70 miuntos. 302 | 303 | ### 3.2 pgr_dijkstraCost 304 | Si lo que queremos es calcular el costo total de desplazamiento, sin necesidad de buscar en los resultados de pgr_dijkstra podemos utilizar `pgr_dijkstraCost` que nos arrojará resultados más compactos. 305 | 306 | **Resumen de signaturas** 307 | ```sql 308 | pgr_dijkstraCost(edges_sql, start_vid, end_vid) 309 | pgr_dijkstraCost(edges_sql, start_vid, end_vid [, directed]) 310 | pgr_dijkstraCost(edges_sql, start_vid, end_vids [, directed]) 311 | pgr_dijkstraCost(edges_sql, start_vids, end_vid [, directed]) 312 | pgr_dijkstraCost(edges_sql, start_vids, end_vids [, directed]) 313 | 314 | RETURNS SET OF (start_vid, end_vid, agg_cost) 315 | OR EMPTY SET 316 | ``` 317 | 318 | #### 3.2.1 Ejercicio 5 - Calcular el costo total entre varios orígenes y varios destinos 319 | ```sql 320 | SELECT * FROM pgr_dijkstraCost('SELECT gid as id, 321 | source, 322 | target, 323 | length_m / 5.56 / 60 AS cost 324 | FROM ruteoamg.ways', 325 | ARRAY[67272, 133535], ARRAY[1017, 55134], 326 | directed := false) 327 | ``` 328 | #### 3.2.2 Ejercicio 6 - Resumen de los costos totales por origen entre varios orígenes y varios destinos 329 | En este caso tenemos que utilizar la clausula `GROUP BY` para obtener los resultados agrupados para cada origen. 330 | ```sql 331 | SELECT start_vid, sum(agg_cost) as tiempo_total 332 | FROM pgr_dijkstraCost('SELECT gid as id, 333 | source, 334 | target, 335 | length_m / 5.56 / 60 AS cost 336 | FROM ruteoamg.ways', 337 | ARRAY[67272, 133535], ARRAY[1017, 55134], 338 | directed := false) 339 | GROUP BY start_vid 340 | ORDER BY start_vid; 341 | ``` 342 | 343 | ¿En cuál de los dos hoteles conviene más hospedarse? 344 | 345 | ## **4. Funciones Avanzadas de Ruteo** 346 | 347 | ### **4.1 Ruteo para vehículos** 348 | Una consulta para ruteo de vehículos es diferente a una para peatones: 349 | * Los segmentos de la red de carreteras suelen considerarse "dirigidos" (pueden tener limitaciones en cuanto al sentido en el que pueden recorrerse) 350 | * El costo puede ser: 351 | * Distancia 352 | * Tiempo 353 | * Dinero 354 | * Emisiones de CO2 355 | * Desgaste del vehículo, etc. 356 | * El atributo reverse_cost debe tenerse en cuenta en vias de doble sentido 357 | * El costo tiene que tener las mismas unidades que el atributo cost 358 | * cost y reverse_cost pueden ser diferentes (Esto es debido a que existen vias de sentido único) 359 | 360 | Dependiendo de la geometría, la forma válida: 361 | * segmento (origen, destino) (`cost >= 0` y `reverse_cost` < 0) 362 | * segmento (destino, origen) (`cost < 0` y `reverse_cost` >= 0) 363 | 364 | De manera que un "sentido contrario" se indica mediante un valor negativo y no es insertado en el grafo para su procesamiento. 365 | 366 | Para vías de doble sentido `cost >= 0` y `reverse_cost >= 0` y sus valores pueden ser diferentes. Por ejemplo, es más rápido ir cuesta abajo en una carretera en pendiente. En general `cost` y `reverse_cost` no tienen porque ser distancias, en realidad pueden ser casi cualquier cosa, por ejemplo: tiempo, pendiente, superficie, tipo de carretera, o una combinación de varios parámetros. 367 | 368 | #### 4.1.1 Ejercicio 7 - Ruteo para vehículos - Ida 369 | Desde el Hotel Portobello al CUCSH: 370 | * El vehículo va desde el nodo 67272 al nodo 1017 371 | * Usaremos los atributos `cost` y `reverse_cost` que están en grados. 372 | 373 | ```sql 374 | SELECT * FROM pgr_dijkstra('SELECT gid as id, 375 | source, 376 | target, 377 | cost, 378 | reverse_cost 379 | FROM ruteoamg.ways', 380 | 67272, 1017, 381 | directed := true); 382 | ``` 383 | 384 | ```sql 385 | WITH ruta as (SELECT * FROM pgr_dijkstra( 386 | 'SELECT gid as id, 387 | source, 388 | target, 389 | cost, 390 | reverse_cost 391 | FROM ruteoamg.ways', 392 | 67272, 1017, 393 | directed := true)) 394 | SELECT ruta.*, b.the_geom 395 | FROM ruta 396 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 397 | ``` 398 | 399 | #### 4.1.2 Ejercicio 8 - Ruteo para vehículos - Regreso 400 | Desde el CUCSH al Hotel Portobello: 401 | * El vehículo va desde el nodo 1017 al nodo 67272 402 | * Usaremos los atributos `cost` y `reverse_cost` que están en grados. 403 | 404 | ```sql 405 | SELECT * FROM pgr_dijkstra('SELECT gid as id, 406 | source, 407 | target, 408 | cost, 409 | reverse_cost 410 | FROM ruteoamg.ways', 411 | 1017, 67272, 412 | directed := true); 413 | ``` 414 | 415 | ```sql 416 | WITH ruta as (SELECT * FROM pgr_dijkstra( 417 | 'SELECT gid as id, 418 | source, 419 | target, 420 | cost, 421 | reverse_cost 422 | FROM ruteoamg.ways', 423 | 1017, 67272, 424 | directed := true)) 425 | SELECT ruta.*, b.the_geom 426 | FROM ruta 427 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 428 | ``` 429 | En un grafo dirigido las rutas de ida y retorno son casi siempre diferentes. 430 | 431 | #### 4.1.3 Ejercicio 9 - Ruteo para vehículos - Cuando el tiempo es oro 432 | Desde el CUCSH al Hotel Portobello: 433 | * El vehículo va desde el nodo 1017 al nodo 67272 434 | * El costo es `$1000 por hora` 435 | * Usaremos los atributos `cost_s` y `reverse_cost_s` que están en segundos. 436 | * La duración del viaje en horas es `cost_s / 3600` 437 | * El costo del viaje en pesos es `cost_s * 1000 / 3600` 438 | 439 | Los atributos `cost_s` y `reverse_cost_s` se calculan usando la longitud de un segmento de la red y los atributos `maxspeed_forward` y `maxspeed_backward` (velocidad máxima y velocidad máxima hacia atrás). Estas velocidades vienen definidas en el archivo de configuración que utilizamos junto con osm2pgrouting para importar los datos a nuestra base de datos. 440 | 441 | ```sql 442 | SELECT * FROM pgr_dijkstra('SELECT gid as id, 443 | source, 444 | target, 445 | cost_s * 1000 / 3600 as cost, 446 | reverse_cost_s * 1000 / 3600 as reverse_cost 447 | FROM ruteoamg.ways', 448 | 1017, 67272, 449 | directed := true); 450 | ``` 451 | 452 | ```sql 453 | WITH ruta as (SELECT * FROM pgr_dijkstra( 454 | 'SELECT gid as id, 455 | source, 456 | target, 457 | cost_s * 1000 / 3600 as cost, 458 | reverse_cost_s * 1000 / 3600 as reverse_cost 459 | FROM ruteoamg.ways', 460 | 1017, 67272, 461 | directed := true)) 462 | SELECT ruta.*, b.the_geom 463 | FROM ruta 464 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 465 | ``` 466 | Como podemos observar las rutas son idénticas y los costos son directamente proporcionales. 467 | 468 | ### **4.2 Manipulación de los costos** 469 | 470 | Cuando usamos datos de OSM importados a través de la herramienta osm2pgrouting, se crean algunas tablas adicionales, una de ellas se llama `configuration`. Esta tabla contiene los parámetros de importación que definimos en el archivo `mapconfig_for_cars_mod.xml`. Vamos a explorar los `tag_id` (identificador de etiqueta) de red. 471 | ```sql 472 | SELECT tag_id, tag_key, tag_value 473 | FROM ruteoamg.configuration 474 | ORDER BY tag_id; 475 | ``` 476 | ```sql 477 | SELECT * 478 | FROM ruteoamg.configuration; 479 | ``` 480 | #### 4.2.1 Ejercicio 10 - Ruteo para vehículos sin penalización 481 | 482 | Para modificar el comportamiento de los algoritmos vamos añadir una columna llamada `penalty` a la tabla `ruteoamg.configuration` y usarla para recalcular el costo de desplzamiento en función de unos criterios definidos por nosotros mismos. A continuación mostramos algunos ejemplos: 483 | 484 | ```sql 485 | ALTER TABLE ruteoamg.configuration ADD COLUMN penalty FLOAT; 486 | -- Sin penalización 487 | UPDATE ruteoamg.configuration SET penalty=1; 488 | ``` 489 | Ahora al calcular la ruta vamos a traer el atributo `penalty` haciendo un `join` con la tabla `ruteoamg.configuration` usando el campo `tag_id` que también está en la tabla `ruteoamg.ways` 490 | ```sql 491 | WITH ruta as (SELECT * FROM pgr_dijkstra(' 492 | SELECT gid AS id, 493 | source, 494 | target, 495 | cost_s * penalty AS cost, 496 | reverse_cost_s * penalty AS reverse_cost 497 | FROM ruteoamg.ways JOIN ruteoamg.configuration 498 | USING (tag_id)', 499 | 1017, 67272, 500 | directed := true)) 501 | SELECT ruta.*, b.the_geom 502 | FROM ruta 503 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 504 | ``` 505 | #### 4.2.2 Ejercicio 10 - Ruteo para vehículos con penalización 506 | 507 | Vamos a cambiar los valores de `penalty` para que algunos tipos de vía no sea utilizados: 508 | 509 | ```sql 510 | --- Vamos a modificar el penalty de las vías primarias 511 | UPDATE ruteoamg.configuration SET penalty=100 WHERE tag_value = 'primary'; 512 | ``` 513 | Ahora volvemos a calcular la ruta: 514 | ```sql 515 | WITH ruta as (SELECT * FROM pgr_dijkstra(' 516 | SELECT gid AS id, 517 | source, 518 | target, 519 | cost_s * penalty AS cost, 520 | reverse_cost_s * penalty AS reverse_cost 521 | FROM ruteoamg.ways JOIN ruteoamg.configuration 522 | USING (tag_id)', 523 | 1017, 67272, 524 | directed := true)) 525 | SELECT ruta.*, b.the_geom 526 | FROM ruta 527 | LEFT JOIN ruteoamg.ways b ON ruta.edge = b.gid; 528 | ``` 529 | ¿Qué ha cambiado con respecto al ejercicio 9? 530 | 531 | ### **4.3 Isocronas** 532 | Las isocronas son iso-lineas que unen puntos con el mismo tiempo de deslazamiento respecto a un origen. Para este ejercicio vamos a usar la Red Nacional de Caminos de INEGI que ya está precargada en la base de datos, una capa de hospitales públicos generada a partir de datos de la Secretaría de Salud ([Catálogo de Clave Única de Establecimientos de Salud - CLUES](http://www.dgis.salud.gob.mx/contenidos/sinais/s_clues.html)), y la función de pgRouting `pgr_drivingDistance`. 533 | 534 | #### 4.3.1 pgr_drivingDistance 535 | 536 | La función pgr_drivingDistance calcula la distancia manejando desde uno o varios nodos iniciales. Usando el algoritmo de Dijkstra extrae todos los nodos que tengan un costo de desplazamiento igual o menor al valor `distance`, este parámetro puede ser en unidades de tiempo o distancia (las mismas que el parámetro de costo que vayamos a utilizar). 537 | 538 | **Sumario de Signaturas** 539 | 540 | ```sql 541 | pgr_drivingDistance(edges_sql, start_vid, distance) -- Un único nodo de origen 542 | pgr_drivingDistance(edges_sql, start_vid, distance, directed) -- Un único nodo de origen dirigido 543 | pgr_drivingDistance(edges_sql, start_vids, distance, directed, equicost) -- Varios nodos de origen 544 | 545 | RETURNS SET OF (seq, [start_vid,] node, edge, cost, agg_cost) 546 | ``` 547 | El algoritmo retorna una sequencia, el nodo de inicio `start_vid,`, el nodo actual de la ruta `node`, el segmento recorrido `edge` para llegar al nodo actual, el costo y el costo acumulado desde `start_vid` a `node`. 548 | 549 | Para calcular el parámetro de costo `cost_s` en la RNC de INEGI podemos utilizar los atributos `longitud` y `velocidad`, donde `cost_s = longitud / (velocidad * 1000 / 3600)` para obtener el costo en segundos. 550 | 551 | ```sql 552 | ALTER TABLE ruteoinegi.red_vial ADD COLUMN cost_s FLOAT; 553 | ``` 554 | ```sql 555 | UPDATE ruteoinegi.red_vial 556 | SET cost_s = longitud / (velocidad * 1000 / 3600); 557 | ``` 558 | 559 | #### 4.3.2 Ejercicio 11 - Un solo origen 560 | Para este ejericio vamos a calcular la distancia de manejo desde el Hospital General Regional 46 del IMSS (`id_union = 635443`) y `distance = 3600` (en segundos). En parmetro de `id_union` lo hemos calculado previamente asignando el nodo más cercano de la RNC a todos los hospitales. Si observamos la tabla `ruteoinegi.red_vial` veremos que tiene dos atributos `id_union` y `distancia`, este segundo atributo nos indica la distancia lineal entre el punto donde está ubicado un hospital y el nodo más cercano de la red, que podemos encontrar en la tabla `ruteoinegi.union`. 561 | 562 | Para realizar este proceso hemos utilizado la consulta que aparece a continuación. En ella para cada hospital calculamos cual es el nodo de la red (tabla `ruteoinegi.union`) más cercano a cada hospital y la distancia a la que se encuentra. El atributo de la distancia nos da una idea de lo correcto que puede ser el cálculo de una ruta para cada hospital. Esto es necesario ya que la RNC, como ya habíamos comentado, no cuenta con toda la red de calles en las zonas urbanas y tampoco están algunos caminos/terracerías de zonas rurales. Este proceso de encontrar el nodo más cercano de la red lo vamos a tener que realizar siempre ya que para los parámetros de `start_vid` y `end_vid` de los diferentes algoritmos siempre tenemos que utilizar las IDs de los nodos de la red (en este caso es el atributo `id_union`) 563 | 564 | ```sql 565 | WITH foo as ( 566 | SELECT 567 | hospitales.id, 568 | closest_node.id_union, 569 | closest_node.dist 570 | FROM ruteoinegi.hospitales 571 | CROSS JOIN LATERAL -- Este CROSS JOIN lateral funciona como un bucle "for each" 572 | (SELECT 573 | id_union, 574 | ST_Distance("union".geom, hospitales.geom) as dist 575 | FROM ruteoinegi."union" 576 | ORDER BY ST_Distance("union".geom, hospitales.geom) 577 | LIMIT 1 -- Este limit 1 hace que solo obtengamos el nodo más cercano de la red 578 | ) AS closest_node) 579 | UPDATE ruteoinegi.hospitales -- Finalmente actualizamos la capa de hospitales 580 | SET id_union = foo.id_union, 581 | distancia = foo.dist 582 | FROM foo 583 | WHERE hospitales.id = foo.id 584 | ``` 585 | Ahora ya estamos listos para usar el algoritmo pgr_drivingDistance, a partir del que luego podemos generar las isocronas (utilizando QGIS). 586 | 587 | ```sql 588 | SELECT subquery.seq, 589 | subquery.node, 590 | subquery.edge, 591 | subquery.cost, 592 | subquery.aggcost, 593 | st_transform(pt.geom,4326) as geom -- Reproyectamos a epsg:4326 para poder ver el resultado en pgAdmin4 594 | FROM pgr_drivingDistance('SELECT 595 | id_red as id, 596 | union_ini AS source, 597 | union_fin AS target, 598 | cost_s AS cost 599 | FROM ruteoinegi.red_vial', 635443, 3600, false) 600 | subquery(seq, node, edge, cost, aggcost) 601 | JOIN ruteoinegi.union pt ON subquery.node = pt.id_union; 602 | ``` 603 | #### 4.3.3 Ejercicio 12 - Varios orígenes 604 | Para generar la distancias de manejo desde varios orígenes tenemos que volver a utilizar un `ARRAY`. En este caso concreto vamos a extraer todos los `id_union` desde la tabla `ruteoinegi.hospitales` y pasárselos a `pgr_drivingDistance`. Para que podamos generar las isocronas de todos los hospitales en conjunto tenemos que hacer dos procesos, primero calculamos la distancia de manejo desde cada hospital y posteriormente agrupamos los resulados por nodo (y geometría) y nos quedamos con el costo agregado mínimo que nos va a representar el tiempo mínimo desde ese nodo a un hospital. 605 | 606 | ```sql 607 | WITH ruteo as ( 608 | SELECT subquery.seq, 609 | subquery.start_v, 610 | subquery.node, 611 | subquery.edge, 612 | subquery.cost, 613 | subquery.aggcost, 614 | pt.geom 615 | FROM pgr_drivingDistance('SELECT 616 | id_red as id, 617 | union_ini AS source, 618 | union_fin AS target, 619 | cost_s AS cost 620 | FROM ruteoinegi.red_vial', 621 | array(SELECT id_union FROM ruteoinegi.hospitales), 1800, false) 622 | subquery(seq, start_v, node, edge, cost, aggcost) 623 | JOIN ruteoinegi.union pt ON subquery.node = pt.id_union) 624 | SELECT node, geom, min(aggcost) AS aggcost 625 | FROM ruteo 626 | GROUP By node, geom; 627 | ``` 628 | #### 4.3.4 Ejercicio 13 - isocronas 629 | 630 | Para generar las isocronas necesitamos el resultado del ejercicio anterior, podemos crear la tabla usando: 631 | 632 | ```sql 633 | CREATE TABLE public."elnombrequetuquieras" as ( 634 | WITH ruteo as ( 635 | SELECT subquery.seq, 636 | subquery.start_v, 637 | subquery.node, 638 | subquery.edge, 639 | subquery.cost, 640 | subquery.aggcost, 641 | pt.geom 642 | FROM pgr_drivingDistance('SELECT 643 | id_red as id, 644 | union_ini AS source, 645 | union_fin AS target, 646 | cost_s AS cost 647 | FROM ruteoinegi.red_vial', 648 | array(SELECT id_union FROM ruteoinegi.hospitales), 1800, false) 649 | subquery(seq, start_v, node, edge, cost, aggcost) 650 | JOIN ruteoinegi.union pt ON subquery.node = pt.id_union) 651 | SELECT node, geom, min(aggcost) AS aggcost 652 | FROM ruteo 653 | GROUP By node, geom); 654 | ``` 655 | 656 | Esto creará la tabla "elnombrequetuquieras" en el `schema public`. Para los siguientes pasos vamos a necesitar QGIS así que a continuación vamos a configurar la conexión a la base de datos dentro de QGIS. 657 | Para ello vamos al menú panel del navegador y en `PostGIS` creamos nueva conexión haciendo clic con el botón derecho. 658 | 659 | ![Base de datos](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/conexion_postgis.png) 660 | 661 | Ahora podemos cargar la tabla que acabamos de crear que estará en el `schema public`. 662 | 663 | ![Base de datos](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/bbdd_taller.png) 664 | 665 | Una vez cargada la capa vamos a exportar a un archivo local para poder trabajar con mayor comodidad sin depender de la conexión. Para ello hacemos clic con el botón derecho sobre la capa y la guardamos como un `geopackage`. 666 | 667 | ![img](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/qgis_iso_2.png) 668 | 669 | Para siguiente paso vamos a interpolar sobre la capa de puntos y generar una superficie de costo continua. Podemos utilizar la herramienta para generar TINs desde la caja de herramientas. 670 | ![img](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/qgis_iso_3.png) 671 | Configuramos la herramienta como se ve en la imagen. 672 | ![img](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/qgis_iso_4.png) 673 | El siguiente paso es generar las isolineas, en este caso, isocronas. Para ello vamos a usar la herramienta de `Curvas de nivel (Contours)` y la vamos a configurar como aparecen en la siguiente imagen. 674 | ![img](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/qgis_iso_6.png) 675 | 676 | El resultado final con un poco de diseño. 677 | ![img](https://raw.githubusercontent.com/RNanclares/Taller-pgRouting/master/imgs/qgis_iso_5.png) 678 | 679 | ### **4.4 Ruteo Masivo** 680 | 681 | Una de las capacidades más notables de pgRouting es la posibilidad de realizar operaciones con grandes cantidades de datos de forma extremadamente rápida. Por ejemplo, podemos contestar preguntas del tipo: ¿Cuáles centros de salud son los más cercanos a cada la localidad de Jalisco?¿Cuáles son los 3 hospitales más cercanos a cada centro de salud? ¿Cuáles localidades se encuentran a más de 30 minutos de una escuela?¿Cuál es la accesibilidad de las escuelas de todo un estado?¿Qué zonas de un estado tienen poca accesibilidad a un servicio (escuelas, centros de salud, taquerías, etc). 682 | 683 | #### 4.4.1 Ejercicio 14 - Hospitales más cercanos a localidades 684 | En este ejercicio vamos a utilizar la función `pgr_dijkstraCost` para analizar cuáles son los tres hospitales más cercanos a cada localidad de Jalisco. Para ello vamos a usar las tablas `ruteoinegi.hospitales` y `ruteoinegi.locpob`. De nuevo vamos a utilizar arreglos (`ARRAY`) para definir los parametros de los nodos de origen y destino del algoritmo `pgr_dijkstraCost`. Esto nos va a permitir analizar 10972 localidades contra 92 hospitales en unos pocos minutos (¡Más de 1,000,000 de combinacines posibles!). El tiempo de ejecución de la consulta es de 13 minutos 44 segundos en nuestro servidor. ¡¡¡Nada mal!!! Para no emplear demasiado tiempo vamos a limitar el número de localidades a analizar añadiendo un pequeño filtro al arreglo y seleccionando las localidaes con `id<1000`. 685 | ```sql 686 | CREATE TABLE ruteoinegi.localidadesVShospitales as (SELECT * 687 | FROM pgr_dijkstraCost( 688 | 'SELECT id, 689 | union_ini as source, 690 | union_fin as target, 691 | cost_s as cost 692 | FROM ruteoinegi.red_vial', 693 | array(SELECT id_union FROM ruteoinegi.locpob WHERE id<1000), 694 | array(SELECT id_union FROM ruteoinegi.hospitales), 695 | directed := false)); 696 | ``` 697 | Ahora vamos a seleccionar solo los tres hospitales más cercanos a cada localidad. 698 | 699 | ```sql 700 | CREATE TABLE ruteoinegi.locpobvshospitalesrank as ( SELECT foo.* FROM ( 701 | SELECT localidadesVShospitales.*, rank() over (partition BY start_vid ORDER BY agg_cost asc) 702 | FROM ruteoinegi.localidadesVShospitales) foo WHERE RANK <= 3); 703 | ``` 704 | 705 | El resultado de esta consulta es el costo de desplazamiento acumulado `agg_cost` desde cada localidad `start_vid` a todos los hospitales `end_vid`. Podemos mejorar la visualización de los resultados añadiendo los nombres de las localidades, los nombres de los hospitales y generando una linea usando `st_makeline` en caso de que queramos crear un mapa. 706 | ```sql 707 | CREATE TABLE ruteoinegi.matriz_locpobVShospitales as ( 708 | SELECT row_number() over (order by a.start_vid) as id, a.start_vid as nodo_inicio, b.nom_loc as localidad, 709 | a.end_vid as nodo_fin, c."nombre de la unidad" as hospital, 710 | c."nombre de tipologia" as tipologia_hosp, a."agg_cost" / 60 as tiempo_minutos, a."rank", 711 | st_makeline(b.geom,c.geom) 712 | FROM ruteoinegi.locpobvshospitalesrank a 713 | LEFT JOIN ruteoinegi.locpob b ON a.start_vid = b.id_union 714 | LEFT JOIN ruteoinegi.hospitales c ON a.end_vid = c.id_union); 715 | ``` 716 | -------------------------------------------------------------------------------- /imgs/bbdd_taller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/bbdd_taller.png -------------------------------------------------------------------------------- /imgs/conexion_postgis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/conexion_postgis.png -------------------------------------------------------------------------------- /imgs/ejercicio_1_pgadmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/ejercicio_1_pgadmin.png -------------------------------------------------------------------------------- /imgs/ejercicio_1_qgis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/ejercicio_1_qgis.png -------------------------------------------------------------------------------- /imgs/ejercicio_1_qgis_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/ejercicio_1_qgis_2.png -------------------------------------------------------------------------------- /imgs/pgadmin_geometry_viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/pgadmin_geometry_viewer.png -------------------------------------------------------------------------------- /imgs/qgis_iso_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/qgis_iso_1.png -------------------------------------------------------------------------------- /imgs/qgis_iso_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/qgis_iso_2.png -------------------------------------------------------------------------------- /imgs/qgis_iso_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/qgis_iso_3.png -------------------------------------------------------------------------------- /imgs/qgis_iso_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/qgis_iso_4.png -------------------------------------------------------------------------------- /imgs/qgis_iso_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/qgis_iso_5.png -------------------------------------------------------------------------------- /imgs/qgis_iso_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/qgis_iso_6.png -------------------------------------------------------------------------------- /imgs/qgis_iso_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnanclares/Taller-pgRouting/c1a1a65bc0ad270083e8aba5747c282a8db42d30/imgs/qgis_iso_7.png -------------------------------------------------------------------------------- /importacion_de_archivos_usando_osm2pgrouting.txt: -------------------------------------------------------------------------------- 1 | osm2pgrouting -f map.osm -c mapconfig_for_cars_mod.xml -d gislocal --schema ruteoamg -U username -W userpassword --clean --chunk 20000 2 | -------------------------------------------------------------------------------- /iscronas.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 1 6 | 1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 0 730 | 0 731 | 1 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 756 | 757 | 758 | 759 | 760 | 761 | 763 | 764 | 765 | 766 | 767 | 768 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 0 815 | 816 | 833 | 0 834 | generatedlayout 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | fid 847 | 848 | 1 849 | 850 | -------------------------------------------------------------------------------- /mapconfig_for_cars_mod.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------