Análisis de datos geográficos de archivos XML en Python -- python campo con parsing campo con xml camp codereview Relacionados El problema

Parsing Geographic Data From XML Files in Python


4
vote

problema

Español

Recientemente he estado trabajando en un programa que toma como entrada, los archivos de registro generados desde una aplicación en marcha / ciclismo. Estos archivos están en un formato XML que es increíblemente regular. En mi programa, primero divido toda la información geográfica y de tiempo, y la salida como un archivo de texto. Luego, leer este archivo de texto, las instancias de la clase de entrada se compilan en una lista. Usando estas entradas, se pueden lograr cálculos (y eventualmente gráficos y tendencias a largo plazo).

Este script de Python requiere que la biblioteca GEOPY se use, y se haya probado en Python3.

Mi principal preocupación es el poco del código en la naturaleza, con múltiples, si las declaraciones, lo que motionan la cuestión de la optimización. He cargado una copia de un archivo de entrada de ejemplo para el script en mi Github, que se puede encontrar aquí . Escribir '4-19-17A.BIN' sin las cotizaciones en el aviso comenzará a analizar, e imprimirá a la salida estándar la representación de la cadena de cada instancia de entrada que se creó, junto con un par de cálculos.


DATAPARSE.PY

  set_error_handler(function($errno ,$errstr,$errfile ,$errline, $errcontext) {     // When we use the error supressing operator (@)      // error_reporting is temporarily set to 0     // Do not log anything for that case     if(ini_get('error_reporting')==0){return;}       $halt = FALSE;     // http://php.net/manual/en/errorfunc.constants.php     switch($errno){         case E_USER_ERROR:         case E_RECOVERABLE_ERROR:             $halt = true;             $logType = 'error';             break;          case E_WARNING:         case E_USER_WARNING:         case E_STRICT:         case E_DEPRECATED:             $logType = 'warning';             break;          case E_NOTICE:         case E_USER_NOTICE:             $logType = 'notice';             break;              // The following error types cannot be caught by set_error_handler             // Adding them for reference only         case E_CORE_WARNING:         case E_COMPILE_ERROR:         case E_COMPILE_WARNING:         case E_CORE_ERROR:         case E_ERROR:         case E_PARSE:             return FALSE;          // We should never reach this case. But you never know         case E_ALL:         default:             $logType = 'warning';     }     Logger::{$logType}($errstr, $errcontext);     // Halt the execution for errors that should halt     if($halt)         exit($errno); }); 2  

entrada.py

  set_error_handler(function($errno ,$errstr,$errfile ,$errline, $errcontext) {     // When we use the error supressing operator (@)      // error_reporting is temporarily set to 0     // Do not log anything for that case     if(ini_get('error_reporting')==0){return;}       $halt = FALSE;     // http://php.net/manual/en/errorfunc.constants.php     switch($errno){         case E_USER_ERROR:         case E_RECOVERABLE_ERROR:             $halt = true;             $logType = 'error';             break;          case E_WARNING:         case E_USER_WARNING:         case E_STRICT:         case E_DEPRECATED:             $logType = 'warning';             break;          case E_NOTICE:         case E_USER_NOTICE:             $logType = 'notice';             break;              // The following error types cannot be caught by set_error_handler             // Adding them for reference only         case E_CORE_WARNING:         case E_COMPILE_ERROR:         case E_COMPILE_WARNING:         case E_CORE_ERROR:         case E_ERROR:         case E_PARSE:             return FALSE;          // We should never reach this case. But you never know         case E_ALL:         default:             $logType = 'warning';     }     Logger::{$logType}($errstr, $errcontext);     // Halt the execution for errors that should halt     if($halt)         exit($errno); }); 3  

main.py

  set_error_handler(function($errno ,$errstr,$errfile ,$errline, $errcontext) {     // When we use the error supressing operator (@)      // error_reporting is temporarily set to 0     // Do not log anything for that case     if(ini_get('error_reporting')==0){return;}       $halt = FALSE;     // http://php.net/manual/en/errorfunc.constants.php     switch($errno){         case E_USER_ERROR:         case E_RECOVERABLE_ERROR:             $halt = true;             $logType = 'error';             break;          case E_WARNING:         case E_USER_WARNING:         case E_STRICT:         case E_DEPRECATED:             $logType = 'warning';             break;          case E_NOTICE:         case E_USER_NOTICE:             $logType = 'notice';             break;              // The following error types cannot be caught by set_error_handler             // Adding them for reference only         case E_CORE_WARNING:         case E_COMPILE_ERROR:         case E_COMPILE_WARNING:         case E_CORE_ERROR:         case E_ERROR:         case E_PARSE:             return FALSE;          // We should never reach this case. But you never know         case E_ALL:         default:             $logType = 'warning';     }     Logger::{$logType}($errstr, $errcontext);     // Halt the execution for errors that should halt     if($halt)         exit($errno); }); 4  
Original en ingles

I have recently been working on a program which takes as input, log files generated from a running/biking app. These files are in an xml format which is incredibly regular. In my program, I first strip all of the geographic and time information, and output as a text file. Then, reading back this text file, instances of Entry class are compiled into a list. Using these entries, calculations (and eventually graphing and long term trends) can be achieved.

This python script requires the library geopy to use, and has been tested on Python3.

My main concern is how little of the code is pythonic in nature, with multiple if statements begging the question of optimization. I have uploaded a copy of an example input file for the script on my github, which can be found here. Typing '4-19-17a.bin' without the quotations at the prompt will begin parsing, and will print to standard output the string representation of each Entry instance that was created, along with a couple calculations.


dataparse.py

import xml.etree.ElementTree as ET   def processtimestring(time):     year = int(time[:4])     month = int(time[5:7])     day = int(time[8:10])     h = int(time[11:13])     m = int(time[14:16])     s = float(time[17:-6])     return [year, month, day, h, m, s]  def parsethedata():     time = []     lat = []     lng = []     alt = []     dist = []     garbage = '{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}'     filestring = input('The file to parse: ')     outputstring = filestring[:-4] + '.txt'          f = open(outputstring, 'w')          tree = ET.parse(filestring)     root = tree.getroot()          for child in root[0][0][1][4]:         for info in child:             if (not info.text):                 for position in info:                     f.write(position.tag.replace(garbage, '') + '\n')                     f.write(position.text + '\n')             else:                 f.write(info.tag.replace(garbage, '') + '\n')                 f.write(info.text + '\n')          f.close()          with open(outputstring) as f:         for i, line in enumerate(f):             if ((i+9) % 10 == 0):                 time.append(processtimestring(line[:-1]))             elif ((i+7) % 10 == 0):                 lat.append(float(line[:-1]))             elif ((i+5) % 10 == 0):                 lng.append(float(line[:-1]))             elif ((i+3) % 10 == 0):                 alt.append(float(line[:-1]))             elif ((i+1) % 10 == 0):                 dist.append(float(line[:-1]))     return [time, lat, lng, alt, dist] 

entry.py

import geopy.distance import datetime as dt   class Entry(object):     @staticmethod     def distancecalc(x1, x2):         coord_1 = (x1.pos[0], x1.pos[1])         coord_2 = (x2.pos[0], x2.pos[1])         return geopy.distance.vincenty(coord_1, coord_2).m      @staticmethod     def timecalc(x1, x2):         a, b = x1.time, x2.time         t1 = dt.datetime(a[0], a[1], a[2], a[3], a[4], int(a[5]))         t2 = dt.datetime(b[0], b[1], b[2], b[3], b[4], int(b[5]))         t1_decimal = float(a[5]-int(a[5]))         t2_decimal = float(b[5]-int(b[5]))         return (t2 - t1).total_seconds() + (t2_decimal - t1_decimal)      def __init__(self, time, lat, lng, alt, dist):         self.time = time         self.pos = [lat, lng]         self.alt = alt         self.dist = dist      def __str__(self):         ymd = ('ymd: ' + str(self.time[0]) + ', ' +                str(self.time[1]) + ', ' + str(self.time[2]))         hms = (' hms: ' + str(self.time[3]) + ', ' +                str(self.time[4]) + ', ' + str(self.time[5]))         lat = ' lat: ' + str(self.pos[0])         lng = ' lng: ' + str(self.pos[1])         alt = ' alt: ' + str(self.alt)         dst = ' dst: ' + str(self.dist)         stringrepresentation = ymd + hms + lat + lng + alt + dst         return stringrepresentation 

main.py

import dataparse as dp from entry import Entry   exitflag = False  while not exitflag:     [time, lat, lng, alt, dist] = dp.parsethedata()     entries = [Entry(x, lat[i], lng[i],                      alt[i], dist[i]) for i, x in enumerate(time)]          prev = entries[0]     total = 0.0     for entry in entries:         dx = Entry.distancecalc(prev, entry)         dt = Entry.timecalc(prev, entry)         total += dx         print('Total: ' + str(total) + ', Logged Dist: ' + str(entry.dist) +               ', dx: ' + str(dx) + ', dt: ' + str(dt), end="")         if dt:             print(', Speed: ' + str(dx/dt))         else:             print(', Speed: 0.0')         prev = entry          userinput = input('Parse another file? y/n: ')          if userinput == 'n':         exitflag = True 
        

Lista de respuestas

2
 
vote
vote
La mejor respuesta
 

He refrescado su código, y la lista completa está a continuación. Discutiré varios elementos de los cambios aquí. Si me perdí, explicando un cambio, o algún concepto, deje un comentario y veré lo que puedo hacer para cubrir mejor eso.

Organización

Lo primero que hice fue mover el código de análisis para ser un método de su clase de entrada. Este código de análisis parece ser muy específico para esta clase, así que hagamos que la relación sea explícita. También rompí el analizador para analizar un solo nodo de entrada, y la parte que administra analizando todo el archivo XML.

analizando

File Purse Code:

  private int numer, denom; 9  

Algunas notas:

  1. No le solicite la entrada del usuario en el código de análisis.
  2. Convierte los datos directamente a la representación deseada (una lista de entradas).

Código de análisis del nodo:

  public boolean equals(Rational x){ 0  

Unos pocos aspectos destacados:

  1. nodos aplanados usando public boolean equals(Rational x){ 1

    public boolean equals(Rational x){ 22 Encuentra todos los nodos debajo de un nodo, por lo que permite la eliminación del 99887766655443323 para los datos anidados 99887776655443324 .

  2. Se eliminó la necesidad de apilados si / else usando public boolean equals(Rational x){ 5 public boolean equals(Rational x){ 6

    Al usar la etiqueta del nodo como una tecla en un public boolean equals(Rational x){ 7 , puede almacenar las piezas necesarias para analizar el valor de la etiqueta. En este caso, almacené una función de conversión y el nombre deseado para el valor.

  3. eliminado public boolean equals(Rational x){ 8 Splitting en public boolean equals(Rational x){ 9 y usando el texto después del equals()0

  4. Convierte los datos directamente a la representación deseada.

    La escritura de los datos en un archivo secundario durante el Traversal del árbol XML, agregó bastante complejidad, sin obtener ganancia aparente. En su lugar, este código almacena cada elemento del nodo actual en un dict. El DICT se usa para iniciar un objeto de entrada.

  5. Las teclas DICT se asignan a los mismos nombres utilizados en equals()1

    Los nombres de los parámetros para equals()2 coinciden con las teclas utilizadas en el equals()3 99887776655443334 . Este mapeo se realizó a través del segundo elemento en el equals()5 en el equals()6 DICT. Esto hace que sea fácil mapear las cosas limpiamente. Esto permite equals()7 para crear directamente una instancia de clase. Tenga en cuenta el hecho de que puede especificar los parámetros que no sean de palabras clave con las palabras clave, que es la forma en que el 99887766555443338 puede expandirse en los 5 parámetros para iniciar el equals()99 clase

Estructura de clase

Los métodos estáticos probablemente no deben pasar una instancia de clase

Si está pasando una instancia de clase a un método estático de esa clase, probablemente lo esté haciendo mal. Los métodos estáticos están destinados al código que está estrechamente alineado con una clase pero que no está asociada directamente con una instancia.

Así que me convertí:

  equals()0  

a:

  equals()1  
Las propiedades

permiten que los campos calculados sean tratados fácilmente

en equals()2 Convertido:

  equals()3  

a:

  equals()4  

proporcionando:

  equals()5  

Dado que equals()6 como un par es una posición, lo que hace que esa relación sea explícita utilizando una propiedad, mejor documenta la relación.

Características de Python

usando bibliotecas de python

Al almacenar la marca de tiempo en la clase como un valor 99887776655443347 , pude convertir:

  equals()8  

a:

  equals()9  

Esto se eliminó toda la conversión de fecha y otras matemáticas.

use la cadena formateo

Al usar la funcionalidad de formato de cadena de Python, pude reducir el método 99887766655443350 de 11 líneas a 2

  public boolean equals(Rational x){ if (numer / denom == x.numer / x.denom){ return(true);         } else { return(false);     } } 1  

public boolean equals(Rational x){ if (numer / denom == x.numer / x.denom){ return(true); } else { return(false); } } 2 permite procesar múltiples listas a la vez

Dado que he completado eliminado las listas de public boolean equals(Rational x){ if (numer / denom == x.numer / x.denom){ return(true); } else { return(false); } } 3, etc., esto es menos relevante, pero pensé que mostraría public boolean equals(Rational x){ if (numer / denom == x.numer / x.denom){ return(true); } else { return(false); } } 4 de esta manera:

  public boolean equals(Rational x){ if (numer / denom == x.numer / x.denom){ return(true);         } else { return(false);     } } 5  

Esta línea se recupera su bucle de impresión de:

  public boolean equals(Rational x){ if (numer / denom == x.numer / x.denom){ return(true);         } else { return(false);     } } 6  

Esto tiene el beneficio lateral de no imprimir la primera línea de todos los ceros, pero es principalmente aquí para mostrar cómo usar 9 88776655443357 . ZIP agarra el primer elemento de cada una de las listas pasadas, y las presenta en la primera iteración. En la segunda iteración, presenta el segundo elemento de cada lista, etc. En este caso, usé todos menos el último elemento de las entradas combinadas con todo, excepto el primer elemento como dos listas para obtener pares adyacentes para cada iteración.

Código de reforma

  public boolean equals(Rational x){ if (numer / denom == x.numer / x.denom){ return(true);         } else { return(false);     } } 8  

 

I have recast your code, and the complete listing is below. I will discuss various elements of the changes here. If I missed explaining a change, or some concept, please leave a comment and I will see what I can do to better cover that.

Organization

First thing I did was move the parsing code to be a method of your Entry class. This parsing code seems to be very specific to this class, so let's make the relationship explicit. I also broke the parsing into parsing a single Entry Node, and the part which manages parsing the entire XML file.

Parsing

File Parse Code:

@classmethod def from_xml(cls, xml_source):     tree = etree.parse(xml_source)     root = tree.getroot()     return [cls.from_xml_node(child) for child in root[0][0][1][4]] 

Some notes:

  1. Don't prompt for user input in the parsing code.
  2. Convert the data directly to the desired representation (a list of Entries).

Node Parse Code:

converters = dict(     AltitudeMeters=(float, 'alt'),     DistanceMeters=(float, 'dist'),     LatitudeDegrees=(float, 'lat'),     LongitudeDegrees=(float, 'lng'),     Time=(dateutil.parser.parse, 'time'), )  @classmethod def from_xml_node(cls, node):     data_point = {}     for info in node.getiterator():         tag = info.tag.split('}')[-1]         if tag in cls.converters:             converter, tag_text = cls.converters[tag]             data_point[tag_text] = converter(info.text.strip())     return cls(**data_point) 

A few highlights:

  1. Flattened nodes using getiterator

    getiterator finds all of the nodes under a node, so allows the removal of the special if for the nested position data.

  2. Removed need for stacked if/else by using converters dict

    By using the node tag as a key into a dict, you can store the pieces needed to parse that tag's value. In this case I stored a conversion function, and the desired name for the value.

  3. Removed garbage string by more simply splitting on } and using text after the }

  4. Convert the data directly to the desired representation.

    The writing of the data into a secondary file during the XML tree traversal, added quite a bit of complexity, for no apparent gain. Instead this code stores each element of the current node into a dict. The dict is then used to init an Entry object.

  5. Dict keys are mapped to same names used in Entry.__init__

    The parameter names for __init__ match the keys used in the data_point dict. This mapping was done via the second element in the tuple in the converters dict. This makes it easy to map things cleanly. This allows return cls(**data_point) to directly create a class instance. Note the fact that you can specify non-keyword parameters with keywords, which is how the **data_point can expand into the 5 parameters to init the Entry class

Class Structure

static methods probably should not be passed a class instance

If you are passing a class instance to a static method of that class, you are probably doing it wrong. Static methods are intended for code which is closely aligned with a class but which it not directly associated with an instance.

So I converted:

@staticmethod def distancecalc(x1, x2): 

To:

def distance_from(self, other): 

properties allow calculated fields to be easily treated

In distance_from I converted:

coord_1 = (x1.pos[0], x1.pos[1]) coord_2 = (x2.pos[0], x2.pos[1]) return geopy.distance.vincenty(coord_1, coord_2).m 

To:

return geopy.distance.vincenty(self.pos, other.pos).m 

By providing:

@property def pos(self):     return self.lat, self.lng 

Since lat, lng as a pair is a position, making that relationship explicit by using a property, better documents the relationship.

Python features

Using python libraries

By storing the timestamp in the class as a datetime value, I was able to convert:

@staticmethod def timecalc(x1, x2):     a, b = x1.time, x2.time     t1 = dt.datetime(a[0], a[1], a[2], a[3], a[4], int(a[5]))     t2 = dt.datetime(b[0], b[1], b[2], b[3], b[4], int(b[5]))     t1_decimal = float(a[5]-int(a[5]))     t2_decimal = float(b[5]-int(b[5]))     return (t2 - t1).total_seconds() + (t2_decimal - t1_decimal) 

To:

def time_since(self, other):     return (self.time - other.time).total_seconds() 

This removed all of the date conversion and other math.

Use string formatting

By using python string formatting functionality I was able to reduce the __str__ method from 11 lines to 2

def __str__(self):     return 'time: %s, lat: %.6f, lng: %.6f, alt: %.2f, dst:%.3f' % (         self.time, self.lat, self.lng, self.alt, self.dist) 

zip allows processing multiple lists at once

Since I have completed removed the lists of 'lat', 'lng' etc, this is less relevant, but I thought I would show zip this way:

for prev, entry in zip(entries[:-1], entries[1:]): 

this line recasts your print loop from:

prev = entries[0] for entry in entries:     ....     prev = entry 

This has the side benefit of not printing the first line of all zeros, but is mostly here to show how to use zip. zip grabs the first element of each of the lists passed to it, and presents them on the first iteration. On the second iteration it presents the second element of each list, etc. In this case I used all but the last element of the entries combined with all but the first element as two lists to get adjacent pairs for each iteration.

Recast Code

import geopy.distance import dateutil.parser import xml.etree.ElementTree as etree   class Entry(object):      def __init__(self, time, lat, lng, alt, dist):         self.time = time         self.lat = lat         self.lng = lng         self.alt = alt         self.dist = dist      def __str__(self):         return 'time: %s, lat: %.6f, lng: %.6f, alt: %.2f, dst:%.3f' % (             self.time, self.lat, self.lng, self.alt, self.dist)      def distance_from(self, other):         return geopy.distance.vincenty(self.pos, other.pos).m      def time_since(self, other):         return (self.time - other.time).total_seconds()      @property     def pos(self):         return self.lat, self.lng      converters = dict(         AltitudeMeters=(float, 'alt'),         DistanceMeters=(float, 'dist'),         LatitudeDegrees=(float, 'lat'),         LongitudeDegrees=(float, 'lng'),         Time=(dateutil.parser.parse, 'time'),     )      @classmethod     def from_xml_node(cls, node):         data_point = {}         for info in node.getiterator():             tag = info.tag.split('}')[-1]             if tag in cls.converters:                 converter, tag_text = cls.converters[tag]                 data_point[tag_text] = converter(info.text.strip())         return cls(**data_point)      @classmethod     def from_xml(cls, xml_source):         tree = etree.parse(xml_source)         root = tree.getroot()         return [cls.from_xml_node(child) for child in root[0][0][1][4]]   while True:     filename = input('The file to parse: ')      entries = Entry.from_xml(filename)      total = 0.0     for prev, entry in zip(entries[:-1], entries[1:]):         dx = entry.distance_from(prev)         dt = entry.time_since(prev)         total += dx         print('Total: %.3f, Logged Dist: %.3f, dx: %.3f, dt: %.3f, '               'Speed: %.1f' % (total, entry.dist, dx, dt, dx/dt if dt else 0))      if 'n' == input('Parse another file? y/n: '):         break 
 
 
       
       

Relacionados problema

8  Python CSV a XML Converter  ( Python csv to xml converter ) 
Estoy creando una aplicación que se lee en los datos de un archivo CSV y crea un archivo XML usando LXML. El siguiente código funciona como se esperaba. Sin e...

0  XQUERY Compara orden de dos secuencias  ( Xquery compare order of two sequences ) 
He escrito una función en Xquery 3.0 (usando Basex) que prueba una secuencia de base contra una secuencia de prueba, con el objetivo de determinar si los elem...

5  Sistema de control de stock  ( Stock control system ) 
He programado este sistema de control de stock simple utilizando Javafx para GUI y PostgreSQL para una base de datos. Por favor, vaya a través de mi código ...

7  Implementación de la configuración XML  ( Xml settings implementation ) 
Estoy tratando de averiguar cómo hacer esto más dinámicamente. En este momento, guardo cada campo de forma individual / manualmente. Me encantaría, tal vez te...

2  XML a Windows.Forms.Keys Listar  ( Xml to windows forms keys list ) 
Me tomó mucho pinchando y las pruebas de unidad para que el código se vea como si fuera ahora. Así que tengo un archivo XML que en parte se ve así value: {...

2  Análisis de XML con doble etiquetas anidadas usando MINDOM  ( Parsing xml with double nested tags using mindom ) 
Quiero recuperar la identificación y el nombre por habilidad. Funciona pero esta bien hecho? Me gustaría quedarme con minidom, pero todos los consejos serán a...

7  Construir menú de MS Word XML  ( Build menu from ms word xml ) 
Recientemente he reunido un poco de jQuery que le permite a un usuario desarrollar una lista de viñetas en MS Word con los hipervínculos y enciende eso en una...

3  Buscar cambio en XML e IMPRIMIR NODE  ( Find change in xml and print node ) 
Este código se ha desarrollado con ayuda en el desbordamiento de la pila. Está diseñado para comparar dos archivos XML e imprimir el nodo para cualquier difer...

3  Leyendo un archivo XML grande y analizando los elementos necesarios en MySQLDB  ( Reading a large xml file and parsing necessary elements into mysqldb ) 
Tengo concepto justo en una programación (aprendiz), pero no un código experto para refactorarse al más alto nivel. Estoy tratando de leer un archivo XML enor...

2  Diferente enfoque a la siguiente acción ASP.NET WEB API  ( Different approach to the following asp net web api action ) 
Tal vez alguien aquí puede ayudarme a resolver esta implementación y hacerlo mejor. Primero las siguientes restricciones: 1 controlador & amp; Acción que...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos