text: # coding=utf-8
  
  from __future__ import unicode_literals
  
  from os import path
  
  
  try:
      with open(path.join(path.dirname(__file__), 'api_key'), 'r') as f:
          API_KEY = f.read().strip()
  except IOError:
      API_KEY = 'adf51226795afbc4e7575ccc124face7'  # default key used by drivenow.com
  
  
  CITIES = {
      'berlin': {
          'loc_key': 6099,
          'electric': 'some',
          'BOUNDS': {
              # actual bounds based on operation areas are
              # 52.572632, 52.389571, 13.565218, 13.183849
  
              # use slightly wider values to allow for GPS wobble
              'NORTH': 52.573,
              'SOUTH': 52.389,
              'EAST': 13.566,
              'WEST': 13.183
          },
          'DEGREE_LENGTHS': {
              # for latitude 52.52
              'LENGTH_OF_LATITUDE': 111277.17,
              'LENGTH_OF_LONGITUDE': 67879.39
          },
          'MAP_SIZES': {
              'MAP_X': 1920,
              'MAP_Y': 1080
          },
          'MAP_LIMITS': {
              # Fit Berlin's car2go and Drivenow in one 16:9 image.
              # Before expanding to 16:9, the limits were 52.576767, 52.38927, 13.565218, 13.181448
              # At 1920x1080 pixels, 16:9, the map is:
              # http://render.openstreetmap.org/cgi-bin/export?bbox=13.099773,52.38927,13.646893,52.576767&scale=113281&format=png
              'NORTH': 52.576767,
              'SOUTH': 52.38927,
              'EAST': 13.646893,
              'WEST': 13.099773
          }
      },
      'brussels': {
          'loc_key': 42619
      },
      'kobenhavn': {
          'loc_key': 41369,
          'electric': 'all',
          'display': 'Copenhagen'
      },
      'duesseldorf': {
          'loc_key': 1293,
          'display': 'Düsseldorf',
      },
      'hamburg': {
          'loc_key': 40065,
          'electric': 'some'
      },
      'koeln': {
          'loc_key': 1774,
          'display': 'Cologne',
          'localized': {
              'de': 'Köln'
          }
      },
      'london': {
          'loc_key': 40758,
          'electric': 'some',
          'BOUNDS': {
              'NORTH': 51.612,  # exact value is 51.611141
              'SOUTH': 51.518,  # exact value is 51.518598
              'EAST': 0.022,  # exact value is 0.021994
              'WEST': -0.165  # exact value is -0.164666
          },
          'MAP_LIMITS': {
              # http://render.openstreetmap.org/cgi-bin/export?bbox=-0.20593,51.518,0.06293,51.612&scale=55659&format=png
              'NORTH': 51.612,
              'SOUTH': 51.518,
              'EAST': 0.06293,
              'WEST': -0.20593
          },
          'DEGREE_LENGTHS': {
              # for latitude 51.56
              'LENGTH_OF_LATITUDE': 111258.94,
              'LENGTH_OF_LONGITUDE': 69349.27
          },
          'MAP_SIZES': {
              'MAP_X': 1920,
              'MAP_Y': 1080
          },
          'LABELS': {
              'fontsizes': [35, 22, 30, 18],
              'lines': [
                  (250, 210),
                  (250, 170),
                  (250, 130),
                  (250, 95)
              ]
          }
      },
      'milano': {
          'loc_key': 42756,
      },
      'muenchen': {
          'loc_key': 4604,
          'electric': 'some',
          'display': 'Munich',
          'localized': {
              'de': 'München'
          }
      },
      'stockholm': {
          'loc_key': 42128
      },
      'wien': {
          'loc_key': 40468,
          'display': 'Vienna',
          'localized': {
              'de': 'Wien'
          }
      }
  }
  
  API_AVAILABLE_VEHICLES_URL = 'https://api2.drive-now.com/cities/{loc}?expand=cars'
  
  # fill in city data that can be assumed and autogenerated
  for city, city_data in CITIES.items():
      city_data['of_interest'] = True  # we want everything for now
  
      city_data['API_AVAILABLE_VEHICLES_URL'] = API_AVAILABLE_VEHICLES_URL.format(loc=city_data['loc_key'])
      city_data['API_AVAILABLE_VEHICLES_HEADERS'] = {
          'X-Api-Key': API_KEY,
          'User-Agent': 'electric2go',
          'Accept': 'application/json',
          'Referer': 'https://de.drive-now.com/',
          'Origin': 'https://de.drive-now.com'
      }
  
  
  def get_operation_areas(city_key):
      import requests
  
      API_URL = 'https://api2.drive-now.com/geodata/{loc_key}/{loc_key}.kml'
  
      r = requests.get(API_URL.format(loc_key=CITIES[city_key]['loc_key']),
                       headers={'User-Agent': 'electric2go'})
  
      return get_areas_from_kml(r.content, '{http://www.opengis.net/kml/2.2}')
  
  
  def get_areas_from_kml(kml_bytes, ns):
      from lxml import etree
  
      results = []
  
      xml = etree.fromstring(kml_bytes)
  
      folders = xml.findall('.//' + ns + 'Folder')
  
      for folder in folders:
          name = folder.find(ns + 'name')
          if name.text == 'allowedParkingAreas':
              areas = folder.findall(ns + 'Placemark')
  
              results.extend([get_details_from_kml(area, ns) for area in areas])
  
      return results
  
  
  def get_details_from_kml(area, ns):
      return {
          'name': area.find(ns + 'name').text,
  
          # JSON-formatted description for the parking area
          'description': area.find(ns + 'description').text,
  
          # KML-string-formatted, process with get_max_latlng()
          'coordinates': area.find('.//' + ns + 'coordinates').text
      }
  
  
  def get_max_latlng(area):
      # collect lats and longs from a KML-formatted list
  
      string_coords = area['coordinates']
  
      # string_coords looks like:
      # -0.121107,51.530953,0 -0.12156,51.53088,0 -0.122024,51.53085,0 -0.122228,51.530843,0 -0.122346,51.530913,0 ...
      # So whitespace-separated objects then comma-separated lng,lat,elevation.
      # Elevation is always 0 for Drivenow, ignore it
  
      pairs = string_coords.split()
      split = (pair.split(',') for pair in pairs)
      parsed = ((float(pair[1]), float(pair[0])) for pair in split)
      latitudes, longitudes = zip(*parsed)
  
      return max(latitudes), min(latitudes), max(longitudes), min(longitudes)
  
  
  def print_operation_areas(city_key):
      import json
  
      areas = get_operation_areas(city_key)
  
      for area in areas:
          description = json.loads(area['description'])
          print('{name}: {type} zone'.format(**description))
          print('border points: {len}, bounds: {bounds}'.format(
              len=len(area['coordinates']), bounds=get_max_latlng(area)))
  
  
  def get_latlng_extent(city_key):
      areas = get_operation_areas(city_key)
  
      latitudes = []
      longitudes = []
  
      # collect max lats and longs across all 'operation areas'
      for area in areas:
          max_lat, min_lat, max_lng, min_lng = get_max_latlng(area)
          latitudes.append(max_lat)
          latitudes.append(min_lat)
          longitudes.append(max_lng)
          longitudes.append(min_lng)
  
      # return max/mins for all operation areas
      return max(latitudes), min(latitudes), max(longitudes), min(longitudes)
  