Archivo de la categoría: Programación

Municipios de Catalunya (Openlayers)

Hemos cogido del IDESCAT los datos de los municipios de más de 20.000 habitantes de Cataluña (una setantena de municipios), y los hemos geolocalizado.
En esta ocasión se quería practicar la selección de capas, y lo quería hacer todo con módulos (imports) de Javascript, y el código los más limpio posible.
Se puede ver el resultado en este video.

Colección de rutas BTT

Estoy programando con OpenLayers, últimamente he hecho los proyectos de Catalunya Mágica y Quintana. Aprovechando que en OpenLayers es fácil pintar una ruta GPX, quería poner todas las rutas BTT que he ido haciendo en un mismo mapa, i así dar una idea de la actividad deportiva. Bien, no todas las rutas son de BTT, también hay de senderismo, y tampoco son todas las rutas que he hecho, pero da una idea.
El siguiente paso será poner unas capas que se puedan activar/desactivar, para seleccionar BTT/senderismo, o bien por zona geográfica.

Cataluña Mágica

Se ha publicado la web de la Cataluña Mágica: demonios, infierno, brujas y diablos, con información extraída de OpenStreetMap.

Hemos utiltzado OpenLayers 6.5.0 (con el paradigma de imports), y los tiles estandard de OSM y también TRESC, una capa de visualización que me gusta mucho. Los datos que se obtienen pueden ser nodos o vías, y ha habido un trabajo previo de limpiar un poco los datos, pero todo ha sido un proceso muy rápido.

Referencias:

Segmentos de ciclismo con la API de Strava

De la API de Strava me interesa sobretodo la parte de segmentos, que son trozos de rutas donde la gent se cronometra y se publican los rankings. Sin una subscripción Premium no se puede acceder a toda la información. Jugando un rato con la API de Strava he llegado a poder ver los segmentos que hay en una zona geográfica, y poder ver los puntos de este segmento, que se pueden representar en cualquier mapa.

Para acceder a la información de un segmento:

$ curl -X GET https://www.strava.com/api/v3/segments/229781 -H 'Authorization: Bearer *******************'

Y el resultado:

{"id":229781,"resource_state":3,"name":"Hawk Hill","activity_type":"Ride","distance":2684.82,"average_grade":5.8,"maximum_grade":10.9,"elevation_high":247.2,"elevation_low":92.0,"start_latlng":[37.833112,-122.483436],"end_latlng":[37.828072,-122.498139],"elevation_profile":"https://d3o5xota0a1fcr.cloudfront.net/v6/charts/KO5P7GCFLK5P5NF5GNUX3D6IVFLIEYRYD6JRBEGMXHOCPENJUQZXD5IIEJOYINQE2HX3XLZMYRTF5GHG5JLQ====","start_latitude":37.833112,"start_longitude":-122.483436,"end_latitude":37.828072,"end_longitude":-122.498139,"climb_category":1,"city":"San Francisco","state":"CA","country":"United States","private":false,"hazardous":false,"starred":false,"created_at":"2009-09-21T20:29:41Z","updated_at":"2021-03-13T09:01:33Z","total_elevation_gain":155.2,

"map":{"id":"s229781","polyline":"}g|eFnpqjVl@En@Md@HbAd@d@^h@Xx@VbARjBDh@OPQf@w@d@k@XKXDFPF\\CbGT`AV`@v@|@NTNb@?XOb@cAxAWLuE@eAFMBoAv@eBt@q@b@}@tAeAt@i@dAC`AFZj@dBA~@Yh@MbAVn@b@b@\\d@Ef@Qd@_@d@eB|@c@h@YfBI|AMpA?VF\\\\t@f@t@h@j@|@b@hCb@b@XTd@Bl@GtA?jAL`ALp@Tr@RXd@Rx@Pn@^Zh@Tx@Zf@`@FTCzDy@f@Yx@m@n@Op@VJr@","resource_state":3},

"effort_count":556805,"athlete_count":49100,"star_count":5022,"athlete_segment_stats":{"pr_elapsed_time":null,"pr_date":null,"pr_activity_id":null,"effort_count":0},"xoms":{"kom":"5:37","qom":"6:42","destination":{"href":"strava://segments/229781/leaderboard","type":"overall","name":"All-Time"}},"local_legend":{"athlete_id":54312907,"title":"Dalton Nonweiler","profile":"https://dgalywyr863hv.cloudfront.net/pictures/athletes/54312907/14907176/5/large.jpg","effort_description":"86 efforts in the last 90 days","effort_count":"86","effort_counts":{"overall":"86 efforts","female":"41 efforts"},"destination":"strava://segments/229781/local_legend?categories%5B%5D=overall"}}

Vemos que la polilinea está codificada en una cadena de texto. La podemos decodificar y obtener las coordenadas:

$ pip3 install polyline

$ python3
>> import polyline
>> #polyline.decode(summary_polyline)
>> polyline.decode("gxu{Fem|Kc@fAq@fAoApAMR[ZMXw@v@a@VQZi@j@sAhAk@hA_@f@oDxCiAhA_@j@a@~@{@pCC`@EFD^H`B?`@BNNZV@JGDMEqA@q@H]NOFSLo@t@eB^i@d@i@t@m@n@Yz@EHBR\\`AdDFdEJ`@RNHIVGf@ATH`At@VXDl@GJU@WY[e@YSKC_@GgA@YMKMc@eBYeBSWg@AUTSVa@|@[tAAPDb@Nr@@TCb@[bACd@BNJ^Xh@N^BNB`@Nn@DJTNZ\\Rn@?\\c@zAA^B\\JZHHh@NZDVHTRHJBNAb@I|@B\\Th@PTVf@F`@Cl@Sj@WJ{Ae@{@OKBSPOr@IvA[z@UTw@JWE_@]QUUKKCu@Hs@NMEIIi@kAOUGE]OUMGIGY?y@XiB@]E]U[SGU?c@XcA`Aw@|@i@~@WbASPK?IC_Au@q@_@SAI@g@Lo@XK?YB[MYOQSQYi@Y_@AK@CDg@X]Bg@KUMi@e@[k@GU@MHQb@a@DIFU@WIYg@u@]y@IqAP{ABq@Is@e@iB}@wAw@oBk@aAa@g@g@w@Km@Q{AYkB_AmCc@eB?]H]NWxAiBZq@JYFm@@m@C[G]OWa@k@eAu@o@k@iBkCm@k@SKc@CK@]LS@e@]]]E@GDEH@`@PZJd@CHEFI?OSMe@Uk@")

[(41.40436, 2.11683), (41.40454, 2.11647), (41.40479, 2.11611), (41.40519, 2.1157), (41.40526, 2.1156), (41.4054, 2.11546), (41.40547, 2.11533), 
...
(41.41868, 2.11667), (41.41859, 2.11653), (41.41853, 2.11634), (41.41855, 2.11629), (41.41858, 2.11625), (41.41863, 2.11625), (41.41871, 2.11635), (41.41878, 2.11654), (41.41889, 2.11676)]

Referències:

Gráficos interactivos con Jupyter Notebooks: tiro parabólico

Ahora que ya he empezado a hacer pruebas con Jupyter Notebook, el siguiente paso que quiero probar es hacer gráficos interactivos. Pues ha resultado ser más fácil y rápido de lo que pensaba.

Partimos de un script python que simula los datos experimentales de una trayectoria parabólica, y calcula la parábola que mejor se ajusta (regresión cuadrática). A partir de la ecuación de la parábola se puede deducir la constante g (=9,81 m/s2). Simulamos los datos experimentales introduciendo un error de ruido en los datos teóricos. Pues bien, con mi gráfico interactivo puedo jugar con el nivel de ruido y con el número de puntos del muestreo, tal com se ve en el video. El código queda de la siguiente forma:

%matplotlib inline
from ipywidgets import interactive
import numpy as np
import pylab as plt
from IPython.display import display, Math, Latex

# tir parabòlic: y = vy*t - .5gt^2
vy = 10 # 10m/s
g = 9.81 # m/s^2
t_max = 2*vy/g

def f(nivell_soroll, num_punts):
    t = np.linspace(0, t_max, num_punts)
    y_or = vy*t - .5*g*t**2
    noise = np.random.normal(0, nivell_soroll, num_punts) # simulem dades experimentals
    y = y_or + noise

    # ajustament a una paràbola
    z = np.polyfit(t, y, 2)
    g_exp = 2.0*z[0]

    t_ = np.linspace(0, t_max, 100)
    y_ = z[0]*t_**2 + z[1]*t_ + z[2]

    fig, ax = plt.subplots()
    plt.plot(t_, y_, t, y, 'bo')
    plt.suptitle("Tir parabòlic. Regressió quadràtica")
    plt.title("y = " + str(round(z[0],3)) + "t^2 + " + str(round(z[1],3)) + "t + " + str(round(z[2],3)) + " -> g exp = " + str(round(g_exp,3)) + " m/s^2")
    ax.set(xlabel='temps (s)', ylabel='y (m)')
    ax.grid()
    plt.show()
    
interactive_plot = interactive(f, nivell_soroll=(0.0, 2.0),  num_punts=(10, 50))
interactive_plot

Otra cosa que me interesa es la manera de exportar estos Jupyter Notebooks a una web, conservando las gráficas y las fórmulas en format Latex (aunque se pierda la interacció con las gráficas). Esto se consigue directamente exportando el Notebook a html:

$ jupyter nbconvert --execute tir_parabolic.ipynb --to html

e integrando este código en una web donde haya más texto y explicaciones, y con la librería Bootstrap.

Introducción a Jupyter Notebooks

Ya hace tiempo que tenía ganas de mirar qué es esto de Jupyter Notebook, del que se está hablando mucho y cada vez se hablará más. Este es el primer ejemplo que hago, la solución de la ecuación de 2o grado con python y la librería sympy, que he cogido de una página que me gusta mucho: arachnoid.com.

El siguiente paso será mirar Jupyter Lab, que proporciona una interfície más moderna, y tengo ganas de jugar con widgets y sliders para poder hacer cosas más interactivas. Ahora que estoy programando bastante con Python para preparar ejemplos para las clases de CNED en la UPC, hacerlo con las Notebooks de Jupyter sería una manera muy puntera de hacer clases interactivas.

Referencias:

Extraer información de un track GPX

Estaba haciendo un pequeño script en NodeJS para extraer la información de mis tracks de BTT. Esta es la información básica que se puede extraer:

$ node xml_stats.js ../rutesgps/montnegreopenmtb_32.gpx
track: ../rutesgps/montnegreopenmtb_32.gpx
2020-11-05
29.4 Km (distància total)
02:25:40 (temps en moviment)
02:58:01 (temps total)
1121 m (desnivell acumulat)
886 m (desnivell acumulat amb filtre)
143m -> 650m (altitud min and max)

La dificultad principal está en entender qué significa y cómo se calcula el desnivel acumulado. Pongamos por ejemplo una excursión bastante plana com puede ser el Passeig de les Aigües de Barcelona. Teniendo en cuenta que la resolución de mi GPS es de 1 metro (sin decimales), el hecho de recorrer una superficie plana hace que se vaya acumulando los metros de desnivel de una manera exagerada (aunque el GPS tuviera más resolución también pasaría). Lo primero que haremos es recalcular las elevaciones de mi track con algún servicio online que dé las alturas exactas, y con algún decimal. Después, la solución pasa por filtrar los datos, hacer un suavizado, es decir, hacer una media. Por ejemplo, por cada punto cogemos el valor de la elevación haciendo la media entre el punto actual y los puntos anterior y posterior. Sería una media de tres puntos. Como cada punto se graba a los 4 segundos, sería una media de 12 segundos. Ahora bien, puedo hacer filtrados de 5, 7, 9 o 11 puntos, i da como resultado valores de pendient acumulado cada vez más pequeños. ¿Qué criterio seguir? El caso es que los diferentes sistemas (mi GPS, wikiloc, etc…) dan valores diferentes. Esto hace que medir la elevación acumulada en una excursión no sea una medida demasiado fiable. Este efecto no es demasiado problemático en las excursiones con bajadas y subidas largas y constantes. Por contra, con excursiones con zonas llanas y terreno irregular el efecto se acumula más.

Todos estos cálculos dan pie a programar otro script para calcular subidas donde podemos definir unas cronoescaladas, subidas que se van repitiendo en los entrenamientos y se podrá extraer información para comparar. De ello hablaremos en un próximo post…

La Casa de Papel: aplicación web

Este es un ejemplo que he hecho mano a mano con Pere, ahora que estamos confinados por la COVID-19 y vamos por la mitad de la 3a temporada de La Casa de Papel.

Para mi también ha servido para hacer una aplicación OpenLayers con código Node, que es la tendencia actual. Todo está explicado en la wiki y en los enlaces a los que se hace referencia.

La renderización que se ha hecho del mapa en blanco y negro es el estilo toner de Stamen. A mí particularmente me gusta mucho, y creo que hemos conseguido una buena integración con el diseño propio de la serie, que juega con los colores negro y rojo.

Enlaces:

Veguerías/regiones de Catalunya. Densidad de población

He estado programando unos scripts para fusionar fronteras (ver enlace). Ha sido un poco más difícil de lo que me pensaba, y me ha queddo un código Python bastante guarro, pero al final he logrado fusionar comarcas de Catalunya para obtener las regiones/veguerías. Ya miraré de arreglar el código cuando pueda, pues estudiar los diferentes casos es un problema de topología bastante interesante y no demasiado difícil. Pero ahora, debido al confinamiento provocado por el corona-virus y la COVID-19, este es el mejor código (y ya le he dedicado más horas de las que deseaba).

Todo esto viene porque estuve buscando el geojson de las veguerías y fui incapaz de encontrarlo. Este código también ha de servir para fusionar otros casos. Estoy pensando por ejemplo fusionar los municipios del Alt Berguedà.

Con todo esto he hecho como ejemplo la densidad de población, clasificado por veguerías/regiones.

Nota 31-03-2020: He mejorado el código y ahora ya estoy contento con el resultado final. Con la nueva versión he fusionado los municipios del Alt Berguedà.

GeoJSON: densidad de las comarcas de Cataluña

Tenía un tema pendiente con la visualización de mapas: programar aquellos mapas en que se ven las fronteras (ya sean países, regiones, comarcas, municipios), y donde podamos asignar un color o podamos clicar en cada una de las regiones.

En la API de Overpass se pueden hacer consultas de las relaciones a nivel administrativo, y normalmente se obtiene las ways que representan líneas de frontera administrativa. Esto ya va bien para pintar el contorno de un municipio, por ejemplo. Ahora bien, para sombrear toea una área necesitamos un polígono cerrado. Lo mejor es buscar una fuente de datos con formato GeoJSON de los datos que necesitamos, por ejemplo las comarcas. Buscando un poco puedes encontrar datos de todo: países, comunidades autónomas, provincias, etc. Combinando estos datos con la información que nos interesa (por ejemplo, la densidad de población por comarca), podemos hacer una aplicación como la que se muestra en la imagen.

Para hacerlo, está explicado en la wiki. Ahora que me he puesto con el GeoJSON, ahora veo que hay otro formato interesante que es el TopoJSON. Esto es un no parar!