Symbian developer community

 
wiki

Location Based Services (Python on Symbian)

From Symbian Developer Community

Jump to: navigation, search

Original Author: Pankaj Nathani

Contents

Introduction

One of the most powerful ways to personalize mobile applications is to make them location aware. With the increasing focus on mobile software and the penetration of GPS enabled handsets, mobile devices are becoming the ultimate portable map, search and navigation tool.

Location Based Services (LBS) add value to applications by providing users with the most appropriate data for their current location. For example, a location aware clock on a mobile device would automatically detect the user's current time zone and change the time accordingly; a location aware calendar might fetch the weather and display it for the upcoming days. The following figure shows the basic classification of location based services.

In this chapter, an effort has been made to show how to add location context sensitivity to Python applications.

Acquiring Location Information

In Python for S60, location information of a mobile device can be retrieved by using the network information or by using GPS information. There are other sophisticated methods particularly for indoor positioning; for example, indoor positioning using WLAN and Bluetooth but they are beyond the scope of this book.

Using Network Information

Network-based location tracking techniques use the service provider's network infrastructure to get the location of the mobile device. The advantage of using network-based techniques is that they can be implemented on low end mobile handsets. The accuracy of network-provided location information is relatively poor, and may vary from place to place (as it is dependent on operator's network infrastructure).

The most common network-based location tracking technique is to use cell identification. Before we understand how location information can be retrieved using cell identification technique, let us familiarize ourselves with some associated terminology:

  • Mobile Country Code - A Mobile Country Code (MCC) is part of the International Mobile Subscriber Identity (IMSI) number, which uniquely identifies a particular subscriber and is stored on a (usually) removable SIM card. A list of MCCs is available here.
  • Mobile Network Code - A Mobile Network Code (MNC) is used in combination with a Mobile Country Code (MCC) to uniquely identify a mobile phone operator/carrier.
  • Location Area Code - A Location Area Code (LAC) uniquely represents a small part of geographical location.
  • Cell ID - A Cell ID is a unique number of a GSM cell for a given service provider.

Although it may seem possible to define a unique location provided the MCC, MNC, LAC and Cell ID are available, the reality is somewhat different. Consider:

"The Cell ID represents a unique GSM cell for a particular service provider"

Not all mobile devices use the same service provider. Further, the size of a GSM cell area is not definite, even throughout a common service provider. For example, in urban areas the size of the cell can vary from a few meters to hundred meters; however in rural and suburban areas, it can be upto a few kilometers. Thus, the accuracy of the cell identification technique can range from a few hundred meters in urban areas to a few kilometers in rural/suburban areas. Overlap of cells and radio signal shadows can cause cells to be non-contiguous areas. There is no one-to-one correspondence between a physical location and the cell used by a phone, e.g., due to changing radio interference. Even though the gsm information can't be guaranteed to be 100% efficient, it has an advantage that it is available almost everywhere. By using a carefully designed framework for recognizing important locations in GSM networks, the efficiency of this method can certainly increase and prove useful.

Coming back to PyS60, a location module exists which can help us to track the location using the above network parameters. The location module is the simplest module in PyS60 with only one function location viz. gsm_location() available. The function returns a tuple containing the GSM location information of the device. Let's see an example.

 
import location
print location.gsm_location()
MCC, MNC, LAC, CID = location.gsm_location()
print "MCC =" + str(MCC)
print "MNC =" + str(MNC)
print "LAC =" + str(LAC)
print "CID =" + str(CID)
 

For the above example, as the variable names indicate, 404 is the Mobile Country Code, 92 is the Mobile Network Code, 1336 is the Location Area Code, and 6674 is the Cell ID - as shown in the screenshot below.

gsm_location()

Note: For using the location module - Location capability is necessary. gsm_location() returns None, if the PyS60 Script Shell or application is not signed with Location capability.

Tip
Besides using the location module, there exist many online web services which return the location information for the handset using its IP address. One can use the urllib module for using such services. Also, additional information such as Country Code, Country Name, Region, Region Name, City, Postal Code, Latitude, Longitude, ISP, Organization, Metro Code, Area Code, etc can be obtained using such online APIs. Again, the accuracy of these web services may not be up to the mark varying from one to another. Just google for 'geo ip' to find such web services.

Using GPS Information

With the advancement of technology, many S60 devices come equipped with an integrated Global Positioning System (GPS) receiver. A GPS receiver calculates its position by precisely timing the signals sent by the GPS satellites high above the Earth. As a GPS receiver works from the navigation signals received from the satellites, the GPS functions only outdoors, when the device is open to the sky. Assisted GPS (A-GPS) which is also equipped in most of the GPS enabled mobile handsets, is a network based system that improves the startup performance of a GPS satellite-based positioning system.

S60 devices with integrated GPS receiver, do have an inbuilt application (GPS Data) for using GPS information for applications like Navigation, Position and Trip distance calculation. The screenshots for these are shown below. The last screenshot indicates the satellite status, and shows the intensity of signal received from a particular satellite. The number indicates the satellite number (total satellites = 32) and the bar on the right indicates the intensity of the signal received from that satellite. Strong signals from at least 4 satellites are necessary to calculate and obtain the GPS information.

    PyS60 has a positioning module which is useful for obtaining GPS information. Alike the location module, the positioning module is also simple and straightforward to use. The module can be used to access position information provided by both - external Bluetooth GPS-devices and by built-in GPS-receivers as well. Let us understand how the positioning module works.

     
    >>> import positioning
    >>> positioning.modules()
    [{'available': 0, 'id': 270526873, 'name': u'Bluetooth GPS'}, {'available': 1, '
    id'
    : 270526858, 'name': u'Integrated GPS'}, {'available': 1, 'id': 270559509, 'n
    ame'
    : u'Network based'}]
     

    The modules() function is used to query the device for the availability and ids of various types/modules of GPS. For example, the dictionary returned by the statement, in the above in the example is as follows.

     
    {'available': 0, 'id': 270526873, 'name': u'Bluetooth GPS'}
    {'available': 1, 'id': 270526858, 'name': u'Integrated GPS'}
    {'available': 1, 'id': 270559509, 'name': u'Network based'}
     

    This indicates the following in layman's language, which is self explanatory.

     
    Bluetooth GPS is not available to the device. The id of Bluetooth GPS is 270526873.
    Integrated GPS is available to the device. The id of Integrated GPS is 270526858.
    Network based GPS is not available to the device. The id of Network based GPS is 270559509.
     

    It may happen that more than one GPS modules are available to the device. For example, in the above illustration Integrated GPS and Network based GPS modules, both are available to the device. Likewise, it may happen that a Bluetooth GPS module may also be available to the device. Therefore, in such cases when there are more than one GPS modules available to the device, a GPS module can be selected for obtaining positioning information. A GPS module can be selected by the select_module(id) function as follows.

     
    >>> positioning.select_module(270526858)
     

    Each GPS module available to the device has a set of characteristics which can be retrieved by querying the device for a specific available GPS module.

    For example,

     
    >>> positioning.module_info(270526858)
    {'available': 1, 'status': {'data_quality': 3, 'device_status': 7}, 'version': u
    '1.00(0)', 'name': u'Integrated GPS', 'position_quality': {'vertical_accuracy':
    10.0, 'time_to_first_fix': 1000000L, 'cost': 1, 'time_to_next_fix': 1000000L, 'h
    orizontal_accuracy'
    : 10.0, 'power_consumption': 3}, 'technology': 1, 'id': 27052
    6858, 'capabilities': 127, 'location': 1}
     

    These characteristics may be used to compare/manipulate the GPS module hardware of different devices dynamically. Thus, the module_info() function gives us a detailed information about the specified GPS module.

    The default GPS module id of the device can be obtained through the default_module() function.

    For example,

     
    >>> positioning.default_module() #returns the default GPS module id
    270526858
    >>> positioning.module_info(default_module()) # returns the module details for the default GPS module
    {'available': 1, 'status': {'data_quality': 3, 'device_status': 7}, 'version': u
    '1.00(0)', 'name': u'Integrated GPS', 'position_quality': {'vertical_accuracy':
    10.0, 'time_to_first_fix': 1000000L, 'cost': 1, 'time_to_next_fix': 1000000L, 'h
    orizontal_accuracy'
    : 10.0, 'power_consumption': 3}, 'technology': 1, 'id': 27052
    6858, 'capabilities': 127, 'location': 1}
     

    Finally, having traversed through all the functions in the positioning module - we have now come to the three most important functions viz. position(), last_position() and stop_position().

    Tip
    One can test the following functions on the emulator by configuring PSY emulation from your emulator. (SimPSYConfigurator-> Select Config File -> <some config files>or Tools -> Position).


    The position() function is responsible for fetching the GPS information through the selected GPS module. If none of the GPS modules are selected using the select_module(id) function then the default GPS module is considered.

    The position() function has some option parameters/arguments that can be passed while calling the function, whenever it is necessary to get some addition information. The syntax of the function with optional parameters is given below.

    position(course=0,satellites=0,callback=None, interval=positioning.POSITION_INTERVAL, partial=0)

    By default, the postion() function returns the position information in a dictionary.

     
    >>> positioning.set_requestors([{"type":"service","format":"application","data":"gps_app"}]
    >>> positioning.position()
    {'satellites': None, 'position': {'latitude': NaN, 'altitude': NaN, 'vertical_accuracy': NaN, 'longitude': NaN, 'horizontal_accuracy': NaN}, 'course': None}
     

    The above output is returned by the position() function, when there is no data available i.e. the satellite navigation signals are not available to the GPS receiver. Whenever the signals from the satellites are available, the position() function works as illustrated below.

     
    >>> positioning.set_requestors([{"type":"service","format":"application","data":"gps_app"}]
    >>> positioning.position()
    {'satellites': None, 'position': {'latitude': 19.120155602532, 'altitude': 16.0, 'vertical_accuracy': 7.5, 'longitude': 72.895265188042, 'horizontal_accuracy': 80.956672668457}, 'course': None}
     


    Note
    It is necessary to set at least one requestors of the service before using the position() function.


    If course and satellite parameters are passed as 1, then the information about course and satellites is also returned (if available). If the partial parameter is set to 1, the function may return with incomplete information before the final fix is calculated.

    For example,

     
    >>> positioning.set_requestors([{"type":"service","format":"application","data":"gps_app"}]
    >>> positioning.position(course=1,satellites=1, partial=1)
    {'satellites': {'horizontal_dop': 2.86999988555908, 'used_satellites': 4, 'vertical_dop': 0.980000019073486, 'time': 1253539177.0, 'satellites': 11, 'time_dop': 1.69000005722046}, 'position': {'latitude': 19.1201448737, 'altitude': 16.0, 'vertical_accuracy': 6.5, 'longitude': 72.89526787025, 'horizontal_accuracy': 121.031242370605}, 'course': {'speed': NaN, 'heading': NaN, 'heading_accuracy': NaN, 'speed_accuracy': NaN}}
     

    The interval parameter (in microseconds) can be used to invoke the call at specified intervals of time (in microseconds).

    An optional parameter, a callback function can be passed while using the position() function. The call returns immediately if a valid callback function is given. The callback function is invoked with the specified time interval (in microseconds) in between the invocations. The callback function is called with the the current position information as parameter. Thus, care should be taken that the callback function definition contains a parameter.

    For example,

     
    import positioning, appuifw
     
    #callback function
    def cb_pos(info):
    appuifw.note(unicode(info))
     
    #start polling
    positioning.set_requestors([{"type":"service","format":"application","data":"gps_app"}]
    positioning.position(course=1,satellites=1, callback=cb_pos, interval=3000000, partial=0)
     

    In the above code snippet, the callback function cb_pos(info) is called after every 3 seconds and displays the positioning information as a note.


    Note
    With no callback function argument provided, the call blocks until the position information is available.


    Sometimes, it may happen in a real life scenario that the position() function always returns NaN (Not a Number) data. In such situations, a function last_position() can be used to retrieved last GPS data that was available.


    Note
    As last_position() returns cached data, it may depend on the programming/coding technique, if the function would return the desired result.


    Finally, the stop_position() function can be used to stop an on-going positioning request.

    Let us take a simple code snippet which would explain many of the functions explained above,

     
    import e32, appuifw, positioning
     
    def initialize_gps():
    '''This function intialized the GPS. The GPS module can be choosen in this function by select_module(module_id).
    In this case we are using the default GPS (integrated GPS) hence we do not need to select it.'
    ''
    appuifw.note(u'Intializing GPS')
    global gps_data
    #Intitialize the global dictionary with some initial dummy value (0.0 in this case)
    gps_data = {
    'satellites': {'horizontal_dop': 0.0, 'used_satellites': 0, 'vertical_dop': 0.0, 'time': 0.0,'satellites': 0, 'time_dop':0.0},
    'position': {'latitude': 0.0, 'altitude': 0.0, 'vertical_accuracy': 0.0, 'longitude': 0.0, 'horizontal_accuracy': 0.0},
    'course': {'speed': 0.0, 'heading': 0.0, 'heading_accuracy': 0.0, 'speed_accuracy': 0.0}
    }
    try:
    # Set requestors - it is mandatory to set atleast one
    positioning.set_requestors([{"type":"service","format":"application","data":"gps_app"}])
    # Request for a fix every 0.5 seconds
    positioning.position(course=1,satellites=1,callback=cb_gps, interval=500000,partial=0)
    # Sleep for 3 seconds for the intitial fix
    e32.ao_sleep(3)
    except:
    appuifw.note(u'Problem with GPS','error')
     
    def cb_gps(event):
    global gps_data
    gps_data = event
     
    def stop_gps():
    '''Function to stop the GPS'''
    try:
    positioning.stop_position()
    appuifw.note(u'GPS stopped','error')
    except:
    appuifw.note(u'Problem with GPS','error')
     
    #initialize GPS
    initialize_gps()
     
    #Set time for the script to run. 10 minutes in this case.
    time_to_run=10
    minutes=time_to_run*60
     
    # Print the GPS data for 10 minutes.
    while (minutes > 0):
    print gps_data['satellites']['used_satellites'], gps_data['position']['latitude'], gps_data['position']['longitude'], gps_data['course']['speed']
    minutes=minutes-1
    e32.ao_sleep(1)
     
    # Stop the on-going request
    stop_gps()
     

    In the above code snippet uses 2 user-defined functions to print gps_data every second for 10 minutes, using a while loop.

    • initialize_gps() - Initializes the GPS by selecting the GPS module, setting requestors and requesting for a fix every 0.5 seconds
    • cb_gps(event) - Call back function which is called every 0.5 seconds
    • stop_gps() - Stops the GPS

    These functions are self-explanatory with the comments in the code snippet.

    While dealing with devices that do not have an integrated GPS receiver, an external Bluetooth GPS receiver may be used to obtain GPS data and thus the location of the device. The positioning module is not at all needed for retrieving data from an external Bluetooth GPS receiver (we would use the socket module). External Bluetooth GPS receivers communicate with the mobile devices using NMEA sentences. If you are not familiar with the NMEA sentences standards, please refer to this page.

    Not going into much details, here is what an NMEA sentence looks like,

     
    $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
     
    Where:
    GGA Global Positioning System Fix Data
    123519 Fix taken at 12:35:19 UTC
    4807.038,N Latitude 48 deg 07.038' N
    01131.000,E Longitude 11 deg 31.000' E
    1 Fix quality: 0 = invalid
    1 = GPS fix (SPS)
    2 = DGPS fix
    3 = PPS fix
    4 = Real Time Kinematic
    5 = Float RTK
    6 = estimated (dead reckoning) (2.3 feature)
    7 = Manual input mode
    8 = Simulation mode
    08 Number of satellites being tracked
    0.9 Horizontal dilution of position
    545.4,M Altitude, Meters, above mean sea level
    46.9,M Height of geoid (mean sea level) above WGS84
    ellipsoid
    (empty field) time in seconds since last DGPS update
    (empty field) DGPS station ID number
    *47 the checksum data, always begins with *
     

    All one needs to define a location is a latitude and longitude. Let us first establish a connection with the Bluetooth GPS receiver using the socket module.

     
    >>> import socket
    >>> address, services = socket.bt_discover() # search for the bluetooth devices
    >>> gps_receiver = (address, services.values()[0]) # select the external bluetooth GPS receiver
    >>> connection = socket.socket(socket.AF_BT, socket.SOCK_STREAM)
    >>> connection.connect(gps_receiver) # establish connection
     

    That being done, a connection is established between the external GPS receiver and the mobile device. In other words, now we can listen to the NMEA sentences transmitted by the external GPS receiver.

     
    >>> gps_data = connection.makefile("r", 0)
    >>> if (gps_data.startswith("$GPGGA"): # check if gps_data is a valid NMEA sentence. Refer to the sentence syntax above.
    gps_data = gps_data.split(",") # string handling
    latitude = gps_data[2] # define Latitude
    longitude = gps_data[4] # define Longitude
    >>> print latitude, longitude
     

    Using Maps with GPS Information

    In the last section, we saw how one can obtain the GPS information (particularly - latitude and longitude) of the mobile handset. This information can be used creatively to make innovative location based applications. For example, the GPS information can be used to map the current location on a map and give the end user an visual feeling as to where he/she is. Many web services exist, which let you easily embed rich and interactive maps into applications. Not only this, but you can also plot points on a map in your application, with features like custom descriptions, URLs, labels, groups, icons, etc. turning it into a personalized, contextual and compelling experience for the end-user. For example, Google Maps API, Yahoo Maps API, Ovi Maps Player API, etc.

    Where Am I! Locate yourself

    In this section, we are going to see a sample application - Where Am I - originally contributed by Herb Jellinek. It is a simple application that determines the location of the user and maps it on Google maps. Where Am I is compatible with devices with an integrated GPS receiver. The application may however work with an external Bluetooth GPS receiver as well, with some changes to the code.

      The application is kept simple with the minimalistic required user interface to focus on the positioning and mapping features. However, the application has an fairly good exception handling, default access point selection techniques and debug logging which can be quite helpful for programming PyS60 applications.

      Let’s now dive into the code! The Where Am I application can be divided into 5 blocks as shown in the following schematic diagram.

      The complete source code of Where Am I is available for download here and contains the following files. All files should be placed in E:\Python\lib, except main.py which can be placed in E:\data\Python or C:\data\Python. These paths may of course be changed with a corresponding change in the code as well.

      Table: Where Am I Source Code Files
      File Name Function
      main.py It is the main application script that uses other libraries and displays the map.
      lat_lon.py For obtaining Latitude and longitude of the current location using positioning module.
      google_maps.py For plotting the current location on google maps.
      persist_ap.py For saving Access Point for future use.
      my_logging.py For adding entries to the debug log.

      Obtaining Latitude and Longitude co-ordinates

      In the last section, we studied about this, so this shouldn't be hard if you understood the previous section well. The code for obtaining the co-ordinates is in the lat_lon.py file, as mentioned in the above table. As we discussed in the previous section, the requestors are set by set_requestors, before querying the device for a location fix.

      The getPos() function returns a dictionary containing the following attributes:

      • time: <float, Unix time>
      • latitude: <float>
      • longitude: <float>
      • altitude: <float>
      • horizontal_accuracy: <float>
      • vertical_accuracy: <float>

      getLatLon() returns a tuple containing the latitude and longitude.

      If the device doesn't support any positioning methods, getPos() will always return None and getLatLon() will always return (None, None). hasPositioning() returns False if the device cannot report its position, True if it can.

       
      # lat_lon.py
      # A simple module that wraps the PyS60 positioning module to hide
      # its initialization sequence and boil down its API to a couple of simple
      # calls.
       
      import positioning
       
      modules = positioning.modules()
      if len(modules) == 0:
       
      # There's no way to get our position, so don't try.
      def getPos():
      return None
      # There's no way to get our position, so don't try.
      def getLatLon():
      return None, None
      # There's no way to get our position.
      def hasPositioning():
      return False
       
      else:
       
      # select a positioning module (a device might have several)
      positioning.select_module(positioning.default_module())
       
      # Setting requestors before requesting for a fix
      positioning.set_requestors([{"type": "service",
      "format": "application",
      "data": "loc"}])
       
      # Return the current position of the device.
      def getPos():
      return positioning.position()['position']
      # Return the current position of the device as a simple lat/lon tuple.
      def getLatLon():
      pos = getPos()
      return (pos['latitude'], pos['longitude'])
      # Yes, we can do positioning.
      def hasPositioning():
      return True
       

      Retrieving Google Maps

      Retrieving Google maps is done in google_maps.py. An API key is pre-requisite for using the Google Maps API. If you do not have an API Key, you can obtain one from http://code.google.com/apis/maps/signup.html


      Warning
      Google Maps API is used in Where Am I! Typically, the web API is made freely available for non-commercial use only. You should always read the terms of use carefully before using any web API in your application.

      Once you get the API key, it should be assigned to GMAPS_KEY variable that is used in the URL for retrieing the required map i.e GMAPS_URL. The map can be retrieved using the urllib.urlretrieve function. The retrieved map is stored in a temporary file (TEMP_FILE) in the required format (GOOGLE_IMAGE_FORMAT). The urllib module is discussed in Advanced Network Programming (Python on Symbian).

       
      # google_maps.py
      # Module for retrieving and displaying Google Maps.
      #
      import urllib
      from graphics import Image
      # for generating temp file names
      import random
       
      from my_logging import log
       
      # The "key" that lets the Google Maps service know who is
      # accessing its service. Treat this as confidential information.
      #
      # You must obtain your own Google Maps key from
      # http://code.google.com/apis/maps/signup.html
      #
      GMAPS_KEY = "not set yet"
       
      if GMAPS_KEY == "not set yet":
      print "You must obtain a Google Maps key."
      print "See the google_maps.py source code for details."
       
      # The format of the images we retrieve. Legal values are gif, jpg,
      # jpg-baseline, png8, png32
      GOOGLE_IMAGE_FORMAT = "jpg"
       
      # Mapping from Google image format name to file extension.
      EXTENSIONS = {"gif": "gif", "jpg": "jpg", "jpg-baseline": "jpg",
      "png8": "png", "png32": "png"}
       
      # Where to store the images we retrieve. Keep it on D:, the temp device.
      # We pass this to urllib.urlretrieve, which should have the ability
      # to generate its own temp file names, but which is broken in PyS60.
      TEMP_FILE = u"D:\\temp"+str(random.randint(0, 10000))+"."+EXTENSIONS[GOOGLE_IMAGE_FORMAT]
       
      # The minimum zoom factor
      MIN_ZOOM = 0
       
      # The maximum zoom factor.
      MAX_ZOOM = 19
       
      # Base URL to be used to retrieve map images from Google. It's a format
      # string with the following fields, in order from left to right:
      #
      # %f - latitude of center of map
      # %f - longitude of center of map
      # %d - zoom factor to use, an integer from 0 (entire Earth) to
      # 19 (individual buildings)
      # %d - image width, in pixels
      # %d - image height, in pixels
      # %s - map type, a string equal to one of "roadmap" (a standard roadmap image),
      # "mobile" (a mobile roadmap map image, with larger features and text),
      # "satellite" (a satellite image),
      # "terrain" (a physical relief map, showing terrain and vegetation),
      # "hybrid" (a hybrid of the satellite and roadmap image).
      # %s - markers, a set of marker descriptors as described at
      # http://code.google.com/apis/maps/documentation/staticmaps/#Markers
      #
      GMAPS_URL = "http://maps.google.com/staticmap?center=%f,%f&format="+GOOGLE_IMAGE_FORMAT+"&zoom=%d&size=%dx%d&maptype=%s&markers=%s&key="+GMAPS_KEY
       
       
      #
      # An object that can produce a map of the area surrounding a given location.
      #
      class GoogleMaps:
      # Create a new Google Maps map factory. We pass in the desired image
      # width and height and map type, as those are likely to be common across
      # successive requests. map type is optional and defaults to "mobile".
      # marker color is optional and defaults to "green". marker size is
      # optional and defaults to "small".
      def __init__(self, (width, height), mapType="mobile", markerColor="green",
      markerSize="small"):
      self.width = width
      self.height = height
      self.mapType = mapType
      self.markerColor = markerColor
      self.markerSize = markerSize
       
      # Given a sequence of lat-lon pairs, return a string containing
      # map markers in the Google Maps format. We use a fixed marker color,
      # size, and no character labels.
      #
      # The string will look like
      # markers=markerDescriptor1|markerDescriptor2|markerDescriptor3|... etc.
      # where a markerDescription looks like
      # latitude,longitude,{size}{color}{alphanumeric-character}
      #
      # latitude - a latitudinal value with precision to 6 decimal places
      # longitude - a longitudinal value with precision to 6 decimal places
      # size - the size of marker, one of "tiny", "mid", "small"
      # color - a color from the set {black, brown, green, purple, yellow,
      # blue, gray, orange, red, white}.
      # alphanumeric-character - a single lowercase alphanumeric character
      # from the set {a-z, 0-9}. Default and mid sized markers are the
      # only ones capable of displaying an alphanumeric-character parameter.
      #
      # Only the latitude and longitude parameters are required. The others
      # are optional.
      def makeMarkerString(self, positions):
      str = ""
      for (lat, lon) in positions:
      str = str + ("%f,%f,%s%s|" % (lat, lon, self.markerSize,
      self.markerColor))
      return str
       
      # Set the width and height for all subsequent requests.
      def setImageSize(self, pos):
      width, height = pos
      log("setImageSize(%d,%d)" % (width, height))
      self.width = width
      self.height = height
       
      # Get the map centered at centerLatLon, at zoom factor zoom,
      # with markers at positions markerPositions, which may be empty.
      def getMapImage(self, centerLatLon, zoom, markerPositions):
      lat, lon = centerLatLon
      url = (GMAPS_URL % (lat, lon, zoom, self.width, self.height,
      self.mapType, self.makeMarkerString(markerPositions)))
      log("url = "+url)
      file, ignoredHeaders = urllib.urlretrieve(url, TEMP_FILE)
      return Image.open(file)
       

      Saving the Access Point and Debug Log

      For accessing the Google Maps API, the user is prompted for a valid Access Point (AP). To avoid querying the user every time for a valid access point, we use the persist_ap.py to save the user selected Access Point. The AP selection and persisting logic is as follows.

       
      Open the access point database.
      If it doesn't exist:
      Get list of available access points.
      If none available:
      Display error message.
      After user confirms, abort program.
      Otherwise:
      Display list of available access points.
      Get user's selection.
      Store it in DB.
      Set it as default.
      If it does exist:
      Get name of access point from DB.
      Look up name of access point in list of available ones.
      If not found:
      (duplicate logic of "If it doesn't exist" case above)
      Otherwise:
      Set this access point as the default.
       

      The persist_ap.py contains two important functions selectAP() and removePersistentAP(). selectAP() allows the user to select a valid access point and saves it in a database (CONN_DB_NAME).

       
      # Database containing connection info
      CONN_DB_NAME = u"e:\\python\\conninfo.db"
       
      # DB key for the Internet Access Point
      IAPID_KEY = u"iapid"
       
      def terminalError(msg):
      appuifw.note(msg, 'error')
       
      #
      # Let the user select an AP from the system's list.
      # Write it to the DB and return the ID and access point object.
      #
      def userChooseAndSelectAP(db):
      accessPoints = socket.access_points()
      if accessPoints and len(accessPoints) > 0:
      apID = socket.select_access_point()
      ap = socket.access_point(apID)
      socket.set_default_access_point(ap)
      db[IAPID_KEY] = str(apID)
      return apID, ap
      else:
      terminalError(u"No access points available")
      return (None, None)
       

      The removePersistentAP() clears the saved access point from the database (CONN_DB_NAME).

       
      #
      # Open the connection info DB in the given mode.
      # Return the DB object, or return None if it was not possible to open it
      # in that mode.
      #
      def openDB(mode="w"):
      try:
      db = e32dbm.open(CONN_DB_NAME, mode)
      log("opened DB %s mode %s" % (str(db), mode))
      return db
      except:
      log("openDB %s returns None" % mode)
      return None
       
      #
      # Remove the Access Point info stored in the database.
      # Note that this doesn't affect the application's current default access point.
      # A subsequent call to selectAP should pop up the Access Point menu and
      # then set the default AP.
      #
      def removePersistentAP():
      db = openDB(mode="w")
      try:
      del db[IAPID_KEY]
      finally:
      if db:
      db.close()
       

      my_logging.py contains functions for logging debug statements to a file (gFile). It contains four functions which are as follows,

      • disableLogging() - for disabling logging
      • logException() - for logging an exception
      • getLogFile() - returns path of the log file
      • log(msg) - to log msg to the log file
       
      # my_logging.py
      # Tiny logging module.
      # This abstracts away the means for adding entries to the debug log,
      # allowing us to turn it off, for example.
      #
       
      import time
       
      # Log file name - use the .txt extension so Symbian's File Manager app knows
      # how to open it, should you want to do that on the handset.
      LOG_FILE_NAME = 'e:/Python/my-log.txt'
       
      gFile = open(LOG_FILE_NAME, 'w')
       
      gEnabled = True
       
      def disableLogging():
      global gEnabled
      gEnabled = False
       
      def logException():
      import traceback
      traceback.print_exc(None, gFile)
       
      def getLogFile():
      return gFile
       
      def log(msg):
      global gEnabled
      if gEnabled:
      logStr = time.strftime('%I:%M:%S: ', time.localtime())+str(msg)+'\n'
      gFile.write(logStr)
       

      Using the above libraries in main.py

      After studying all those important library files, all that is remaining to do is to use them to retrieve the lat-long, retrieve the map and create a canvas to display the map to the user. We should also allow the user to zoom in and out (using up and down keys, in our case) and update the map accordingly.

       
      # main.py
      # Repeatedly fetch our position and display a map of it. Zoom in and out
      # using the up and down rocker keys.
      #
       
      import e32, appuifw
      from graphics import Image
      from key_codes import *
       
      # Choosing the directories where lat_lon.py and google_maps.py reside
      import sys
      sys.path.append('e:\Python\lib')
       
      import lat_lon, google_maps
       
      APP_LOCK = e32.Ao_lock()
       
      DEFAULT_ZOOM = 17
       
      def main():
      # Select a network access point
      persist_ap.selectAP()
       
      appuifw.app.screen='full'
      appuifw.exit_key_handler = lambda:APP_LOCK.signal()
       
      # This will create our map images.
      mapFactory = google_maps.GoogleMaps((240, 320), mapType="satellite")
       
      image = None
      canvas = None
       
      # Make zoom a list so we can set the zoom value from within handleEvent.
      # Python doesn't implement assignment of variables bound in an enclosing
      # lexical context correctly.
      zoom = [DEFAULT_ZOOM]
       
      # Set the zoom value. See note above regarding why we do it this way.
      def setZoom(newValue):
      zoom[0] = newValue
       
      # Get the zoom value. See note above regarding why we do it this way.
      def getZoom():
      return zoom[0]
       
      # Draw the map image.
      def drawImage(rect):
      canvas.blit(image)
      canvas.text(((10, 20)),
      u"Zoom: %d%%" % ((100.0 * getZoom()) / google_maps.MAX_ZOOM))
       
      # Handle keyboard events, like the zoom keys.
      def handleEvent(event):
      try:
      if event["type"] == appuifw.EEventKeyUp:
      if event["scancode"] == EScancodeUpArrow:
      # zoom in (larger factor)
      setZoom(min(google_maps.MAX_ZOOM, getZoom() + 1))
      elif event["scancode"] == EScancodeDownArrow:
      # zoom out (smaller factor)
      setZoom(max(google_maps.MIN_ZOOM, getZoom() - 1))
      except:
      # handle any exception here
      pass
       
      canvas = appuifw.Canvas(redraw_callback=drawImage,
      event_callback=handleEvent)
      appuifw.app.body = canvas
       
      oldLatLon = None
      while True:
      latLon = lat_lon.getLatLon()
      if oldLatLon == latLon:
      continue
      oldLatLon = latLon
      image = mapFactory.getMapImage(latLon, getZoom(), (latLon,))
      drawImage(())
       
      APP_LOCK.wait()
       
      if __name__ == "__main__":
      main()
       

      Source Code

      Download source code of Where Am I: File:WhereAmI.zip

      All files should be placed in E:\Python\lib, except main.py which can be placed in E:\data\Python or C:\data\Python. These paths may of course be changed with a corresponding change in the code as well.

      Sign in to comment…

      Contents

      Aaaaapo said…

      Is there an error in section "Using GPS Information"?

      {'available': 0, 'id': 270526873, 'name': u'Bluetooth GPS'} {'available': 1, 'id': 270526858, 'name': u'Integrated GPS'} {'available': 1, 'id': 270559509, 'name': u'Network based'}

      Bluetooth GPS is not available to the device. The id of Bluetooth GPS is 270526873. Integrated GPS is available to the device. The id of Bluetooth GPS is 270526858. Network based is not available to the device. The id of Bluetooth GPS is 270559509.

      This should be

      Bluetooth GPS is not available to the device. The id of Bluetooth GPS is 270526873. Integrated GPS is available to the device. The id of Integrated GPS is 270526858. Network based is available to the device. The id of Network based is 270559509.


      --Aaaaapo 12:58, 26 November 2009 (UTC)

      Aaaaapo said…

      In the section "Using GPS Information":

      "However, Assisted GPS (APS) - -"

      Should be "(A-GPS)".

      "- - which is also equipped in most of the GPS enabled mobile handsets, is a network based system that improves the startup performance of a GPS satellite-based positioning system, comes handy when using GPS devices indoors."

      I think A-GPS doesn't help using GPS indoors, but speeds up startup time as http://en.wikipedia.org/wiki/Assisted_GPS says.

      --Aaaaapo 13:02, 26 November 2009 (UTC)

      Croozeus said…

      Thanks Aapo for your comments!

      I've fixed them.

      --Croozeus 18:52, 6 February 2010 (UTC)

      Bogdan said…

      Very good chapter. Nicely organized.

      The only problem is the lack of indentation in the code: every line in every example starts at the extreme left, and in Python this is quite an issue ;)

      --Bogdan 19:25, 20 February 2010 (UTC)