
Tipos de terreno para todas mis carreras, desde NikeRunning, mostrado con JavaFX
Desde que comencé a correr he utilizado a NikeRunning.com de una u otra manera; Primero comencé con mi Itouch y ahora lo utilizó con el NikeWatch.
Sin embargo, el sitio de Nike es un poco fastidioso; El sitio web está escrito en Flash (lo cual lo hace pesado), tiene gráficos limitados pero lo mas inconveniente es que mis datos están cautivos en el sitio web. Según ellos ya llevan tiempo trabajando en una migración que eliminará Flash:
Hello Everyone,
Thank you all for your feedback.
To let everyone know, we are in the process of revamping our website. We are in transition to move away from flash to HTML. The goal is to be faster and more efficient.
We thank you for your patience as we update our sites. There is not an estimated time of when the transition will be fixed, but I can assure you that I’ve seen parts of the site and it’s looks and feels amazing.
Stay tuned!
Buscando un poco en Google me conseguí conque se puede obtener toda la información de las carreras (siempre y cuando estas esten marcadas como públicas) usando el siguiente URL:
1
| http://nikerunning.nike.com/nikeplus/v1/services/widget/get_public_run_list.jsp?userID=IDENTIFICADOR_DE_USUARIO |
El archivo resultante está en formato XML.
Pero, ¿y de donde obtenemos el identificador del usuario? Es sencillo, simplemente vaya a nikerunning.nike.com y una vez que entre con su usuario y clave al sitio web haga click sobre una de sus corridas. El URL resultante se verá como esto:
1
| http://nikerunning.nike.com/nikeos/p/nikeplus/en_US/plus/#//runs/gps/IDENTIFICADOR_DE_USUARIO/299193444/ |
En donde IDENTIFICADOR_DE_USUARIO es el número mágico que queremos usar.
Se me ocurrió por ejemplo escribir un pequeño programa en Jython y JavaFX el cual muestra la distribución de tipos de terreno encontrados mientras corrí:
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| #!/usr/bin/env jython
# Author: Jose Vicente Nunez Zuleta, josevnz@kodegeek.com
# This script parses the user run data and creates a simple distribtion by terrain types
# See: http://nikerunning.nike.com/nikeos/p/nikeplus/en_EMEA/plus/#//dashboard/
# Nike and Nikeplus/Nike+ are trademarks owned by Nike.
# License: BSD
import sys
from javafx.application import Application
from javafx.collections import FXCollections
from javafx.collections import ObservableList
from javafx.scene import Scene
from javafx.stage import Stage
from javafx.scene.chart import PieChart
from javafx.scene import Group
from com.kodegeek.fitness.nikerun.query import CannedQuery
from com.kodegeek.fitness.nikerun.public import PlusPublicService
class TerrainTypesChart(Application):
def __getData__(self):
args = self.getParameters().getRaw()
pServ = PlusPublicService(args[0])
data = pServ.getUserData()
dp = CannedQuery('string', data)
return dp.getTypesOfTerrain()
def start(self, stage):
scene = Scene(Group())
stage.setTitle("Terrain types found in all races")
stage.setWidth(500)
stage.setHeight(500)
count = self.__getData__()
data = [PieChart.Data(key, value) for (key, value) in count.iteritems()]
pieChartData = FXCollections.observableArrayList(data)
chart = PieChart(pieChartData)
chart.setTitle("Terrain types found")
scene.getRoot().getChildren().add(chart)
stage.setScene(scene)
stage.show()
if __name__ == "__main__":
Application.launch(TerrainTypesChart().class, sys.argv[1:]) |
La clase ‘PlusPublicService’ no es más que un simple cliente GET el cual se baja los datos del sitio de Nike:
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| !/usr/bin/env jython
#
# Author: Jose Vicente Nunez Zuleta, josevnz@kodegeek.com
# See: http://nikerunning.nike.com/nikeos/p/nikeplus/en_EMEA/plus/#//dashboard/
# Nike and Nikeplus/Nike+ are trademarks owned by Nike.
# License: BSD
#
from httplib import HTTPConnection, HTTP_PORT
import sys
__URL__ = 'nikerunning.nike.com'
__SERVICE__ = '/nikeplus/v1/services/widget/get_public_run_list.jsp?userID='
class PlusPublicService:
def __init__(self, userId, port = HTTP_PORT):
self.service = __SERVICE__ + userId
self.userId = userId
self.port = port
def getUserData(self, debug=0):
data = None
con = HTTPConnection(__URL__, self.port)
con.set_debuglevel(debug)
con.request('GET', self.service)
response = con.getresponse()
if response.status == 200:
data = response.read()
else:
raise Exception("Error. Status = %s, reason = %s" % (response.status, response.reason))
con.close()
return data
if __name__ == "__main__":
argv = sys.argv[1:]
if len(argv) > 0:
pServ = PlusPublicService(argv[0])
print "%s" % pServ.getUserData(8)
else:
print "NikeRunning user id is required!"
sys.exit(192) |
Y para procesar mis datos utilizo un poco de XPATH en la clase ‘CannedQuery’ la cual tiene un método el cual se encarga de crear la distribución para los distintos tipos de terreno:
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
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
| /usr/bin/env jython
# Author: Jose Vicente Nunez Zuleta, josevnz@kodegeek.com
# See: http://nikerunning.nike.com/nikeos/p/nikeplus/en_EMEA/plus/#//dashboard/
# Nike and Nikeplus/Nike+ are trademarks owned by Nike.
# License: BSD
from xml.dom.minidom import parse, parseString
from xml.etree import ElementTree
import sys
class DataParser(object):
def __init__(self, type='string'):
self.type = type
def __parseDomFile__(self, file):
return parse(open(file, 'r'))
def __parseDomString__(self, string):
return parseString(string)
def __parseXpathFile__(self, file):
return ElementTree.parse(file)
def __parseXpathString__(self, string):
return ElementTree.fromstring(string)
def __getDom__(self, source):
if self.type == 'string':
return self.__parseDomString__(source)
else:
return self.__parseDomFile__(source)
def __getTree__(self, source):
if self.type == 'string':
return self.__parseXpathString__(source)
else:
return self.__parseXpathFile__(source)
class CannedQuery(DataParser):
def __init__(self, type, source):
super(CannedQuery, self).__init__(type)
self.source = source
self.terrainMap = { '0': 'Not defined', '1': 'Road', '2': 'Trail', '3': 'Treadmill', '4': 'Track' }
def getTypesOfTerrain(self):
tree = self.__getTree__(self.source)
count = {}
for elem in tree.findall(".//terrain"):
key = 'Never collected'
if elem.text in self.terrainMap:
key = self.terrainMap[elem.text]
if key in count:
count[key] += 1
else:
count[key] = 1
return count
if __name__ == "__main__":
argv = sys.argv[1:]
if len(argv) > 0:
dp = CannedQuery('file', argv[0])
count = dp.getTypesOfTerrain()
print "%s" % count |
Pienso abrir un proyecto con todas estas clases de Python (Jython) en unos pocos días. Quiero agregarle un par de cosas más antes de soltarlas al aire libre
internet, java, javafx, kodegeek, vida sana
nikerunning xml webservices hacking javafx java
Comentarios recientes