<jupyter_start><jupyter_text>Drone Lighthouses Network XYZ is scoping out a project to aid the search and rescue operations of the Swedish Coast Guard. Their goal is to establish a network of docked drones along the coastline that provide a rapid response to emergency transmissions.(The XYZ Dock is a cloud-connected, weatherproof home base and charging station that enables our drones to fly without a pilot on-scene.)The goal is to establish coverage from their main office in Stockholm east to the island of Vindö and then southwest to Torö.They have permission to build a dock into any lighthouse in the region. They are interested in building a route such that a drone can hop from dock to dock from Stockholm to Torö, so that they can recover their fleet and swap drones without sending humans to the lighthouses.  Part 1: Find all of the lighthousesUse [OpenStreetMap](https://en.wikipedia.org/wiki/OpenStreetMap) to find known lighthouses in the region and get that data into your notebook. OSM provides APIs to query their geographic database by structure type. Plot the lighthouses on a map using [pydeck](https://deckgl.readthedocs.io/en/latest/).Note that you are free to install python packages in colab: `!pip install pydeck`<jupyter_code># Install required dependencies
!pip install pydeck
!pip install OSMPythonTools
!pip install geopy
import os
from OSMPythonTools.nominatim import Nominatim
from OSMPythonTools.overpass import overpassQueryBuilder, Overpass
from OSMPythonTools.api import Api
import pydeck as pdk
import pandas as pd
import csv, math
import requests
import urllib.parse
import geopy.distance

class DataConverter:
  """
  Data converter class
  """
  def __init__(self, result=None, structure=None):
    """
    Constructor

    Arguments:
    result - Overpass query object
    structure - Element we want to plot for eg. lighthouse

    Returns:
    None
    """
    self.result = result
    self.structure = structure

  def writeToCsv(self, data):
    """
    Writes data to a CSV file

    Arguments:
    data - Data to be written, expects list of strings

    Returns:
    None
    """
    with open('/content/data/' + self.structure + '.csv', 'a', newline='') as file:
      writer = csv.writer(file)
      writer.writerow(data)

  def pointsToCsv(self):
    """
    Convert overpass geometry of points list
    to CSV file with (lng, lat) for each point.
    Each point represents the structure to be plotted.

    Arguments:
    None

    Returns:
    None
    """
    elements = self.result.elements()
    geometries = [element.geometry() for element in elements]
    if not os.path.exists('/content/data/' + self.structure + '.csv'):
      self.writeToCsv(data=['lng', 'lat'])
    for geometry in geometries:
      self.writeToCsv(data=[str(geometry['coordinates'][0]), 
                            str(geometry['coordinates'][1])])

class GeographyPlotter:
  """
  Geography plotter class
  """
  def __init__(self, region, regionPoints, structure, latitude, longitude):
    """
    Constructor

    Arguments:
    region - Country
    regionPoints - List of cities/islands
    structure = Element we want to plot for eg. lighthouse
    latitude = Latitude
    longitude = Longitude

    Returns:
    None
    """
    self.region = region
    self.structure = structure
    self.latitude = latitude
    self.longitude = longitude
    self.regionPoints = regionPoints

  def findLocation(self, address):
    """
    Finds location of a given address

    Arguments:
    address - Address

    Returns:
    Response object list of dictionary with Lat and Lng keys
    """
    url = 'https://nominatim.openstreetmap.org/search/' + urllib.parse.quote(address) +'?format=json'
    response = requests.get(url).json()
    return response

  def regionPointsToCoords(self):
    """
    Fetches the coordinates of the cities/islands list

    Arguments:
    None

    Returns:
    Coordinates list with (lat, lng) for each point
    """
    coords = list()
    for i in range(len(self.regionPoints)):
      address = self.regionPoints[i] + ', ' + self.region
      location = self.findLocation(address)
      latitude, longitude = location[0]["lat"], location[0]["lon"]
      coords.append((float(latitude), float(longitude)))
    return coords

  def queryRegionPointsCoords(self):
    """
    Fetches the coordinates of structure lying nearest to the 
    line between Pi and Pi+1 and does so for all 
    i points in the coordinates list

    Arguments:
    None

    Returns:
    Coordinates list with (lat, lng) for each point that satisfies
    the distance condition
    """
    coords = self.regionPointsToCoords()
    for i in range(len(coords) - 1):
      converter = DataConverter(structure='optimal_' + self.structure)
      if not os.path.exists('/content/data/' + 'optimal_' + self.structure + '.csv'):
        converter.writeToCsv(data=['lng', 'lat'])
      
      # Iterate through each point in csv
      with open('/content/data/' + self.structure + '.csv') as file:
        reader = csv.reader(file)
        for row in reader:
          if row == ['lng', 'lat']:
            continue
          x, y = float(row[0]), float(row[1])
          d1 = geopy.distance.distance((y, x), coords[i]).m
          d2 = geopy.distance.distance((y, x), coords[i+1]).m
          d = d1 + d2
          distanceSourceDest = geopy.distance.distance(coords[i], coords[i+1]).m
          distance = d - distanceSourceDest
          if distance < 5000:
            converter.writeToCsv(data=[x, y])
    return coords

  def queryRegionCoords(self):
    """
    Fetches the coordinates of all structures lying
    in the region and stores each point as (lng, lat)
    in CSV file.

    Arguments:
    None

    Returns:
    None
    """
    nominatim = Nominatim()
    areaId = nominatim.query(self.region).areaId()
    print('region: ({}), areaId: {}'.format(self.region, areaId))
    overpass = Overpass()
    if areaId is None:
      print(self.region + ' areaId not found, hence not plotting')
      return
    query = overpassQueryBuilder(area=areaId,
                                elementType='node',
                                selector='"man_made"=' + '"' + self.structure + '"',
                                includeGeometry=True)
    result = overpass.query(query)
    converter = DataConverter(result=result,
                              structure=self.structure)
    converter.pointsToCsv()
  
  def initializePlot(self, name):
    """
    Initializes the map plot

    Arguments:
    name - Name of csv file to be plotted

    Returns:
    Pydeck map layer and view state
    """
    DATA = ('/content/data/' + name)
    df = pd.read_csv(DATA)
    # Define a layer to display on a map
    layer = pdk.Layer(
        'HexagonLayer',
        df,
        get_position=['lng', 'lat'],
        auto_highlight=True,
        elevation_scale=50,
        pickable=True,
        elevation_range=[0, 3000],
        extruded=True,
        coverage=1
    )
    # Set the viewport location
    view_state = pdk.ViewState(
        longitude=self.longitude,
        latitude=self.latitude,
        zoom=8,
        min_zoom=5,
        max_zoom=15,
        pitch=0,
        bearing=0)
    
    return layer, view_state

  def plot(self, name):
    """
    Plot the points on the map

    Arguments:
    name - Name of csv file to be plotted

    Returns:
    None
    """
    layer, view_state = self.initializePlot(name=name)
    # Combined all of it and render a viewport
    r = pdk.Deck(layers=[layer], 
                 initial_view_state=view_state)
    r.to_html(self.structure + '.html')

region = 'Sweden'
regionPoints = ['Stockholm','Vindö','Torö']
structure = 'lighthouse'

if os.path.exists('/content/data/'  + structure + '.csv'):
  os.remove('/content/data/'  + structure + '.csv')
if os.path.exists('/content/data/'  + 'optimal_' + structure + '.csv'):
  os.remove('/content/data/'  + 'optimal_' + structure + '.csv')

plotter = GeographyPlotter(region=region,
                           regionPoints=regionPoints,
                           structure=structure,
                           latitude=59.3293,
                           longitude=18.0686)
plotter.queryRegionCoords()
coords = plotter.queryRegionPointsCoords()
plotter.plot('optimal_lighthouse.csv')<jupyter_output><empty_output><jupyter_text>Part 2: Choose where to build docksSuggest a minimal set of lighthouses at which to build a dock that would allow a drone to traverse from Stockholm to Torö, if each drone has a usable battery life of 30 minutes and travels at a ground speed of 12 meters per second. Plot the route, assuming approximate endpoints at Stockholm and Torö. Also print the total route distance.It's okay to make reasonable assumptions here and state your thinking. You can go as straightforward or complex as you like.<jupyter_code># your code here
class Drone:
  """
  Drone class
  """
  def __init__(self, pointsFile, batteryLife, groundSpeed, coords):
    """
    Constructor

    Arguments:
    pointsFile - Optimal points file
    batteryLife -  Drone Battery life in minutes
    groundSpeed - Ground speed of Drone in seconds
    coords - Optimal coordinates list

    Returns:
    None
    """
    self.pointsFile = pointsFile
    self.batteryLife = batteryLife
    self.groundSpeed = groundSpeed
    self.coords = coords
    self.range = self.groundSpeed*self.batteryLife*60

  def headingDirection(self, source, destination):
    """
    Check direction to which drone needs to head
    towards

    Arguments:
    source - Source point
    destination - Destination point

    Returns:
    headingNorth, headingEast. If headingNorth = True,
    drone needs to head north else south. If headingEast = True,
    need to head east else west.
    """
    # Check heading direction
    headingNorth, headingEast =  False, False
    source_lat, source_lng = source[0], source[1]
    destination_lat, destination_lng = destination[0], destination[1]
    if destination_lat > source_lat:
      headingNorth = True
    if destination_lng > source_lng:
      headingEast = True
    return headingNorth, headingEast

  def findNextLighthouse(self, source, destination):
    """
    Finds the coordinates of the next lighthouse
    available from source point. 

    Arguments:
    source - Source point
    destination - Destination point

    Returns:
    Coordinates of the next lighthouse
    """
    MaxDistSource = 0
    nextLighthouse = None
    headingNorth, headingEast = self.headingDirection(source=source,
                                                      destination=destination)
    with open('/content/data/' + self.pointsFile) as file:
      reader = csv.reader(file)
      for row in reader:
        if row == ['lng', 'lat']:
          continue
        x, y = float(row[0]), float(row[1])
        d1 = geopy.distance.distance((y, x), source).m
        if headingNorth and (y < source[0]) and d1 > 15000:
          continue
        if headingEast and (x < source[1]) and d1 > 15000:
          continue
        if not headingNorth and (y > source[0]) and d1 > 15000:
          continue
        if not headingEast and (x > source[1]) and d1 > 15000:
          continue
        if (d1 < self.range) and (d1 > MaxDistSource):
          MaxDistSource = d1
          nextLighthouse = (y, x)
    return nextLighthouse

  def routeBtwPoints(self, source, destination):
    """
    Finds the route between any two source and 
    destination points

    Arguments:
    source - Source point
    destination - Destination point

    Returns:
    Route list with (lat, lng) points
    """
    # Store the route in this list
    route = list()
    route.append(source)
    sourceDistance = geopy.distance.distance(source, destination).m
    reachedDestination = False
    while not reachedDestination:
      nextLighthouse = self.findNextLighthouse(source=source,
                                               destination=destination)
      route.append(nextLighthouse)
      destinationDistance = geopy.distance.distance(nextLighthouse, destination).m
      if destinationDistance < self.range:
        reachedDestination = True
      source = nextLighthouse
    route.append(destination)
    return route

  def calculateRouteDistance(self):
    """
    Calculates Total Route Distance 

    Arguments:
    None

    Returns:
    Total route distance
    """
    distance = 0
    for i in range(len(self.finalRoute) - 1):
      distance += geopy.distance.distance(self.finalRoute[i],
                                          self.finalRoute[i+1]).m
    return distance

  def findRoute(self):
    """
    Finds the complete route from first
    to last endpoint

    Arguments:
    None

    Returns:
    Final route list with (lat, lng) points
    """
    finalRoute = []
    MaxDistSource = 0
    for i in range(len(coords) - 1):
      source, destination = coords[i], coords[i+1]
      route = self.routeBtwPoints(source=source,
                                  destination=destination)
      finalRoute += route
    # Convert route to csv for plotting
    converter = DataConverter(structure='route')
    if not os.path.exists('/content/data/route.csv'):
      converter.writeToCsv(data=['lng', 'lat'])
    for point in finalRoute:
      converter.writeToCsv(data=[str(point[1]), str(point[0])])
    
    self.finalRoute = finalRoute
    return self.finalRoute
# Get list of lighthouse coords from Stockholm to Vindo and vindo to toro
drone = Drone(pointsFile='optimal_' + structure + '.csv',
              batteryLife=30,
              groundSpeed=12,
              coords=coords)

if os.path.exists('/content/data/route.csv'):
  os.remove('/content/data/route.csv')

finalRoute = drone.findRoute()
totalRouteDistance = drone.calculateRouteDistance()
# print the suggested set of lighthouses and the total distance
print('Final Route:', finalRoute)
print('Total Route Distance: {} Kms'.format(totalRouteDistance/1000))
# plot the route
plotter.plot('route.csv')<jupyter_output><empty_output><jupyter_text>Part 3: Get drones from Stockholm to the docks!Let's say we installed empty docks into each of the selected lighthouses. How long would it take to fill every dock if we are dispatching the drones from Stockholm, and a drone takes 45 minutes to fully charge its battery in the dock?You may choose to write code here at any level of depth for this problem, or you can provide a ballpark estimate with a few written sentences.<jupyter_code># your code or written description here
class Dock:
  """
  Dock class
  """
  def __init__(self, batteryLife, groundSpeed, route, timeToCharge):
    """
    Constructor 

    Arguments:
    batteryLife - Drone battery life in minutes
    groundSpeed - Ground speed of drone in m/s
    route - Final route of the drone
    timeToCharge = Time it takes to charge the drone in minutes

    Returns:
    None
    """
    self.batteryLife = batteryLife*60
    self.fullBatteryLife = self.batteryLife
    self.groundSpeed = groundSpeed
    self.route = route
    self.timeToCharge = timeToCharge*60
  
  def charge(self):
    """
    Charges the drone 

    Arguments:
    None

    Returns:
    Time it took to charge the drone in seconds
    """
    batteryLifeNeeded = self.fullBatteryLife - self.batteryLife
    chargingTime = (self.timeToCharge/self.fullBatteryLife)*batteryLifeNeeded
    return chargingTime

  def timeToFillDocks(self):
    """
    Total time it takes to charge the drone
    in adding up each dock 

    Arguments:
    None

    Returns:
    Total time it takes to fill docks in seconds
    """
    totalTime = 0
    
    for i in range(len(self.route) - 1):
      distance = geopy.distance.distance(self.route[i], self.route[i+1]).m
      time = distance/self.groundSpeed
      self.batteryLife -= time
      chargingTime = self.charge()
      totalTime += chargingTime
      self.batteryLife = self.fullBatteryLife

    return totalTime
# your estimate of the duration required to fill the docks
dock = Dock(batteryLife=30,
            groundSpeed=12,
            route=finalRoute, 
            timeToCharge=45)

totalTime = dock.timeToFillDocks()
print('Total estimated time to fill up docks: {} Mins'.format(totalTime/60))<jupyter_output>Total estimated time to fill up docks: 311.11419069479837 Mins
{'fraction_non_alphanumeric': 0.08902095331454479, 'fraction_numerical': 0.00869991422619777, 'mean_word_length': 2.7412330964932385, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 21, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '521952', 'license': 'MIT', 'n_tokens_mistral': 4806, 'n_tokens_neox': 4533, 'n_words': 1515, 'path': 'drone_lighthouse_network.ipynb', 'repo_name': 'nalindas9/drone-docking-network', 'size': '16322'}