From 86cb01527923ccaef3fb92371a9591d7cba5b048 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 25 Aug 2019 18:54:33 +0200 Subject: [PATCH] API - get gpx and chart data for an activity segment - #14 --- docs/_sources/api/activities.rst.txt | 4 +- docs/api/activities.html | 123 ++++++++++++++++ docs/http-routingtable.html | 10 ++ docs/objects.inv | Bin 750 -> 782 bytes docs/searchindex.js | 2 +- docsrc/source/api/activities.rst | 4 +- .../fittrackee_api/activities/activities.py | 138 +++++++++++++++++- .../fittrackee_api/activities/utils_gpx.py | 48 +++++- .../tests/test_activities_api_1_post.py | 73 +++++++++ 9 files changed, 394 insertions(+), 8 deletions(-) diff --git a/docs/_sources/api/activities.rst.txt b/docs/_sources/api/activities.rst.txt index 51b82b2b..14853645 100644 --- a/docs/_sources/api/activities.rst.txt +++ b/docs/_sources/api/activities.rst.txt @@ -11,4 +11,6 @@ Activities activities.delete_activity, activities.get_map, activities.get_activity_gpx, - activities.get_activity_chart_data + activities.get_activity_chart_data, + activities.get_segment_gpx, + activities.get_segment_chart_data diff --git a/docs/api/activities.html b/docs/api/activities.html index 824a9ecf..a60cf51d 100644 --- a/docs/api/activities.html +++ b/docs/api/activities.html @@ -958,6 +958,129 @@ +
+
+GET /api/activities/(int: activity_id)/gpx/segment/(int: segment_id)
+

Get gpx file for an activity segment displayed on map with Leaflet

+

Example request:

+
GET /api/activities/3/gpx/segment/0 HTTP/1.1
+Content-Type: application/json
+
+
+

Example response:

+
HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+  "data": {
+    "gpx": "gpx file content"
+  },
+  "message": "",
+  "status": "success"
+}
+
+
+
+
Parameters
+
    +
  • auth_user_id (integer) – authenticate user id (from JSON Web Token)

  • +
  • activity_id (integer) – activity id

  • +
  • segment_id (integer) – segment id

  • +
+
+
Request Headers
+
+
+
Status Codes
+
+
+
+
+ +
+
+GET /api/activities/(int: activity_id)/chart_data/segment/(int: segment_id)
+

Get chart data from an activity gpx file, to display it with Recharts

+

Example request:

+
GET /api/activities/3/chart/segment/0 HTTP/1.1
+Content-Type: application/json
+
+
+

Example response:

+
HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+  "data": {
+    "chart_data": [
+      {
+        "distance": 0,
+        "duration": 0,
+        "elevation": 279.4,
+        "latitude": 51.5078118,
+        "longitude": -0.1232004,
+        "speed": 8.63,
+        "time": "Fri, 14 Jul 2017 13:44:03 GMT"
+      },
+      {
+        "distance": 7.5,
+        "duration": 7380,
+        "elevation": 280,
+        "latitude": 51.5079733,
+        "longitude": -0.1234538,
+        "speed": 6.39,
+        "time": "Fri, 14 Jul 2017 15:47:03 GMT"
+      }
+    ]
+  },
+  "message": "",
+  "status": "success"
+}
+
+
+
+
Parameters
+
    +
  • auth_user_id (integer) – authenticate user id (from JSON Web Token)

  • +
  • activity_id (integer) – activity id

  • +
  • segment_id (integer) – segment id

  • +
+
+
Request Headers
+
+
+
Status Codes
+
+
+
+
+ diff --git a/docs/http-routingtable.html b/docs/http-routingtable.html index a50324e3..628f75ac 100644 --- a/docs/http-routingtable.html +++ b/docs/http-routingtable.html @@ -138,11 +138,21 @@ GET /api/activities/(int:activity_id)/chart_data + + + + GET /api/activities/(int:activity_id)/chart_data/segment/(int:segment_id) + GET /api/activities/(int:activity_id)/gpx + + + + GET /api/activities/(int:activity_id)/gpx/segment/(int:segment_id) + diff --git a/docs/objects.inv b/docs/objects.inv index 8799981b1471b7521da34d8627d50d594403526c..14af34ef395a4fb1ee7474ee332d8b8912178653 100644 GIT binary patch delta 669 zcmV;O0%HB{1&#)geSfudkq5Qgvk3QN6g4=(C;Z&9U4IaG?8^t|%I1gru^W~j3N zzWxMbvs-Akm&l%Z-^cb~&n9O*&sl-|H%ATwsS*2Q1xUVSZJ27 zL3%Mp29%Xf{uJMd(^TV~U4uHWhmKh1ji`8+(o`zps2H{K2p~pG4mttmBRaoU zhk%|JI7(P+SAP>^(Wo@Xo34Vk9A*_F#T##6>v_C<1(8F^amQw?iyBvmlTh&iL2gG) z4u|P^SH%Y!2axe4G&<*zPzYHjKXEP`8NJwfhh9ibtTq}sopfPz&sdMw-p~z>d!Cci zW085PKDKEa=gZ0H61vvLa2>O3@28Xh_8Q1r_`njz?tfM=Uu?@~w8GGpA3SukpU#$b z((<|OA_fmH`d?_k@>q(3(AB0!9;SKbA3$R9kAgg?k?PXCV8}&h47@AhJ$9I&vphc$ zl^Jz+pI!cTN51eWkRNuE1=l~42iKOom~l2;hD1Bv5b~loGN@Bnx&5}0d@ecpsfU^R zG4Co+y?-`TPC={uW~M~Vvo~P+b1b+%X2p+gmQ~QrMQqp^8ZGuwD}K_$EU9&%c;xsh zS@8=%$<$$+##DQ$p5q=nN3vFwEBoj#)F-UxyC_H1o$ZbWV6v!}4jXf&w)tmPL55O{ z=LUva2vl7$XJyMf^&KQbA;W#%6uMg#h7xj8rxsO#CA}_F2}@suTPU5M?@9Itr@x$0 D`?gCG delta 636 zcmV-?0)zdI2JQureSfW$J#X7E5QcaC3Ibi+ffc%X3kn3tP#{Rs*`URXL{t_9>Qu!4 zzWkIZQg-FWi$UJK??WAVq)XO|l2s`F2ox}o2C?7QfaE(_X1+CeYQN|Vr8+?)3oXhu zNH2#=6qEWlrre1R_kMmp2>#W9IkbRq0qv40oEj8epf)de(|=}sW}%*8)m+*!le^Di zWz{f+2hNb)r9c|-wk%)wANN_;fbs=TtS@)^=YJ1STj}J_@tr(O1>D%-)O!PT#HMUz zExMehQyE9csFNoEIbv4m1ei zzJe%$3%ucDoPUd2SBz6oivdAxM@C6F-?@k?nP)cog`iSoIq~%XG1AYj=C8p5p;KM~ai=E8o$dX-GKFPsvC9 zI=e3#fT^lkI$X?ewaY)V8VXc$Jhm`2QlfqpOU6&4)90WVN)>yIFymIeOeVb W(%UkXu}mXeLz(>CC&gd+v>*eMFEfY$ diff --git a/docs/searchindex.js b/docs/searchindex.js index 0590b191..3d126f7c 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["api/activities","api/auth","api/index","api/records","api/sports","api/stats","api/users","changelog","features","index","installation","troubleshooting/administrator","troubleshooting/index","troubleshooting/user"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,sphinx:56},filenames:["api/activities.rst","api/auth.rst","api/index.rst","api/records.rst","api/sports.rst","api/stats.rst","api/users.rst","changelog.md","features.rst","index.rst","installation.rst","troubleshooting/administrator.rst","troubleshooting/index.rst","troubleshooting/user.rst"],objects:{"":{"/api/activities":[0,1,1,"post--api-activities"],"/api/activities/(int:activity_id)":[0,3,1,"patch--api-activities-(int-activity_id)"],"/api/activities/(int:activity_id)/chart_data":[0,0,1,"get--api-activities-(int-activity_id)-chart_data"],"/api/activities/(int:activity_id)/gpx":[0,0,1,"get--api-activities-(int-activity_id)-gpx"],"/api/activities/map/(map_id)":[0,0,1,"get--api-activities-map-(map_id)"],"/api/activities/no_gpx":[0,1,1,"post--api-activities-no_gpx"],"/api/auth/login":[1,1,1,"post--api-auth-login"],"/api/auth/logout":[1,0,1,"get--api-auth-logout"],"/api/auth/picture":[1,1,1,"post--api-auth-picture"],"/api/auth/profile":[1,0,1,"get--api-auth-profile"],"/api/auth/profile/edit":[1,1,1,"post--api-auth-profile-edit"],"/api/auth/register":[1,1,1,"post--api-auth-register"],"/api/ping":[6,0,1,"get--api-ping"],"/api/records":[3,0,1,"get--api-records"],"/api/sports":[4,0,1,"get--api-sports"],"/api/sports/(int:sport_id)":[4,0,1,"get--api-sports-(int-sport_id)"],"/api/stats/(int:user_id)/by_sport":[5,0,1,"get--api-stats-(int-user_id)-by_sport"],"/api/stats/(int:user_id)/by_time":[5,0,1,"get--api-stats-(int-user_id)-by_time"],"/api/users":[6,0,1,"get--api-users"],"/api/users/(user_id)":[6,0,1,"get--api-users-(user_id)"],"/api/users/(user_id)/picture":[6,0,1,"get--api-users-(user_id)-picture"]}},objnames:{"0":["http","get","HTTP get"],"1":["http","post","HTTP post"],"2":["http","delete","HTTP delete"],"3":["http","patch","HTTP patch"]},objtypes:{"0":"http:get","1":"http:post","2":"http:delete","3":"http:patch"},terms:{"default":[0,5],"export":9,"float":0,"int":[0,4,5],"null":[0,1,6],"return":[0,3],"static":10,"true":6,"try":[0,1],NOT:[0,1,4],Not:[0,1,4,5,6],That:1,The:10,Use:7,_can_be_delet:4,accord:9,account:8,acit:0,activ:[2,5,7,9],activity_d:[0,3],activity_id:[0,3],adapt:10,add:[7,8,9],admin:[1,6,8,10],administr:[0,1,8,9,12],again:[0,1,3,4,5,6],all:[3,4,5,6,10],allow:[0,1,9],alpinequest:9,alreadi:1,also:[9,10],android:9,api:[0,1,3,4,5,6,8,9,10,11],app:9,applic:[0,1,3,4,5,6,9,10],arch:10,archiv:10,ascent:0,auth:[0,1,3,4,5,6],auth_token:1,auth_user_id:[0,3,4,5,6],authent:[0,2,3,4,5,6,9],author:[0,1,3,4,5,6],avail:3,ave_spe:0,ave_speed_from:0,ave_speed_to:0,averag:[0,3,8],backup:10,bad:[0,1],bearer:[0,1,3,4,5,6],befor:10,beta:10,bike:[0,4,8],bio:[1,6],biographi:1,birth:1,birth_dat:[1,6],bound:0,build:10,by_sport:5,by_tim:5,calcul:7,calendar:[7,8],can:[8,9,10],chang:[9,10],charact:[1,12],chart:[0,8,10],chart_data:0,check:[6,11],client:[10,11],clone:10,code:[0,1,3,4,5,6],column:12,com:[1,6,10],config:[10,11],confirm:1,contact:[0,1],content:[0,1,3,4,5,6],coordin:10,copi:10,correctli:[7,11],coverag:9,creat:[0,1,8],created_at:[1,6],creation:8,creation_d:0,credenti:1,current:7,custom:[10,11],cycl:[4,8],dark:10,darkski:8,dashboard:[7,9],data:[0,1,3,4,5,6,9,10,12],databas:10,date:[0,1,5,8],debian:10,delet:[0,1,8],depend:7,desc:0,descent:0,describ:10,detail:[6,7,9],develop:9,directori:10,disabl:[1,8,10],displai:[0,7,8,9,10],distanc:[0,3,7,8],distance_from:0,distance_to:0,document:9,doe:[0,5,6],don:1,download:10,durat:[0,3,7,8],duration_from:0,duration_to:0,dure:1,edit:[1,8],elev:[0,8,10],email:[1,6,10],encount:10,end:[0,5],endpoint:[2,6,12],enter:8,environn:11,error:[0,1,7],europ:[1,6],even:8,exampl:[0,1,3,4,5,6,9,10,11],exist:[0,1,5,6,9,11],exodu:9,expir:[0,1,3,4,5,6],extens:[0,1],fa33f4d996844a5c73ecd1ae24456ab8:0,fals:[0,1,4,6],farest:[3,8],featur:9,fetch:10,file:[0,1,7,8,9,10,11],filter:[7,8],first:1,first_nam:[1,6],fittracke:10,fittrackee_api:10,fix:9,flask:10,flaticon:10,follow:[3,10],forbidden:[0,1],forecast:10,form:[0,1],format:[0,1,5],former:10,forrunn:9,found:[0,1,4,5,6],frame:5,freepik:10,fri:0,from:[0,3,4,5,6,9,10],gener:10,get:[0,1,3,4,5,6],gif:1,git:10,github:10,gmt:[0,1,3,6],gpl:9,gpx:[0,7,8,9,10],gpxpy:10,have:0,header:[0,1,3,4,5,6],health:6,hike:[4,8],his:8,http:[0,1,3,4,5,6,10,11],icon:10,imag:[0,1,6,10],img:4,improv:9,incorrect:7,info:1,inform:9,initi:[10,11],instal:9,instanc:10,integ:[0,3,4,5,6],intern:[0,1],invalid:[0,1,3,4,5,6],issu:[9,10],jan:0,javascript:10,jpeg:6,jpg:1,json:[0,1,3,4,5,6,12],jul:[0,1,3,6],keep:9,kei:[8,10],label:4,last:[1,10],last_nam:[1,6],latitud:0,leaflet:[0,10],least:0,line:12,linux:10,list:9,local:9,localhost:[10,11],locat:[1,6],log:[0,1,3,4,5,6,9,10],login:[1,10],logout:1,longest:[3,8],longitud:0,made:[10,11],mai:10,make:10,makefil:[10,11],mandatori:[0,10],map:[0,8,9,10],map_id:0,match:1,max:0,max_alt:0,max_spe:0,max_speed_from:0,max_speed_to:0,maxim:0,maximum:[3,8],mean:11,messag:[0,1,6],min_alt:0,minim:[0,9],mobil:9,modification_d:0,mon:0,mondai:5,montain:8,month:[5,7,8],more:9,morn:0,mountain:4,move:[0,7],mpwoadmin:10,multipart:[0,1],must:[1,8],name:1,nb_activ:[1,5,6],nb_sport:[1,6],necessari:10,need:10,network:11,next_act:0,no_gpx:0,note:[0,7,8,9,10],now:[8,10],number:0,oauth:[0,1,3,4,5,6],object:[0,1],one:0,onli:[0,8,9],open:[9,10],order:0,other:10,out:1,outdoor:9,own:9,owner:8,packag:10,page:0,pagin:0,paramet:[0,1,3,4,5,6],pari:[1,6],pars:[10,12],part:[0,1],password:[1,10],password_conf:1,past:10,patch:0,paus:[0,7],payload:[0,1],per:0,per_pag:0,permiss:0,pg_dump:10,pictur:[1,6],ping:6,pleas:[0,1,3,4,5,6],png:[0,1,4],pong:6,possibl:9,post:[0,1],postgresql:10,prerequisit:9,previous_act:0,privai:9,product:[9,10],profil:1,proprietari:9,provid:[0,1,3,4,5,6,8],pull:10,python:[9,10],queri:[0,5],react:10,react_app_api_url:11,readi:[9,10],rebuild:11,rechart:[0,10],record:[0,2,7,8,9],record_typ:[0,3],redux:10,regist:[1,10],registr:[1,8,10],reinstal:10,relat:10,releas:[7,10],repo:10,report:9,repositori:10,request:[0,1,3,4,5,6,11],requir:1,respons:[0,1,3,4,5,6],restart:10,run:[4,8,10],runner:9,sam:[1,6],samr1:10,sat:6,search:7,second:0,see:[8,9,10,11],segment:[0,7],select:[0,1],serv:10,server:[0,1,9,10],sever:9,signatur:[0,1,3,4,5,6],singl:6,sky:10,some:[0,7,10],sorri:1,sort:0,speed:[0,3,8,10],sport:[0,2,5,8,9,10],sport_id:[0,3,4,5],standard:8,start:[0,5,10],stat:[5,7],staticmap:10,statist:[2,9],statu:[0,1,3,4,5,6],step:10,still:9,stop:10,store:9,street:9,string:[0,1,5],success:[0,1,3,4,5,6],successfulli:1,sun:[0,1,3,6],sundai:[0,5],support:[7,8],system:10,tab:11,tar:10,test:[9,10],them:9,thi:[0,7,9,10],thunderforest:10,time:[0,1,5,8],timezon:[1,6,7],titl:0,todo:[10,13],token:[0,1,3,4,5,6],tooltip:7,total:7,total_dist:[1,5,6],total_dur:[1,5,6],track:9,tracker:9,transport:[4,8],troubleshoot:9,type:[0,1,3,4,5,6],unauthor:[0,1,3,4,5,6],undefin:11,under:9,unexpect:12,updat:[0,1,7,10],upgrad:9,upload:[7,8,10],user:[0,1,2,3,4,5,7,8,9,10,12],user_id:[0,3,5,6],usernam:[1,6],using:[0,9],valid:[0,1,3,4,5,6],valu:[0,3,10],variabl:11,version:9,view:7,virtualenv:10,walk:[4,8],weather:[7,8,10],weather_end:0,weather_start:0,web:[0,1,3,4,5,6,9],week:5,weekm:5,were:7,wget:10,when:7,with_gpx:0,without:[0,5,8,9],workout:9,written:10,www:10,xzf:10,yarn:10,year:5,yet:[9,10],you:[0,9],your:9,zip:0,zone:1},titles:["Activities","Authentication","API documentation","Records","Sports","Statistics","Users","Change log","Features","FitTrackee","Installation","Administrator","Troubleshooting","User"],titleterms:{"new":7,activ:[0,8],administr:11,api:2,authent:1,bug:7,chang:7,charact:11,close:7,column:11,content:9,dashboard:8,data:11,detail:8,dev:10,document:2,environ:10,featur:[7,8],fittracke:9,fix:7,improv:7,instal:10,issu:7,json:11,line:11,list:8,log:7,minim:7,misc:7,pars:11,prerequisit:10,prod:10,record:3,sport:4,statist:[5,7,8],tabl:9,troubleshoot:12,unexpect:11,upgrad:10,user:[6,13],version:7,workout:8}}) \ No newline at end of file +Search.setIndex({docnames:["api/activities","api/auth","api/index","api/records","api/sports","api/stats","api/users","changelog","features","index","installation","troubleshooting/administrator","troubleshooting/index","troubleshooting/user"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,sphinx:56},filenames:["api/activities.rst","api/auth.rst","api/index.rst","api/records.rst","api/sports.rst","api/stats.rst","api/users.rst","changelog.md","features.rst","index.rst","installation.rst","troubleshooting/administrator.rst","troubleshooting/index.rst","troubleshooting/user.rst"],objects:{"":{"/api/activities":[0,1,1,"post--api-activities"],"/api/activities/(int:activity_id)":[0,3,1,"patch--api-activities-(int-activity_id)"],"/api/activities/(int:activity_id)/chart_data":[0,0,1,"get--api-activities-(int-activity_id)-chart_data"],"/api/activities/(int:activity_id)/chart_data/segment/(int:segment_id)":[0,0,1,"get--api-activities-(int-activity_id)-chart_data-segment-(int-segment_id)"],"/api/activities/(int:activity_id)/gpx":[0,0,1,"get--api-activities-(int-activity_id)-gpx"],"/api/activities/(int:activity_id)/gpx/segment/(int:segment_id)":[0,0,1,"get--api-activities-(int-activity_id)-gpx-segment-(int-segment_id)"],"/api/activities/map/(map_id)":[0,0,1,"get--api-activities-map-(map_id)"],"/api/activities/no_gpx":[0,1,1,"post--api-activities-no_gpx"],"/api/auth/login":[1,1,1,"post--api-auth-login"],"/api/auth/logout":[1,0,1,"get--api-auth-logout"],"/api/auth/picture":[1,1,1,"post--api-auth-picture"],"/api/auth/profile":[1,0,1,"get--api-auth-profile"],"/api/auth/profile/edit":[1,1,1,"post--api-auth-profile-edit"],"/api/auth/register":[1,1,1,"post--api-auth-register"],"/api/ping":[6,0,1,"get--api-ping"],"/api/records":[3,0,1,"get--api-records"],"/api/sports":[4,0,1,"get--api-sports"],"/api/sports/(int:sport_id)":[4,0,1,"get--api-sports-(int-sport_id)"],"/api/stats/(int:user_id)/by_sport":[5,0,1,"get--api-stats-(int-user_id)-by_sport"],"/api/stats/(int:user_id)/by_time":[5,0,1,"get--api-stats-(int-user_id)-by_time"],"/api/users":[6,0,1,"get--api-users"],"/api/users/(user_id)":[6,0,1,"get--api-users-(user_id)"],"/api/users/(user_id)/picture":[6,0,1,"get--api-users-(user_id)-picture"]}},objnames:{"0":["http","get","HTTP get"],"1":["http","post","HTTP post"],"2":["http","delete","HTTP delete"],"3":["http","patch","HTTP patch"]},objtypes:{"0":"http:get","1":"http:post","2":"http:delete","3":"http:patch"},terms:{"default":[0,5],"export":9,"float":0,"int":[0,4,5],"null":[0,1,6],"return":[0,3],"static":10,"true":6,"try":[0,1],NOT:[0,1,4],Not:[0,1,4,5,6],That:1,The:10,Use:7,_can_be_delet:4,accord:9,account:8,acit:0,activ:[2,5,7,9],activity_d:[0,3],activity_id:[0,3],adapt:10,add:[7,8,9],admin:[1,6,8,10],administr:[0,1,8,9,12],again:[0,1,3,4,5,6],all:[3,4,5,6,10],allow:[0,1,9],alpinequest:9,alreadi:1,also:[9,10],android:9,api:[0,1,3,4,5,6,8,9,10,11],app:9,applic:[0,1,3,4,5,6,9,10],arch:10,archiv:10,ascent:0,auth:[0,1,3,4,5,6],auth_token:1,auth_user_id:[0,3,4,5,6],authent:[0,2,3,4,5,6,9],author:[0,1,3,4,5,6],avail:3,ave_spe:0,ave_speed_from:0,ave_speed_to:0,averag:[0,3,8],backup:10,bad:[0,1],bearer:[0,1,3,4,5,6],befor:10,beta:10,bike:[0,4,8],bio:[1,6],biographi:1,birth:1,birth_dat:[1,6],bound:0,build:10,by_sport:5,by_tim:5,calcul:7,calendar:[7,8],can:[8,9,10],chang:[9,10],charact:[1,12],chart:[0,8,10],chart_data:0,check:[6,11],client:[10,11],clone:10,code:[0,1,3,4,5,6],column:12,com:[1,6,10],config:[10,11],confirm:1,contact:[0,1],content:[0,1,3,4,5,6],coordin:10,copi:10,correctli:[7,11],coverag:9,creat:[0,1,8],created_at:[1,6],creation:8,creation_d:0,credenti:1,current:7,custom:[10,11],cycl:[4,8],dark:10,darkski:8,dashboard:[7,9],data:[0,1,3,4,5,6,9,10,12],databas:10,date:[0,1,5,8],debian:10,delet:[0,1,8],depend:7,desc:0,descent:0,describ:10,detail:[6,7,9],develop:9,directori:10,disabl:[1,8,10],displai:[0,7,8,9,10],distanc:[0,3,7,8],distance_from:0,distance_to:0,document:9,doe:[0,5,6],don:1,download:10,durat:[0,3,7,8],duration_from:0,duration_to:0,dure:1,edit:[1,8],elev:[0,8,10],email:[1,6,10],encount:10,end:[0,5],endpoint:[2,6,12],enter:8,environn:11,error:[0,1,7],europ:[1,6],even:8,exampl:[0,1,3,4,5,6,9,10,11],exist:[0,1,5,6,9,11],exodu:9,expir:[0,1,3,4,5,6],extens:[0,1],fa33f4d996844a5c73ecd1ae24456ab8:0,fals:[0,1,4,6],farest:[3,8],featur:9,fetch:10,file:[0,1,7,8,9,10,11],filter:[7,8],first:1,first_nam:[1,6],fittracke:10,fittrackee_api:10,fix:9,flask:10,flaticon:10,follow:[3,10],forbidden:[0,1],forecast:10,form:[0,1],format:[0,1,5],former:10,forrunn:9,found:[0,1,4,5,6],frame:5,freepik:10,fri:0,from:[0,3,4,5,6,9,10],gener:10,get:[0,1,3,4,5,6],gif:1,git:10,github:10,gmt:[0,1,3,6],gpl:9,gpx:[0,7,8,9,10],gpxpy:10,have:0,header:[0,1,3,4,5,6],health:6,hike:[4,8],his:8,http:[0,1,3,4,5,6,10,11],icon:10,imag:[0,1,6,10],img:4,improv:9,incorrect:7,info:1,inform:9,initi:[10,11],instal:9,instanc:10,integ:[0,3,4,5,6],intern:[0,1],invalid:[0,1,3,4,5,6],issu:[9,10],jan:0,javascript:10,jpeg:6,jpg:1,json:[0,1,3,4,5,6,12],jul:[0,1,3,6],keep:9,kei:[8,10],label:4,last:[1,10],last_nam:[1,6],latitud:0,leaflet:[0,10],least:0,line:12,linux:10,list:9,local:9,localhost:[10,11],locat:[1,6],log:[0,1,3,4,5,6,9,10],login:[1,10],logout:1,longest:[3,8],longitud:0,made:[10,11],mai:10,make:10,makefil:[10,11],mandatori:[0,10],map:[0,8,9,10],map_id:0,match:1,max:0,max_alt:0,max_spe:0,max_speed_from:0,max_speed_to:0,maxim:0,maximum:[3,8],mean:11,messag:[0,1,6],min_alt:0,minim:[0,9],mobil:9,modification_d:0,mon:0,mondai:5,montain:8,month:[5,7,8],more:9,morn:0,mountain:4,move:[0,7],mpwoadmin:10,multipart:[0,1],must:[1,8],name:1,nb_activ:[1,5,6],nb_sport:[1,6],necessari:10,need:10,network:11,next_act:0,no_gpx:0,note:[0,7,8,9,10],now:[8,10],number:0,oauth:[0,1,3,4,5,6],object:[0,1],one:0,onli:[0,8,9],open:[9,10],order:0,other:10,out:1,outdoor:9,own:9,owner:8,packag:10,page:0,pagin:0,paramet:[0,1,3,4,5,6],pari:[1,6],pars:[10,12],part:[0,1],password:[1,10],password_conf:1,past:10,patch:0,paus:[0,7],payload:[0,1],per:0,per_pag:0,permiss:0,pg_dump:10,pictur:[1,6],ping:6,pleas:[0,1,3,4,5,6],png:[0,1,4],pong:6,possibl:9,post:[0,1],postgresql:10,prerequisit:9,previous_act:0,privai:9,product:[9,10],profil:1,proprietari:9,provid:[0,1,3,4,5,6,8],pull:10,python:[9,10],queri:[0,5],react:10,react_app_api_url:11,readi:[9,10],rebuild:11,rechart:[0,10],record:[0,2,7,8,9],record_typ:[0,3],redux:10,regist:[1,10],registr:[1,8,10],reinstal:10,relat:10,releas:[7,10],repo:10,report:9,repositori:10,request:[0,1,3,4,5,6,11],requir:1,respons:[0,1,3,4,5,6],restart:10,run:[4,8,10],runner:9,sam:[1,6],samr1:10,sat:6,search:7,second:0,see:[8,9,10,11],segment:[0,7],segment_id:0,select:[0,1],serv:10,server:[0,1,9,10],sever:9,signatur:[0,1,3,4,5,6],singl:6,sky:10,some:[0,7,10],sorri:1,sort:0,speed:[0,3,8,10],sport:[0,2,5,8,9,10],sport_id:[0,3,4,5],standard:8,start:[0,5,10],stat:[5,7],staticmap:10,statist:[2,9],statu:[0,1,3,4,5,6],step:10,still:9,stop:10,store:9,street:9,string:[0,1,5],success:[0,1,3,4,5,6],successfulli:1,sun:[0,1,3,6],sundai:[0,5],support:[7,8],system:10,tab:11,tar:10,test:[9,10],them:9,thi:[0,7,9,10],thunderforest:10,time:[0,1,5,8],timezon:[1,6,7],titl:0,todo:[10,13],token:[0,1,3,4,5,6],tooltip:7,total:7,total_dist:[1,5,6],total_dur:[1,5,6],track:9,tracker:9,transport:[4,8],troubleshoot:9,type:[0,1,3,4,5,6],unauthor:[0,1,3,4,5,6],undefin:11,under:9,unexpect:12,updat:[0,1,7,10],upgrad:9,upload:[7,8,10],user:[0,1,2,3,4,5,7,8,9,10,12],user_id:[0,3,5,6],usernam:[1,6],using:[0,9],valid:[0,1,3,4,5,6],valu:[0,3,10],variabl:11,version:9,view:7,virtualenv:10,walk:[4,8],weather:[7,8,10],weather_end:0,weather_start:0,web:[0,1,3,4,5,6,9],week:5,weekm:5,were:7,wget:10,when:7,with_gpx:0,without:[0,5,8,9],workout:9,written:10,www:10,xzf:10,yarn:10,year:5,yet:[9,10],you:[0,9],your:9,zip:0,zone:1},titles:["Activities","Authentication","API documentation","Records","Sports","Statistics","Users","Change log","Features","FitTrackee","Installation","Administrator","Troubleshooting","User"],titleterms:{"new":7,activ:[0,8],administr:11,api:2,authent:1,bug:7,chang:7,charact:11,close:7,column:11,content:9,dashboard:8,data:11,detail:8,dev:10,document:2,environ:10,featur:[7,8],fittracke:9,fix:7,improv:7,instal:10,issu:7,json:11,line:11,list:8,log:7,minim:7,misc:7,pars:11,prerequisit:10,prod:10,record:3,sport:4,statist:[5,7,8],tabl:9,troubleshoot:12,unexpect:11,upgrad:10,user:[6,13],version:7,workout:8}}) \ No newline at end of file diff --git a/docsrc/source/api/activities.rst b/docsrc/source/api/activities.rst index 51b82b2b..14853645 100644 --- a/docsrc/source/api/activities.rst +++ b/docsrc/source/api/activities.rst @@ -11,4 +11,6 @@ Activities activities.delete_activity, activities.get_map, activities.get_activity_gpx, - activities.get_activity_chart_data + activities.get_activity_chart_data, + activities.get_segment_gpx, + activities.get_segment_chart_data diff --git a/fittrackee_api/fittrackee_api/activities/activities.py b/fittrackee_api/fittrackee_api/activities/activities.py index af72fa41..9272f54e 100644 --- a/fittrackee_api/fittrackee_api/activities/activities.py +++ b/fittrackee_api/fittrackee_api/activities/activities.py @@ -16,7 +16,9 @@ from .utils import ( get_datetime_with_tz, process_files ) from .utils_format import convert_in_duration -from .utils_gpx import get_chart_data +from .utils_gpx import ( + ActivityGPXException, extract_segment_from_gpx_file, get_chart_data +) activities_blueprint = Blueprint('activities', __name__) @@ -345,7 +347,7 @@ def get_activity(auth_user_id, activity_id): return jsonify(response_object), code -def get_activity_data(auth_user_id, activity_id, data_type): +def get_activity_data(auth_user_id, activity_id, data_type, segment_id=None): """Get data from an activity gpx file""" activity = Activity.query.filter_by(id=activity_id).first() content = '' @@ -364,10 +366,21 @@ def get_activity_data(auth_user_id, activity_id, data_type): try: absolute_gpx_filepath = get_absolute_file_path(activity.gpx) if data_type == 'chart': - content = get_chart_data(absolute_gpx_filepath) + content = get_chart_data(absolute_gpx_filepath, segment_id) else: # data_type == 'gpx' with open(absolute_gpx_filepath, encoding='utf-8') as f: content = f.read() + if segment_id is not None: + content = extract_segment_from_gpx_file( + content, + segment_id + ) + except ActivityGPXException as e: + appLog.error(e.message) + response_object = {'status': e.status, + 'message': e.message} + code = 404 if e.status == 'not found' else 500 + return jsonify(response_object), code except Exception as e: appLog.error(e) response_object = {'status': 'error', @@ -505,6 +518,125 @@ def get_activity_chart_data(auth_user_id, activity_id): return get_activity_data(auth_user_id, activity_id, 'chart') +@activities_blueprint.route( + '/activities//gpx/segment/', + methods=['GET'] +) +@authenticate +def get_segment_gpx(auth_user_id, activity_id, segment_id): + """ + Get gpx file for an activity segment displayed on map with Leaflet + + **Example request**: + + .. sourcecode:: http + + GET /api/activities/3/gpx/segment/0 HTTP/1.1 + Content-Type: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "data": { + "gpx": "gpx file content" + }, + "message": "", + "status": "success" + } + + :param integer auth_user_id: authenticate user id (from JSON Web Token) + :param integer activity_id: activity id + :param integer segment_id: segment id + + :reqheader Authorization: OAuth 2.0 Bearer Token + + :statuscode 200: success + :statuscode 400: no gpx file for this activity + :statuscode 401: + - Provide a valid auth token. + - Signature expired. Please log in again. + - Invalid token. Please log in again. + :statuscode 404: activity not found + :statuscode 500: + + """ + return get_activity_data(auth_user_id, activity_id, 'gpx', segment_id) + + +@activities_blueprint.route( + '/activities//chart_data/segment/', + methods=['GET'] +) +@authenticate +def get_segment_chart_data(auth_user_id, activity_id, segment_id): + """ + Get chart data from an activity gpx file, to display it with Recharts + + **Example request**: + + .. sourcecode:: http + + GET /api/activities/3/chart/segment/0 HTTP/1.1 + Content-Type: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "data": { + "chart_data": [ + { + "distance": 0, + "duration": 0, + "elevation": 279.4, + "latitude": 51.5078118, + "longitude": -0.1232004, + "speed": 8.63, + "time": "Fri, 14 Jul 2017 13:44:03 GMT" + }, + { + "distance": 7.5, + "duration": 7380, + "elevation": 280, + "latitude": 51.5079733, + "longitude": -0.1234538, + "speed": 6.39, + "time": "Fri, 14 Jul 2017 15:47:03 GMT" + } + ] + }, + "message": "", + "status": "success" + } + + :param integer auth_user_id: authenticate user id (from JSON Web Token) + :param integer activity_id: activity id + :param integer segment_id: segment id + + :reqheader Authorization: OAuth 2.0 Bearer Token + + :statuscode 200: success + :statuscode 400: no gpx file for this activity + :statuscode 401: + - Provide a valid auth token. + - Signature expired. Please log in again. + - Invalid token. Please log in again. + :statuscode 404: activity not found + :statuscode 500: + + """ + return get_activity_data(auth_user_id, activity_id, 'chart', segment_id) + + @activities_blueprint.route('/activities/map/', methods=['GET']) def get_map(map_id): """ diff --git a/fittrackee_api/fittrackee_api/activities/utils_gpx.py b/fittrackee_api/fittrackee_api/activities/utils_gpx.py index 9c67b7f6..d8c2f8b9 100644 --- a/fittrackee_api/fittrackee_api/activities/utils_gpx.py +++ b/fittrackee_api/fittrackee_api/activities/utils_gpx.py @@ -121,7 +121,22 @@ def get_gpx_info(gpx_file, update_map_data=True, update_weather_data=True): return gpx_data, map_data, weather_data -def get_chart_data(gpx_file): +def get_gpx_segments(track_segments, segment_id=None): + if segment_id is not None: + if segment_id > (len(track_segments) - 1): + raise ActivityGPXException( + 'not found', + f'No segment with id \'{segment_id}\'', + None + ) + segments = [track_segments[segment_id]] + else: + segments = track_segments + + return segments + + +def get_chart_data(gpx_file, segment_id=None): gpx = open_gpx_file(gpx_file) if gpx is None: return None @@ -131,7 +146,10 @@ def get_chart_data(gpx_file): previous_point = None previous_distance = 0 - for segment_idx, segment in enumerate(gpx.tracks[0].segments): + track_segments = gpx.tracks[0].segments + segments = get_gpx_segments(track_segments, segment_id) + + for segment_idx, segment in enumerate(segments): for point_idx, point in enumerate(segment.points): if segment_idx == 0 and point_idx == 0: first_point = point @@ -161,3 +179,29 @@ def get_chart_data(gpx_file): previous_distance = distance return chart_data + + +def extract_segment_from_gpx_file(content, segment_id): + gpx_content = gpxpy.parse(content) + if len(gpx_content.tracks) == 0: + return None + + track_segment = get_gpx_segments( + gpx_content.tracks[0].segments, + segment_id + ) + + gpx = gpxpy.gpx.GPX() + gpx_track = gpxpy.gpx.GPXTrack() + gpx.tracks.append(gpx_track) + gpx_segment = gpxpy.gpx.GPXTrackSegment() + gpx_track.segments.append(gpx_segment) + + for point_idx, point in enumerate(track_segment[0].points): + gpx_segment.points.append( + gpxpy.gpx.GPXTrackPoint( + point.latitude, + point.longitude, + elevation=point.elevation)) + + return gpx.to_xml() diff --git a/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py b/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py index 1e184029..d6652422 100644 --- a/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py +++ b/fittrackee_api/fittrackee_api/tests/test_activities_api_1_post.py @@ -274,6 +274,21 @@ def activity_assertion(app, user_1, sport_1_cycling, gpx_file, with_segments): assert '' in data['message'] assert len(data['data']['gpx']) != '' + response = client.get( + '/api/activities/1/gpx/segment/0', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert '' in data['message'] + assert len(data['data']['gpx']) != '' + response = client.get( f'/api/activities/map/{map_id}', headers=dict( @@ -419,6 +434,36 @@ def test_get_chart_data_activty_with_gpx( assert data['message'] == '' assert data['data']['chart_data'] != '' + response = client.get( + '/api/activities/1/chart_data/segment/0', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert data['message'] == '' + assert data['data']['chart_data'] != '' + + response = client.get( + '/api/activities/1/chart_data/segment/999999', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 404 + assert 'not found' in data['status'] + assert data['message'] == 'No segment with id \'999999\'' + assert 'data' not in data + def test_get_chart_data_activty_with_gpx_different_user( app, user_1, user_2, sport_1_cycling, gpx_file @@ -467,6 +512,34 @@ def test_get_chart_data_activty_with_gpx_different_user( assert 'error' in data['status'] assert 'You do not have permissions.' in data['message'] + response = client.get( + '/api/activities/1/chart_data/segment/0', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 403 + assert 'error' in data['status'] + assert 'You do not have permissions.' in data['message'] + + response = client.get( + '/api/activities/1/chart_data/segment/999999', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 403 + assert 'error' in data['status'] + assert 'You do not have permissions.' in data['message'] + def test_add_an_activity_with_gpx_without_name( app, user_1, sport_1_cycling, gpx_file_wo_name