123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- #
- """
- geo.gps (Geo-Positioning)
- =========================
-
- **Author:**
-
- * Dirk Alders <sudo-dirk@mount-mockery.de>
-
- **Description:**
-
- This module is a submodule of :mod:`geo` and includes functions and classes for geographic issues (e.g. coordinate, area, tracks, ...).
-
- **Contentlist:**
-
- * :class:`geo.gps.area`
- * :class:`geo.gps.coordinate`
- * :class:`geo.gps.tracklist`
- * :class:`geo.gps.track`
-
- **Unittest:**
-
- See also the :download:`unittest <../../geo/_testresults_/unittest.pdf>` documentation.
- """
-
- import calendar
- import math
- import time
- import xml.parsers.expat
-
-
- class area(object):
- """
- :param coord1: Corner Coordinate 1
- :type coord1: coordinate
- :param coord2: Corner Coordinate 2
- :type coord2: coordinate
-
- Class to store a geographic area and support some calculations.
-
- **Example:**
-
- .. code-block:: python
-
- >>> import geo
-
- >>> ar = geo.gps.area(...)
- """
- def __init__(self, coord1, coord2):
- min_lon = min(coord1[coordinate.LONGITUDE], coord2[coordinate.LONGITUDE])
- max_lon = max(coord1[coordinate.LONGITUDE], coord2[coordinate.LONGITUDE])
- min_lat = min(coord1[coordinate.LATITUDE], coord2[coordinate.LATITUDE])
- max_lat = max(coord1[coordinate.LATITUDE], coord2[coordinate.LATITUDE])
- self.coord1 = coordinate(lon=min_lon, lat=min_lat)
- self.coord2 = coordinate(lon=max_lon, lat=max_lat)
-
- def _max_lat(self):
- return self.coord2[coordinate.LATITUDE]
-
- def _max_lon(self):
- return self.coord2[coordinate.LONGITUDE]
-
- def _min_lat(self):
- return self.coord1[coordinate.LATITUDE]
-
- def _min_lon(self):
- return self.coord1[coordinate.LONGITUDE]
-
- def center_pos(self):
- """
- .. warning:: Needs sphinx documentation!
- """
- clon = (self.coord1[coordinate.LONGITUDE] + self.coord2[coordinate.LONGITUDE]) / 2
- clat = (self.coord1[coordinate.LATITUDE] + self.coord2[coordinate.LATITUDE]) / 2
- return coordinate(lon=clon, lat=clat)
-
- def coordinate_in_area(self, coord):
- """
- .. warning:: Needs sphinx documentation!
- """
- lon = coord[coordinate.LONGITUDE]
- lat = coord[coordinate.LATITUDE]
- return lon >= self._min_lon() and lon <= self._max_lon() and lat >= self._min_lat() and lat <= self._max_lat()
-
- def corner_coordinates(self):
- """
- .. warning:: Needs sphinx documentation!
- """
- return self.coord1, self.coord2
-
- def extend_area(self, coord):
- """
- .. warning:: Needs sphinx documentation!
- """
- if coord[coordinate.LONGITUDE] < self.coord1[coordinate.LONGITUDE]:
- self.coord1[coordinate.LONGITUDE] = coord[coordinate.LONGITUDE]
- elif coord[coordinate.LONGITUDE] > self.coord2[coordinate.LONGITUDE]:
- self.coord2[coordinate.LONGITUDE] = coord[coordinate.LONGITUDE]
-
- if coord[coordinate.LATITUDE] < self.coord1[coordinate.LATITUDE]:
- self.coord1[coordinate.LATITUDE] = coord[coordinate.LATITUDE]
- elif coord[coordinate.LATITUDE] > self.coord2[coordinate.LATITUDE]:
- self.coord2[coordinate.LATITUDE] = coord[coordinate.LATITUDE]
-
- def osm_map(self, map_code):
- """
- :param map_code: Map code as defined in :class:`geo.osm` (e.g. :class:`geo.osm.MAP_STANDARD`)
-
- This Method returns a :class:`geo.osm.map` instance.
-
- .. warning:: Needs sphinx documentation!
- """
- # TODO: needs to be implemented
- pass
-
- def __str__(self):
- return "%s / %s" % self.coordinates()
-
-
- class coordinate(dict):
- """
- :param lon: Londitude
- :type lon: float
- :param lat: Latitude
- :type lat: float
- :param height: Height
- :type height: float
- :param time: Time (Seconds since 1970)
- :type time: int
-
- Class to store a geographic coodinate and support some calculations.
-
- **Example:**
-
- .. code-block:: python
-
- >>> import geo
-
- >>> ab = geo.gps.coordinate(lat=49.976596,lon=9.1481443)
- >>> gb = geo.gps.coordinate(lat=53.6908298,lon=12.1583252)
- >>> ab.dist_to(gb) / 1000
- 462.3182843470017
- >>> ab.angle_to(gb) / math.pi * 180
- 39.02285256685333
- """
- LATITUDE = 'lat'
- LONGITUDE = 'lon'
- HIGHT = 'hight'
- TIME = 'time'
-
- def __init__(self, **kwargs):
- dict.__init__(self, **kwargs)
-
- def __str__(self):
- def to_string(lon_or_lat, plus_minus=('N', 'S')):
- degrees = int(lon_or_lat)
- lon_or_lat -= degrees
- minutes = lon_or_lat * 60
- pm = 0 if degrees >= 0 else 1
- return "%d°%.4f%s" % (abs(degrees), abs(minutes), plus_minus[pm])
- lon = self.get(self.LONGITUDE)
- lat = self.get(self.LATITUDE)
- if lon is not None and lat is not None:
- return to_string(lat) + ' ' + to_string(lon, ['E', 'W'])
- else:
- return None
-
- def angle_to(self, coord):
- """
- This Method calculates the geographic direction in radiant from this to the given coordinate.
-
- .. note:: North is 0 (turning right). That means east is :class:`math.pi`/2.
-
- :param coord: Target coordinate.
- :type coord: corrdinate
- :returns: The geographic direction in radiant.
- :rtype: int or float
- """
- lat1 = coord[self.LATITUDE]
- lon1 = coord[self.LONGITUDE]
- lat2 = self[self.LATITUDE]
- lon2 = self[self.LONGITUDE]
- if lat1 is not None and lat2 is not None and lon1 is not None and lon2 is not None:
- dlon = lon1 - lon2
- dlat = lat1 - lat2
-
- if dlat > 0:
- # case (half circle north)
- angle = math.atan(dlon / dlat)
- pass
- elif dlat < 0:
- # case (half circle south)
- angle = math.pi + math.atan(dlon / dlat)
- elif dlon > 0:
- # case (east)
- angle = math.pi / 2
- elif dlon < 0:
- # case (west)
- angle = math.pi * 3 / 2
- else:
- # same point
- return None
-
- if angle < 0:
- angle += 2 * math.pi
- return angle
- else:
- return None
-
- def dist_to(self, coord):
- """
- This Method calcultes the distance from this coordinate to a given coordinate.
-
- :param coord: Target coordinate.
- :type coord: coordinate
- :return: The distance between two coordinates in meters.
- :rtype: int or float
- :raises: -
- """
- lat1 = coord[self.LATITUDE]
- lon1 = coord[self.LONGITUDE]
- lat2 = self[self.LATITUDE]
- lon2 = self[self.LONGITUDE]
- if lat1 is not None and lat2 is not None and lon1 is not None and lon2 is not None:
- R = 6378140
- dLat = math.radians(lat2 - lat1)
- dLon = math.radians(lon2 - lon1)
- a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dLon / 2) * math.sin(dLon / 2)
- c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
- return R * c
- else:
- return None
-
-
- class tracklist(list):
- """
- Class to store a a list of tracks and parse xml files created by a navigation system like Etrax Vista.
-
- **Example:**
-
- .. code-block:: python
-
- >>> import geo
-
- >>> ...
- """
- def __init__(self):
- list.__init__(self)
- self.__xml_track_on_read = None
- self.__xml_data_on_read = ''
-
- def __xml_start_element(self, name, attrs):
- self.__xml_data_on_read = ''
- if name == 'trk':
- # new track found in file
- self.__xml_track_on_read = track()
- elif name == 'trkpt':
- # new waypoint to append
- if 'lon' in attrs and 'lat' in attrs:
- self.__xml_track_on_read.append(coordinate(lon=float(attrs['lon']), lat=float(attrs['lat'])))
-
- def __xml_end_element(self, name):
- if name == 'trk':
- if self.__xml_track_on_read is not None and len(self.__xml_track_on_read) > 0:
- self.append(self.__xml_track_on_read)
- self.__xml_track_on_read = None
- elif name == 'name':
- if self.__xml_data_on_read != '':
- self.__xml_track_on_read.set_name(self.__xml_data_on_read)
- elif name == 'ele':
- c = self.__xml_track_on_read[len(self.__xml_track_on_read) - 1]
- c[c.HIGHT] = float(self.__xml_data_on_read)
- elif name == 'time':
- c = self.__xml_track_on_read[len(self.__xml_track_on_read) - 1]
- c[c.TIME] = int(calendar.timegm(time.strptime(self.__xml_data_on_read, '%Y-%m-%dT%H:%M:%SZ')))
-
- def __xml_char_data(self, data):
- self.__xml_data_on_read += data
-
- def load_from_file(self, xmlfilehandle):
- """
- .. warning:: Needs to be documented
- """
- # TODO: implement either usage of a filename or filehandle
- # parse xml-handle
- p = xml.parsers.expat.ParserCreate()
- p.StartElementHandler = self.__xml_start_element
- p.EndElementHandler = self.__xml_end_element
- p.CharacterDataHandler = self.__xml_char_data
- p.ParseFile(xmlfilehandle)
-
-
- class track(list):
- """
- Class to store a a tracks and support some calculations.
-
- **Example:**
-
- .. code-block:: python
-
- >>> import geo
-
- >>> ...
- """
- def __init__(self):
- self._name = None
- list.__init__(self)
- self.__init_state_variables()
-
- def __init_state_variables(self):
- self._area = None
- self._average_speed = None
- self._hightcharacteristic = None
- self._end_date = None
- self._optimized_track = None
- self._passed_hight = None
- self._speedcharacteristic = None
- self._start_date = None
- self._total_distance = None
- self._total_time = None
-
- def append(self, coord):
- """
- .. warning:: Needs to be documented
- """
- list.append(self, coord)
- self.__init_state_variables()
-
- def extend(self, *args, **kwargs):
- """
- .. warning:: Needs to be documented
- """
- self.__init_state_variables()
- return list.extend(self, *args, **kwargs)
-
- def insert(self, index, coord):
- """
- .. warning:: Needs to be documented
- """
- list.insert(self, index, coord)
- self.__init_state_variables()
-
- def set_name(self, name):
- """
- .. warning:: Needs to be documented
- """
- self._name = name
-
- def area(self):
- """
- :rtype: geo.gps.area or None
-
- .. warning:: Needs to be documented
- """
- if self._area is None:
- if len(self) > 1:
- self._area = area()
- for c in self:
- self._area.extend_area(c)
- return self._area
-
- def average_speed(self):
- """
- .. warning:: Needs to be documented
- """
- if self._average_speed is None:
- self._average_speed = self.total_distance() / self.total_time()
- return self._average_speed
-
- def hightcharacteristic(self):
- """
- .. warning:: Needs to be documented
- """
- if self._hightcharacteristic is None:
- pass # TODO: implement functionality
- return self._hightcharacteristic
-
- def end_date(self):
- """
- .. warning:: Needs to be documented
- """
- if self._end_date is None:
- if len(self) > 0:
- self._end_date = self[len(self) - 1].get(coordinate.TIME)
- return self._end_date
-
- def name(self):
- """
- .. warning:: Needs to be documented
- """
- return self._name
-
- def optimized_track(self):
- """
- .. warning:: Needs to be documented
- """
- # TODO: REWORK TRACK OPTIMIZATION
- DIST_MOVED = 15. # 15m
- ACCELERATION_MAX = 0.5 * 9.81 # 0.5g
- ANGLE_DIF_FOR_BACKWARDS = 10 # +/- 5deg
- MAX_DELETED_POINTS = 30
-
- def backwards_direction(l_angle, t_angle):
- dang = l_angle - t_angle
- if dang < 0:
- dang += math.degrees(math.pi * 2)
- if dang > math.degrees(math.pi) - ANGLE_DIF_FOR_BACKWARDS / 2 and dang < math.degrees(math.pi) + ANGLE_DIF_FOR_BACKWARDS / 2:
- return True
- else:
- return False
-
- if self._optimized_track is None:
- self._optimized_track = track()
- self._optimized_track.set_name(self._name)
- del_lst = []
- for coord in self:
- if len(self._optimized_track) == 0:
- # first item has to be added always
- self._optimized_track.append(coord)
- else:
- last = self._optimized_track[-1:][0]
- #try:
- acc = coord.dist_to(last) / ((coord[coordinate.TIME] - last[coordinate.TIME]) ** 2)
- #except:
- # acc = 0.0
- if len(self._optimized_track) > 1:
- # calculate last angle
- last_angle = self._optimized_track[-2:-1][0].angle_to(last)
- if last_angle is not None:
- last_angle = math.degrees(last_angle)
- # calculate this angle
- this_angle = last.angle_to(coord)
- if this_angle is not None:
- this_angle = math.degrees(this_angle)
- else:
- last_angle = this_angle = None
- if coord.dist_to(last) > DIST_MOVED:
- # distance ok.
- if acc < ACCELERATION_MAX:
- # acceleration ok.
- if this_angle is None or last_angle is None or not backwards_direction(last_angle, this_angle):
- # direction ok
- del_lst = []
- self._optimized_track.append(coord)
- else:
- del_lst.append(coord)
- #print "this one was in backwards direction (%d)!" % (len(del_lst))
- #print " %.2fkm: last = %.1f\xc2\xb0 - this = %.1f\xc2\xb0" % (self.total_distance()/1000., last_angle, this_angle)
- else:
- del_lst.append(coord)
- #print "this one was with to high acceleration (%d)!" % (len(del_lst))
- #print " %.2fkm: %.1fm/s\xc2\xb2" % (self.total_distance()/1000., acc)
- if len(del_lst) >= MAX_DELETED_POINTS:
- print("Moeglicherweise ist die Optimierung des Tracks noch nicht ausgereift genug.")
- print(" Bei %.1f km gab es %d Koordinaten die aussortiert worden waeren. Optimierung ausgelassen." % (self.get_total_distance() / 1000., MAX_DELETED_POINTS))
- self._optimized_track.extend(del_lst)
- del_lst = []
- return self._optimized_track
-
- def osm_map(self, map_code):
- return self.area().osm_map(map_code)
-
- def passed_hight(self):
- """
- .. warning:: Needs to be documented
- """
- if self._passed_hight is None:
- if len(self) > 0:
- self._passed_hight = 0.0
- hight = self[0][coordinate.HIGHT]
- if hight is not None:
- for c in self:
- last_hight = hight
- # hysteresis of 1 meter
- hightlist = [c[coordinate.HIGHT] - 1, hight, c[coordinate.HIGHT] + 1]
- hightlist.sort()
- hight = hightlist[1]
- if hight > last_hight:
- self._passed_hight += hight - last_hight
- return self._passed_hight
-
- def speedcharacteristic(self):
- """
- .. warning:: Needs to be documented
- """
- if self._speedcharacteristic is None:
- pass # TODO: implement functionality
- return self._speedcharacteristic
-
- def start_date(self):
- """
- .. warning:: Needs to be documented
- """
- if self._start_date is None:
- if len(self) > 0:
- self._start_date = self[0].get(coordinate.TIME)
- return self._start_date
-
- def total_distance(self):
- """
- .. warning:: Needs to be documented
- """
- if self._total_distance is None:
- if len(self) > 0:
- self._total_distance = 0.
- for i in range(0, len(self) - 1):
- self._total_distance += self[i].dist_to(self[i + 1])
- return self._total_distance
-
- def total_time(self):
- """
- .. warning:: Needs to be documented
- """
- if self._total_time is None:
- try:
- self._total_time = self.end_date() - self.start_date()
- except TypeError:
- pass # doing nothing will return None as needed, if calculation is not possible
- return self._total_time
-
-
- '''
- class gpxmanipu():
- debug=False
- class trackmanipu():
- def __init__(self):
- self.lines=[]
- def AddLine(self, line):
- self.lines.append(line)
- def SetName(self, name):
- SEARCHFORSTARTTAG=0
- SEARCHFORENDTAG=1
- state=SEARCHFORSTARTTAG
- for i in range(0, len(self.lines)):
- if state==SEARCHFORSTARTTAG:
- if self.lines[i].find('<name>')>0:
- newline=self.lines[i][:self.lines[i].find('<name>')+6]
- newline+=name
- state=SEARCHFORENDTAG
- if state==SEARCHFORENDTAG:
- if self.lines[i].find('</name>')>0:
- self.lines[i]=newline+self.lines[i][self.lines[i].find('</name>'):]
- else:
- self.lines.remove(self.lines[i])
- def GetTrack(self):
- rv=''
- for line in self.lines:
- rv+=line
- return rv
- def __init__(self, fh):
- HEADERSEARCH=0
- TRACKSEARCH=1
- state=HEADERSEARCH
- self.header=''
- self.tracks=[]
- for line in fh:
- if state==HEADERSEARCH:
- if line.lstrip().startswith('<trk>'):
- state=TRACKSEARCH
- track=self.trackmanipu()
- track.AddLine(line)
- else:
- self.header+=line
- elif state==TRACKSEARCH:
- if line.lstrip().startswith('</trk>'):
- track.AddLine(line)
- self.tracks.append(track)
- track=self.trackmanipu()
- else:
- track.AddLine(line)
- self.footer=track.GetTrack() # This was no track
- del(track)
- if self.debug:
- print "Header found:"
- print self.header[:20]+' ... '+self.header[-20:]
- print str(len(self.tracks))+' tracks found:'
- for track in self.tracks:
- track=track.GetTrack()
- print track[:20]+' ... '+track[-20:]
- print "Footer found:"
- print self.footer
- def GetGpx(self):
- rv=self.header
- for track in self.tracks:
- rv+=track.GetTrack()
- rv+=self.footer
- return rv
- def SetTrackname(self, number, name):
- if len(self.tracks)>number-1:
- self.tracks[number].SetName(name)
- def DeleteTrack(self, number):
- if len(self.tracks)>number-1:
- return self.tracks.pop(number)
- else:
- return None
- '''
|