Data

Pease clone repository or download data from https://github.com/U-Shift/shinytrips-aml/tree/master/data

  • MUNICIPIOSgeo.Rds : geometries of municipalities in Lisbon Metropolitan Area (AML), as polygons

  • TRIPSmodemunicipio.Rds : matrix of trips between municipalities, and separated by transportation mode, in long-format

MUNICIPIOSgeo = readRDS("data/MUNICIPIOSgeo.Rds")
TRIPS = readRDS("data/TRIPSmodemunicipio.Rds")

Centroids

You will need to get the centroids (center of mass) from the municipal geometries. There are two types of centroids: the geometric ones and the population or workplace weighted ones.

To calculate geometric centroids:

library(sf)
CENTROIDSgeo = st_centroid(MUNICIPIOSgeo)

library(mapview)
mapviewOptions(fgb = FALSE)
mapview(MUNICIPIOSgeo) + mapview(CENTROIDSgeo, col.regions = "black", legend = F)

How to calculate population weighted centroids, also known as mean coordinates?

Tip:

  • use Census 2011 database, at any smaller level than municipalities, and its population data.
  • check QGIS> Vector > Analysis Tools > Mean Coordinates. The ID should be the municipality.

Distance and time matrix

There are several routing softwares that can do this process. We will guide you through a free and opensource one, based on OpenStreetMap road network: openroute service.

First you will need to sign up and register for an API key (token).

QGIS

If you are familiar with QGIS, you can use the plugin ORS tools, which might be more intuitive for the following operations.

At settings, add your API Key. Batch Jobs > Matrix.

In this case we will use driving-car as the transportation mode.

You will get a matrix with the travel time (hours) and travel distance (km) between each centroid, that you can export in any format you’d like.

Keep in mind that your API key have a maximum number of requests per day and per minute.

This will not retrieve the routes (polylines) that were considered. If you need those, you’ll have to use other process: Batch Jobs > Points (2 Layers) > All-by-All.

R

You will need to install the openrouteservice-r package. See vignette for more information.

# remotes::install_github("GIScience/openrouteservice-r")
library(openrouteservice)
# ors_api_key("<your-api-key>")
# ors_api_key(Sys.getenv("ORS_API_KEY")) #if you have it stotred in usethis::edit_r_environ()

CENTROIDSgeo$coordinates = st_coordinates(CENTROIDSgeo) #list of xy coordinates

# compute time and distance, with routing profile
TRIPSmatrix = ors_matrix(
  CENTROIDSgeo$coordinates,
  profile = "driving-car",
  metrics = c("duration", "distance"),
  units = "km"
) 

TRIPSmatrix_dist = as.data.frame(TRIPSmatrix[["distances"]])
TRIPSmatrix_time = as.data.frame(TRIPSmatrix[["durations"]])
TRIPSmatrix_time = TRIPSmatrix_time/60 #in minutes

# row and colunm names
MUNICIPALITIES = CENTROIDSgeo$Concelho
colnames(TRIPSmatrix_dist) = MUNICIPALITIES
rownames(TRIPSmatrix_dist) = MUNICIPALITIES
colnames(TRIPSmatrix_time) = MUNICIPALITIES
rownames(TRIPSmatrix_time) = MUNICIPALITIES

This will give you a matrix in wide format as this:

Alcochete Almada Amadora Barreiro Cascais Lisboa Loures Mafra Moita Montijo Odivelas Oeiras Palmela Seixal Sesimbra Setubal Sintra Vila Franca de Xira
Alcochete 0.00 40.07 36.49 24.77 60.65 34.89 34.22 58.22 17.25 26.45 33.53 47.16 28.78 31.99 39.10 33.98 53.13 36.22
Almada 39.60 0.00 22.18 20.81 38.39 16.71 36.72 60.71 27.25 59.34 27.45 24.90 42.47 10.98 23.15 41.49 40.86 71.00
Amadora 37.87 22.41 0.00 41.32 27.12 8.62 18.16 42.16 43.38 59.50 8.89 9.21 57.88 25.19 38.85 59.80 19.57 51.76
Barreiro 25.81 21.34 33.79 0.00 50.00 28.32 49.87 73.87 6.99 45.54 49.19 36.51 30.84 13.26 19.27 29.86 52.47 57.21
Cascais 58.29 34.30 24.13 53.20 0.00 25.46 39.95 42.52 56.56 79.92 34.80 14.72 68.30 37.08 50.74 67.31 18.30 72.59
Lisboa 35.39 18.54 8.26 37.45 30.16 0.00 20.89 44.89 40.90 57.03 11.62 16.67 52.54 21.32 34.98 51.56 26.71 45.82
Loures 36.08 33.39 18.34 49.12 43.21 18.11 0.00 31.22 41.59 57.72 15.39 29.60 56.10 36.16 49.83 58.02 33.08 40.33
Mafra 60.10 57.40 42.36 73.14 38.82 42.12 31.89 0.00 65.61 81.73 35.29 47.88 80.11 60.18 73.84 82.04 25.51 66.49
Moita 17.84 28.63 44.18 7.00 61.11 39.43 41.91 65.91 0.00 37.58 41.22 47.62 21.09 20.55 27.66 24.12 60.82 49.24
Montijo 26.34 59.73 61.46 44.44 85.61 59.85 59.19 83.18 36.91 0.00 58.50 72.12 22.98 51.65 58.77 48.53 78.10 44.07
Odivelas 34.83 27.38 8.11 47.87 32.27 12.10 15.12 36.03 40.34 56.47 0.00 18.78 54.85 30.15 43.82 56.77 24.39 49.43
Oeiras 48.40 24.41 10.31 43.32 16.24 15.58 29.96 49.19 46.68 70.04 24.81 0.00 58.41 27.19 40.85 57.42 19.36 62.60
Palmela 32.36 43.95 66.90 30.81 72.65 50.97 64.63 88.63 21.15 22.97 63.94 59.16 0.00 33.31 40.17 18.44 75.12 63.76
Seixal 31.61 11.57 25.51 12.82 41.72 20.04 40.05 64.04 19.26 51.35 30.78 28.23 32.32 0.00 14.12 31.34 44.19 63.01
Sesimbra 38.65 23.88 39.14 19.25 55.35 33.67 53.68 77.67 26.30 58.39 44.41 41.86 45.58 14.08 0.00 31.52 57.82 70.05
Setubal 33.05 43.89 59.51 30.28 72.12 50.44 57.24 81.23 23.87 55.17 56.55 58.63 18.08 32.78 30.98 0.00 76.15 64.46
Sintra 54.64 40.27 21.11 59.17 15.08 26.77 32.85 24.27 60.16 76.28 27.70 20.33 74.26 43.04 56.71 76.58 0.00 65.49
Vila Franca de Xira 35.45 71.38 48.73 56.09 72.41 47.13 37.61 62.79 48.56 47.28 46.21 58.80 60.10 63.30 70.42 65.29 62.28 0.00

You also can put it back to long format as:

TRIPSmatrix_dist$Origin = rownames(TRIPSmatrix_dist)
TRIPSmatrix_time$Origin = rownames(TRIPSmatrix_time)

library(tidyr)
TRIPSdist = pivot_longer(
  TRIPSmatrix_dist,
  cols = !Origin,
  names_to = "Destination",
  values_to = "Distance"
)

TRIPStime = pivot_longer(
  TRIPSmatrix_time,
  cols = !Origin,
  names_to = "Destination",
  values_to = "Time"
)

And then use left_jointo join distance and time values to the original TRIPS table.

Origin Destination Distance Time
Alcochete Alcochete 0.00 0.00
Alcochete Almada 40.07 31.06
Alcochete Amadora 36.49 29.25
Alcochete Barreiro 24.77 21.44
Alcochete Cascais 60.65 43.92
Alcochete Lisboa 34.89 29.55
Alcochete Loures 34.22 29.34
Alcochete Mafra 58.22 48.40
Alcochete Moita 17.25 18.40
Alcochete Montijo 26.45 29.94
Alcochete Odivelas 33.53 29.65
Alcochete Oeiras 47.16 36.92
Alcochete Palmela 28.78 36.23
Alcochete Seixal 31.99 24.27
Alcochete Sesimbra 39.10 37.40
Alcochete Setubal 33.98 31.28
Alcochete Sintra 53.13 40.55
Alcochete Vila Franca de Xira 36.22 52.33
Almada Alcochete 39.60 28.67
Almada Almada 0.00 0.00

Easy, right? 😉

LS0tDQp0aXRsZTogIkRpc3RhbmNlIGFuZCBkdXJhdGlvbiBtYXRyaXggZm9yIHRyaXBzIg0KYXV0aG9yOiAiUiBGw6lsaXgiDQpkYXRlOiAiMjEvMDQvMjAyMSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiAzDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiMjIERhdGENCg0KUGVhc2UgY2xvbmUgW3JlcG9zaXRvcnldKGh0dHBzOi8vZ2l0aHViLmNvbS9VLVNoaWZ0L3NoaW55dHJpcHMtYW1sLykgb3IgZG93bmxvYWQgZGF0YSBmcm9tIDxodHRwczovL2dpdGh1Yi5jb20vVS1TaGlmdC9zaGlueXRyaXBzLWFtbC90cmVlL21hc3Rlci9kYXRhPg0KDQotICAgYE1VTklDSVBJT1NnZW8uUmRzYCA6IGdlb21ldHJpZXMgb2YgbXVuaWNpcGFsaXRpZXMgaW4gTGlzYm9uIE1ldHJvcG9saXRhbiBBcmVhIChBTUwpLCBhcyBwb2x5Z29ucw0KDQotICAgYFRSSVBTbW9kZW11bmljaXBpby5SZHNgIDogbWF0cml4IG9mIHRyaXBzIGJldHdlZW4gbXVuaWNpcGFsaXRpZXMsIGFuZCBzZXBhcmF0ZWQgYnkgdHJhbnNwb3J0YXRpb24gbW9kZSwgaW4gW2xvbmctZm9ybWF0XShodHRwczovL2RvY3Mucm9wZW5zY2kub3JnL3N0cGxhbnIvcmVmZXJlbmNlL29kX3RvX29kbWF0cml4Lmh0bWwpDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpNVU5JQ0lQSU9TZ2VvID0gcmVhZFJEUygiZGF0YS9NVU5JQ0lQSU9TZ2VvLlJkcyIpDQpUUklQUyA9IHJlYWRSRFMoImRhdGEvVFJJUFNtb2RlbXVuaWNpcGlvLlJkcyIpDQpgYGANCg0KIyMjIENlbnRyb2lkcw0KDQpZb3Ugd2lsbCBuZWVkIHRvIGdldCB0aGUgY2VudHJvaWRzIChjZW50ZXIgb2YgbWFzcykgZnJvbSB0aGUgbXVuaWNpcGFsIGdlb21ldHJpZXMuIFRoZXJlIGFyZSB0d28gdHlwZXMgb2YgY2VudHJvaWRzOiB0aGUgZ2VvbWV0cmljIG9uZXMgYW5kIHRoZSBwb3B1bGF0aW9uIG9yIHdvcmtwbGFjZSB3ZWlnaHRlZCBvbmVzLg0KDQpUbyBjYWxjdWxhdGUgKipnZW9tZXRyaWMgY2VudHJvaWRzKio6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHNmKQ0KQ0VOVFJPSURTZ2VvID0gc3RfY2VudHJvaWQoTVVOSUNJUElPU2dlbykNCg0KbGlicmFyeShtYXB2aWV3KQ0KbWFwdmlld09wdGlvbnMoZmdiID0gRkFMU0UpDQptYXB2aWV3KE1VTklDSVBJT1NnZW8pICsgbWFwdmlldyhDRU5UUk9JRFNnZW8sIGNvbC5yZWdpb25zID0gImJsYWNrIiwgbGVnZW5kID0gRikNCmBgYA0KDQpIb3cgdG8gY2FsY3VsYXRlICoqcG9wdWxhdGlvbiB3ZWlnaHRlZCBjZW50cm9pZHMqKiwgYWxzbyBrbm93biBhcyBtZWFuIGNvb3JkaW5hdGVzPw0KDQo+ICoqVGlwOioqDQo+DQo+IC0gICB1c2UgW0NlbnN1cyAyMDExIGRhdGFiYXNlXShodHRwOi8vbWFwYXMuaW5lLnB0L2Rvd25sb2FkL2luZGV4MjAxMS5waHRtbCksIGF0IGFueSBzbWFsbGVyIGxldmVsIHRoYW4gbXVuaWNpcGFsaXRpZXMsIGFuZCBpdHMgcG9wdWxhdGlvbiBkYXRhLg0KPiAtICAgY2hlY2sgW1FHSVNdKGh0dHBzOi8vd3d3LnFnaXMub3JnL2VuL3NpdGUvKVw+IFZlY3RvciBcPiBBbmFseXNpcyBUb29scyBcPiBNZWFuIENvb3JkaW5hdGVzLiBUaGUgSUQgc2hvdWxkIGJlIHRoZSBtdW5pY2lwYWxpdHkuDQoNCiMjIERpc3RhbmNlIGFuZCB0aW1lIG1hdHJpeA0KDQpUaGVyZSBhcmUgc2V2ZXJhbCByb3V0aW5nIHNvZnR3YXJlcyB0aGF0IGNhbiBkbyB0aGlzIHByb2Nlc3MuIFdlIHdpbGwgZ3VpZGUgeW91IHRocm91Z2ggYSBmcmVlIGFuZCBvcGVuc291cmNlIG9uZSwgYmFzZWQgb24gT3BlblN0cmVldE1hcCByb2FkIG5ldHdvcms6IFtvcGVucm91dGUgc2VydmljZV0oaHR0cHM6Ly9vcGVucm91dGVzZXJ2aWNlLm9yZy8pLg0KDQpGaXJzdCB5b3Ugd2lsbCBuZWVkIHRvIHNpZ24gdXAgYW5kIHJlZ2lzdGVyIGZvciBhbiBBUEkga2V5ICh0b2tlbikuDQoNCiMjIyBRR0lTDQoNCklmIHlvdSBhcmUgZmFtaWxpYXIgd2l0aCBRR0lTLCB5b3UgY2FuIHVzZSB0aGUgcGx1Z2luIFtPUlMgdG9vbHNdKGh0dHBzOi8vcGx1Z2lucy5xZ2lzLm9yZy9wbHVnaW5zL09SU3Rvb2xzLyksIHdoaWNoIG1pZ2h0IGJlIG1vcmUgaW50dWl0aXZlIGZvciB0aGUgZm9sbG93aW5nIG9wZXJhdGlvbnMuDQoNCkF0IHNldHRpbmdzLCBhZGQgeW91ciBBUEkgS2V5LiBCYXRjaCBKb2JzIFw+IE1hdHJpeC4NCg0KSW4gdGhpcyBjYXNlIHdlIHdpbGwgdXNlIGBkcml2aW5nLWNhcmAgYXMgdGhlIHRyYW5zcG9ydGF0aW9uIG1vZGUuDQoNCiFbXShtYXRyaXhfUUdJUzEucG5nKQ0KDQpZb3Ugd2lsbCBnZXQgYSBtYXRyaXggd2l0aCB0aGUgdHJhdmVsIHRpbWUgKGhvdXJzKSBhbmQgdHJhdmVsIGRpc3RhbmNlIChrbSkgYmV0d2VlbiBlYWNoIGNlbnRyb2lkLCB0aGF0IHlvdSBjYW4gZXhwb3J0IGluIGFueSBmb3JtYXQgeW91J2QgbGlrZS4NCg0KIVtdKG1hdHJpeF9RR0lTLnBuZykNCg0KPiBLZWVwIGluIG1pbmQgdGhhdCB5b3VyIEFQSSBrZXkgaGF2ZSBhIFttYXhpbXVtIG51bWJlciBvZiByZXF1ZXN0c10oaHR0cHM6Ly9vcGVucm91dGVzZXJ2aWNlLm9yZy9yZXN0cmljdGlvbnMvKSBwZXIgZGF5IGFuZCBwZXIgbWludXRlLg0KDQpUaGlzIHdpbGwgbm90IHJldHJpZXZlIHRoZSAqKnJvdXRlcyoqIChwb2x5bGluZXMpIHRoYXQgd2VyZSBjb25zaWRlcmVkLiBJZiB5b3UgbmVlZCB0aG9zZSwgeW91J2xsIGhhdmUgdG8gdXNlIG90aGVyIHByb2Nlc3M6IEJhdGNoIEpvYnMgXD4gUG9pbnRzICgyIExheWVycykgXD4gQWxsLWJ5LUFsbC4NCg0KIyMjIFINCg0KWW91IHdpbGwgbmVlZCB0byBpbnN0YWxsIHRoZSBgb3BlbnJvdXRlc2VydmljZS1yYCBwYWNrYWdlLiBTZWUgW3ZpZ25ldHRlXShodHRwczovL2dpc2NpZW5jZS5naXRodWIuaW8vb3BlbnJvdXRlc2VydmljZS1yL2FydGljbGVzL29wZW5yb3V0ZXNlcnZpY2UuaHRtbCkgZm9yIG1vcmUgaW5mb3JtYXRpb24uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJHSVNjaWVuY2Uvb3BlbnJvdXRlc2VydmljZS1yIikNCmxpYnJhcnkob3BlbnJvdXRlc2VydmljZSkNCiMgb3JzX2FwaV9rZXkoIjx5b3VyLWFwaS1rZXk+IikNCiMgb3JzX2FwaV9rZXkoU3lzLmdldGVudigiT1JTX0FQSV9LRVkiKSkgI2lmIHlvdSBoYXZlIGl0IHN0b3RyZWQgaW4gdXNldGhpczo6ZWRpdF9yX2Vudmlyb24oKQ0KDQpDRU5UUk9JRFNnZW8kY29vcmRpbmF0ZXMgPSBzdF9jb29yZGluYXRlcyhDRU5UUk9JRFNnZW8pICNsaXN0IG9mIHh5IGNvb3JkaW5hdGVzDQoNCiMgY29tcHV0ZSB0aW1lIGFuZCBkaXN0YW5jZSwgd2l0aCByb3V0aW5nIHByb2ZpbGUNClRSSVBTbWF0cml4ID0gb3JzX21hdHJpeCgNCiAgQ0VOVFJPSURTZ2VvJGNvb3JkaW5hdGVzLA0KICBwcm9maWxlID0gImRyaXZpbmctY2FyIiwNCiAgbWV0cmljcyA9IGMoImR1cmF0aW9uIiwgImRpc3RhbmNlIiksDQogIHVuaXRzID0gImttIg0KKSANCg0KVFJJUFNtYXRyaXhfZGlzdCA9IGFzLmRhdGEuZnJhbWUoVFJJUFNtYXRyaXhbWyJkaXN0YW5jZXMiXV0pDQpUUklQU21hdHJpeF90aW1lID0gYXMuZGF0YS5mcmFtZShUUklQU21hdHJpeFtbImR1cmF0aW9ucyJdXSkNClRSSVBTbWF0cml4X3RpbWUgPSBUUklQU21hdHJpeF90aW1lLzYwICNpbiBtaW51dGVzDQoNCiMgcm93IGFuZCBjb2x1bm0gbmFtZXMNCk1VTklDSVBBTElUSUVTID0gQ0VOVFJPSURTZ2VvJENvbmNlbGhvDQpjb2xuYW1lcyhUUklQU21hdHJpeF9kaXN0KSA9IE1VTklDSVBBTElUSUVTDQpyb3duYW1lcyhUUklQU21hdHJpeF9kaXN0KSA9IE1VTklDSVBBTElUSUVTDQpjb2xuYW1lcyhUUklQU21hdHJpeF90aW1lKSA9IE1VTklDSVBBTElUSUVTDQpyb3duYW1lcyhUUklQU21hdHJpeF90aW1lKSA9IE1VTklDSVBBTElUSUVTDQpgYGANCg0KVGhpcyB3aWxsIGdpdmUgeW91IGEgbWF0cml4IGluIHdpZGUgZm9ybWF0IGFzIHRoaXM6DQoNCmBgYHtyIGVjaG89RkFMU0V9DQprbml0cjo6a2FibGUoVFJJUFNtYXRyaXhfZGlzdCkNCmBgYA0KDQpZb3UgYWxzbyBjYW4gcHV0IGl0IGJhY2sgdG8gbG9uZyBmb3JtYXQgYXM6DQoNCmBgYHtyfQ0KVFJJUFNtYXRyaXhfZGlzdCRPcmlnaW4gPSByb3duYW1lcyhUUklQU21hdHJpeF9kaXN0KQ0KVFJJUFNtYXRyaXhfdGltZSRPcmlnaW4gPSByb3duYW1lcyhUUklQU21hdHJpeF90aW1lKQ0KDQpsaWJyYXJ5KHRpZHlyKQ0KVFJJUFNkaXN0ID0gcGl2b3RfbG9uZ2VyKA0KICBUUklQU21hdHJpeF9kaXN0LA0KICBjb2xzID0gIU9yaWdpbiwNCiAgbmFtZXNfdG8gPSAiRGVzdGluYXRpb24iLA0KICB2YWx1ZXNfdG8gPSAiRGlzdGFuY2UiDQopDQoNClRSSVBTdGltZSA9IHBpdm90X2xvbmdlcigNCiAgVFJJUFNtYXRyaXhfdGltZSwNCiAgY29scyA9ICFPcmlnaW4sDQogIG5hbWVzX3RvID0gIkRlc3RpbmF0aW9uIiwNCiAgdmFsdWVzX3RvID0gIlRpbWUiDQopDQpgYGANCg0KQW5kIHRoZW4gdXNlIGBsZWZ0X2pvaW5gdG8gam9pbiBkaXN0YW5jZSBhbmQgdGltZSB2YWx1ZXMgdG8gdGhlIG9yaWdpbmFsIFRSSVBTIHRhYmxlLg0KDQpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNClRSSVBTZGlzdHRpbWUgPSBUUklQU2Rpc3QgJT4lIGxlZnRfam9pbihUUklQU3RpbWUpDQpUUklQU2Rpc3R0aW1lJFRpbWUgPSByb3VuZChUUklQU2Rpc3R0aW1lJFRpbWUsMikNCmtuaXRyOjprYWJsZShUUklQU2Rpc3R0aW1lWzE6MjAsXSkNCmBgYA0KDQpFYXN5LCByaWdodD8g8J+YiQ0K