Archivo

Entradas Etiquetadas ‘nikerunning xml webservices hacking javafx java’

Hackeando mis datos en NikeRunning.com

Miércoles, 12 de octubre de 2011


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