Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
organizing by distance
#1
This is my code. Depending on the season and climate chosen I need to print the 5 closest cities from to coordinates 37°22'N and 107°25'W. I don't know where I would go from here. How do I find the closest cities. I already have the distance formula in my code but I can't implement it correctly

P.S. I know my for loops at the end are wrong. I have them there so I could get the correct syntax.

with open('cities.txt','r', encoding = 'cp1252') as fin: # opens file with read permission

    import math

    season = input('Will you take your vacation during summer or winter? ')
    climate = input('Do you want to go to a warm or a cold place? ')

    coldsummer = [] # these are empty lists that will be filled in later
    warmsummer = []
    coldwinter = []
    warmwinter = []
        
    data = fin.read().splitlines(True)
    
    for line in data[1:-1]: 
        line = line.split('\t') # splits elements in line with a tab

        lat = line[0] # these assign variables to certain parts of the file
        long = line[1]
        city = line[2]
          
        lat_deg = int(lat[0:2].strip('°').strip('N').strip('S')) # strips non integer parts
        lat_min = int(lat[3:5].strip('°').strip('N').strip('S'))
        long_deg = int(long[0:2].strip('°').strip('E').strip('W'))
        long_min = int(long[3:5].strip('°').strip('E').strip('W'))

        lat_DMS = lat_deg + (lat_min/60) # converts to degree-minute-second
        lat_rad = lat_DMS*(math.pi/180) # converts to radian
        long_DMS = long_deg + (long_min/60)
        long_rad = long_DMS*(math.pi/180)

        R = 6360 # assumed radius of Earth
        Mercedlat_DMS = 37 + 22/60 # coordinate for Merced, CA in degree-minute-second
        Mercedlat_rad = Mercedlat_DMS*(math.pi/180) # in radians
        Mercedlong_DMS = 107 + 25/60
        Mercedlong_rad = Mercedlong_DMS*(math.pi/180)

        distance = 2*R*math.sin(math.sqrt(math.sin((lat_rad - Mercedlat_rad)/2)**2 + # distance formula
                                          math.cos(lat_rad)*math.cos(Mercedlat_rad) *
                                          math.sin((long_rad - Mercedlong_rad)/2)**2))**-1

        if lat_deg > 66: # these if statements will fill in lists with appropriate values
            coldsummer.append(line)
            coldwinter.append(line)
        elif lat_deg > 35 and lat_deg < 66:
            warmsummer.append(line)
            coldwinter.append(line)
        else:
            warmsummer.append(line)
            warmwinter.append(line)
     
        for x in warmsummer: # the for loops will take desired list and print values
            if season == 'summer':
                if climate == 'warm':
                    print(x[2])
        for x in coldsummer:
            if season == 'summer':
                if climate == 'cold':
                    print(x[2])
        for x in warmwinter:
            if season == 'winter':
                if climate == 'warm':
                    print(x[2])
        for x in coldwinter:
            if season == 'winter':
                if climate == 'cold':
                    print(x[2])
Reply
#2
If you want to sort them by distance (closest first), you'll need to create a list of cities and use the list.sort() method. You could append the distance to each line from the file, append them to your lists as you already are, and then use the sort method.

If you only want to report cities within a specified distance, you should add a conditional to only append cities under that distance. Again, you'll want to append the distance to the line.

Something like this should work for the sorting option (you may have to fiddle with the key argument):

with open('cities.txt','r', encoding = 'cp1252') as fin: # opens file with read permission
 
    import math
 
    season = input('Will you take your vacation during summer or winter? ')
    climate = input('Do you want to go to a warm or a cold place? ')
 
    coldsummer = [] # these are empty lists that will be filled in later
    warmsummer = []
    coldwinter = []
    warmwinter = []
         
    data = fin.read().splitlines(True)
     
    for line in data[1:-1]: 
        line = line.split('\t') # splits elements in line with a tab
 
        lat = line[0] # these assign variables to certain parts of the file
        long = line[1]
        city = line[2]
           
        lat_deg = int(lat[0:2].strip('°').strip('N').strip('S')) # strips non integer parts
        lat_min = int(lat[3:5].strip('°').strip('N').strip('S'))
        long_deg = int(long[0:2].strip('°').strip('E').strip('W'))
        long_min = int(long[3:5].strip('°').strip('E').strip('W'))
 
        lat_DMS = lat_deg + (lat_min/60) # converts to degree-minute-second
        lat_rad = lat_DMS*(math.pi/180) # converts to radian
        long_DMS = long_deg + (long_min/60)
        long_rad = long_DMS*(math.pi/180)
 
        R = 6360 # assumed radius of Earth
        Mercedlat_DMS = 37 + 22/60 # coordinate for Merced, CA in degree-minute-second
        Mercedlat_rad = Mercedlat_DMS*(math.pi/180) # in radians
        Mercedlong_DMS = 107 + 25/60
        Mercedlong_rad = Mercedlong_DMS*(math.pi/180)
 
        distance = 2*R*math.sin(math.sqrt(math.sin((lat_rad - Mercedlat_rad)/2)**2 + # distance formula
                                          math.cos(lat_rad)*math.cos(Mercedlat_rad) *
                                          math.sin((long_rad - Mercedlong_rad)/2)**2))**-1

      line.append(distance)
 
        if lat_deg > 66: # these if statements will fill in lists with appropriate values
            coldsummer.append(line)
            coldwinter.append(line)
        elif lat_deg > 35 and lat_deg < 66:
            warmsummer.append(line)
            coldwinter.append(line)
        else:
            warmsummer.append(line)
            warmwinter.append(line)

        def sortByDistance(cityStats):
            return cityStats[len(cityStats - 1)]

        """ The key argument requires a function or method that will act upon each item in the list.
        The function defined above should suffice in this case.
        In effect, list.sort() does this with more finesse:

            length = len(self)
            for i in range(length):
                if key(self[i]) > key(self[i + 1]):
                    swap(self[i], self[i + 1])
        """
        coldsummer.sort(key = sortByDistance)
        warmsummer.sort(key = sortByDistance)
        coldwinter.sort(key = sortByDistance)
        warmwinter.sort(key = sortByDistance)
      
        for x in warmsummer: # the for loops will take desired list and print values
            if season == 'summer':
                if climate == 'warm':
                    print(x[2])
        for x in coldsummer:
            if season == 'summer':
                if climate == 'cold':
                    print(x[2])
        for x in warmwinter:
            if season == 'winter':
                if climate == 'warm':
                    print(x[2])
        for x in coldwinter:
            if season == 'winter':
                if climate == 'cold':
                    print(x[2])
Reply
#3
(Oct-11-2018, 01:55 AM)stullis Wrote: If you want to sort them by distance (closest first), you'll need to create a list of cities and use the list.sort() method. You could append the distance to each line from the file, append them to your lists as you already are, and then use the sort method.

If you only want to report cities within a specified distance, you should add a conditional to only append cities under that distance. Again, you'll want to append the distance to the line.

Something like this should work for the sorting option (you may have to fiddle with the key argument):

with open('cities.txt','r', encoding = 'cp1252') as fin: # opens file with read permission
 
    import math
 
    season = input('Will you take your vacation during summer or winter? ')
    climate = input('Do you want to go to a warm or a cold place? ')
 
    coldsummer = [] # these are empty lists that will be filled in later
    warmsummer = []
    coldwinter = []
    warmwinter = []
         
    data = fin.read().splitlines(True)
     
    for line in data[1:-1]: 
        line = line.split('\t') # splits elements in line with a tab
 
        lat = line[0] # these assign variables to certain parts of the file
        long = line[1]
        city = line[2]
           
        lat_deg = int(lat[0:2].strip('°').strip('N').strip('S')) # strips non integer parts
        lat_min = int(lat[3:5].strip('°').strip('N').strip('S'))
        long_deg = int(long[0:2].strip('°').strip('E').strip('W'))
        long_min = int(long[3:5].strip('°').strip('E').strip('W'))
 
        lat_DMS = lat_deg + (lat_min/60) # converts to degree-minute-second
        lat_rad = lat_DMS*(math.pi/180) # converts to radian
        long_DMS = long_deg + (long_min/60)
        long_rad = long_DMS*(math.pi/180)
 
        R = 6360 # assumed radius of Earth
        Mercedlat_DMS = 37 + 22/60 # coordinate for Merced, CA in degree-minute-second
        Mercedlat_rad = Mercedlat_DMS*(math.pi/180) # in radians
        Mercedlong_DMS = 107 + 25/60
        Mercedlong_rad = Mercedlong_DMS*(math.pi/180)
 
        distance = 2*R*math.sin(math.sqrt(math.sin((lat_rad - Mercedlat_rad)/2)**2 + # distance formula
                                          math.cos(lat_rad)*math.cos(Mercedlat_rad) *
                                          math.sin((long_rad - Mercedlong_rad)/2)**2))**-1

      line.append(distance)
 
        if lat_deg > 66: # these if statements will fill in lists with appropriate values
            coldsummer.append(line)
            coldwinter.append(line)
        elif lat_deg > 35 and lat_deg < 66:
            warmsummer.append(line)
            coldwinter.append(line)
        else:
            warmsummer.append(line)
            warmwinter.append(line)

        def sortByDistance(cityStats):
            return cityStats[len(cityStats - 1)]

        """ The key argument requires a function or method that will act upon each item in the list.
        The function defined above should suffice in this case.
        In effect, list.sort() does this with more finesse:

            length = len(self)
            for i in range(length):
                if key(self[i]) > key(self[i + 1]):
                    swap(self[i], self[i + 1])
        """
        coldsummer.sort(key = sortByDistance)
        warmsummer.sort(key = sortByDistance)
        coldwinter.sort(key = sortByDistance)
        warmwinter.sort(key = sortByDistance)
      
        for x in warmsummer: # the for loops will take desired list and print values
            if season == 'summer':
                if climate == 'warm':
                    print(x[2])
        for x in coldsummer:
            if season == 'summer':
                if climate == 'cold':
                    print(x[2])
        for x in warmwinter:
            if season == 'winter':
                if climate == 'warm':
                    print(x[2])
        for x in coldwinter:
            if season == 'winter':
                if climate == 'cold':
                    print(x[2])
I understand what this code is doing but the len() command returns this
Error:
line 54, in sortByDistance return cityStats[len(cityStats-1)] TypeError: unsupported operand type(s) for -: 'list' and 'int'
Reply
#4
Ah, typo that I missed. It should be:

return cityStats[len(cityStats) - 1]
Reply
#5
this is the text file https://transfernow.net/91bu72e2wb1l

My current code:
with open('cities.txt','r', encoding = 'cp1252') as fin: # opens file with read permission
  
    import math
  
    season = input('Will you take your vacation during summer or winter? ')
    climate = input('Do you want to go to a warm or a cold place? ')
  
    coldsummer = [] # these are empty lists that will be filled in later
    warmsummer = []
    coldwinter = []
    warmwinter = []
          
    data = fin.read().splitlines(True)
      
    for line in data[1:-1]: 
        line = line.split('\t') # splits elements in line with a tab
  
        lat = line[0] # these assign variables to certain parts of the file
        long = line[1]
        city = line[2]
            
        lat_deg = int(lat[0:2].strip('°').strip('N').strip('S')) # strips non integer parts
        lat_min = int(lat[3:5].strip('°').strip('N').strip('S'))
        long_deg = int(long[0:2].strip('°').strip('E').strip('W'))
        long_min = int(long[3:5].strip('°').strip('E').strip('W'))
  
        lat_DMS = lat_deg + (lat_min/60) # converts to degree-minute-second
        lat_rad = lat_DMS*(math.pi/180) # converts to radian
        long_DMS = long_deg + (long_min/60)
        long_rad = long_DMS*(math.pi/180)
  
        R = 6360 # assumed radius of Earth
        Mercedlat_DMS = 37 + 22/60 # coordinate for Merced, CA in degree-minute-second
        Mercedlat_rad = Mercedlat_DMS*(math.pi/180) # in radians
        Mercedlong_DMS = 107 + 25/60
        Mercedlong_rad = Mercedlong_DMS*(math.pi/180)
  
        distance = 2*R*math.sin(math.sqrt(math.sin((lat_rad - Mercedlat_rad)/2)**2 + # distance formula
                                          math.cos(lat_rad)*math.cos(Mercedlat_rad) *
                                          math.sin((long_rad - Mercedlong_rad)/2)**2))**-1
        line.append(distance)
  
        if lat_deg > 66: # these if statements will fill in lists with appropriate values
            coldsummer.append(line)
            coldwinter.append(line)
        elif lat_deg > 35 and lat_deg < 66:
            warmsummer.append(line)
            coldwinter.append(line)
        else:
            warmsummer.append(line)
            warmwinter.append(line)
 
        def sortByDistance(cityStats):
            return cityStats[len(cityStats)-1]
                    
        coldsummer.sort(key = sortByDistance)
        warmsummer.sort(key = sortByDistance)
        coldwinter.sort(key = sortByDistance)
        warmwinter.sort(key = sortByDistance)
       
        for x in warmsummer: # the for loops will take desired list and print values
            if season == 'summer':
                if climate == 'warm':
                    print(x[2])
        for x in coldsummer:
            if season == 'summer':
                if climate == 'cold':
                    print(x[2])
        for x in warmwinter:
            if season == 'winter':
                if climate == 'warm':
                    print(x[2])
        for x in coldwinter:
            if season == 'winter':
                if climate == 'cold':
                    print(x[2])
I'm not sure what I'm doing wrong.
I need to print out the 5 closes cities that correspond with the user input. (ex: if the user inputs 'summer' and 'warm' then I need to print the closest 5 cities from that list)
Reply
#6
First, there are a couple things to adjust before getting to the problem at hand. All the lines from 53 down should not be in the for loop started on line 15. By having those lines under that, the script will perform the other loops and assessments repeatedly when we only have to do them once (plus, I believe list.sort() is a bubble sort which is has poor performance in the first place; let's not do that over and over again).

Lines 53 through the end should not be in the with body either. By the time we get to those lines, we're done with the file so let's get rid of the file and free up some memory before running those lines.

The function on line 53... I would move it to the top. I had it right before its call for demonstration purposes. For implementation, I would put it at the top so it gets defined once and then called later.

Likewise for line 3, imports should be at the very top of your file. The layout should be imports at the top, followed by class and function definitions, and finally some implementation code (if present).

As for the problem... what exactly is happening? I suspect that you aren't getting five or fewer results printed. Perhaps the sortByDistance function needs a revisit too though that should already be working.
Reply
#7
(Oct-16-2018, 12:28 AM)stullis Wrote: First, there are a couple things to adjust before getting to the problem at hand. All the lines from 53 down should not be in the for loop started on line 15. By having those lines under that, the script will perform the other loops and assessments repeatedly when we only have to do them once (plus, I believe list.sort() is a bubble sort which is has poor performance in the first place; let's not do that over and over again).

Lines 53 through the end should not be in the with body either. By the time we get to those lines, we're done with the file so let's get rid of the file and free up some memory before running those lines.

The function on line 53... I would move it to the top. I had it right before its call for demonstration purposes. For implementation, I would put it at the top so it gets defined once and then called later.

Likewise for line 3, imports should be at the very top of your file. The layout should be imports at the top, followed by class and function definitions, and finally some implementation code (if present).

As for the problem... what exactly is happening? I suspect that you aren't getting five or fewer results printed. Perhaps the sortByDistance function needs a revisit too though that should already be working.

THANK YOU! That really fixed a lot of problems, But how do I limit the number of printed results in this case? since 'x[2]' is printing out the 3rd column. I only need the first 5.
Reply
#8
One more improvement and then the answer. Lines 61 through 76 are expensive. They are looping though each of your four lists and then checking the user input. So, if the user specifies "warm summer", the script with loop through the 200+ entries for coldwinter (and the other two) and then ask if that's what the user wants.

A colloquial way to think of it: You tell a friend "we should get pizza" and your friend then asks if you want to go to 200 different Chinese restaurants before asking about pizza places. It's frustrating and, more importantly here, time consuming.

If we swap the conditionals and the for loops, we will get better performance. If the user specifies "warm summer", this version will only loop over the warmsummer list and ignore the other three:

if climate == "warm":
    if season == "summer":
        for locale in warmsummer:
            print(locale[2])
    elif season == "winter":
        for locale in warmwinter:
            print(locale[2])
elif climate == "cold":
    if season == "summer":
        for locale in coldsummer:
            print(locale[2])
    elif season == "winter":
        for locale in coldwinter:
            print(locale[2])
Now for the problem at hand! If you only want the first five, you can retrieve those very simply by slicing the lists. Instead of iterating over the entire list, we want to iterate over the first first items only:

if climate == "warm":
    if season == "summer":
        for locale in warmsummer[:5]:
            print(locale[2])
    elif season == "winter":
        for locale in warmwinter[:5]:
            print(locale[2])
elif climate == "cold":
    if season == "summer":
        for locale in coldsummer[:5]:
            print(locale[2])
    elif season == "winter":
        for locale in coldwinter[:5]:
            print(locale[2])
There are other, more Pythonic ways to write this. For now though, you should be set.

Also, x[2] returns the third column because Python is zero indexed. So, column 1 is x[0].
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Organizing several similar classes with overlapping variables 6hearts 7 1,329 May-07-2023, 02:00 PM
Last Post: 6hearts
  Organizing list of objects by attribute scarney1988 3 2,155 Mar-11-2020, 03:55 PM
Last Post: scarney1988
  Visualize Geo Map/Calculate distance zarize 1 1,860 Dec-05-2019, 08:36 PM
Last Post: Larz60+
  Organizing Data in Dictionary Ranjirock 3 2,580 Aug-27-2019, 02:48 PM
Last Post: Ranjirock
  euclidean distance jenya56 3 2,769 Mar-29-2019, 02:56 AM
Last Post: scidam
  Organizing tips Demini 1 2,183 Feb-10-2018, 05:32 AM
Last Post: metulburr
  Python Ble Distance Problem aleynasarcanli 10 12,417 Feb-09-2018, 10:48 PM
Last Post: aleynasarcanli
  Organizing results acmichelman 2 3,232 Sep-05-2017, 06:39 PM
Last Post: ichabod801

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020