Explore the World with Python

A few semesters ago I had a cartography class where the professor would quiz us on countries by shape at the start of class, or add them as bonus questions to tests. I’m not going to lie, I was embarrassed by my woeful lack of knowledge of basic world geography.

I was recently thinking back on that and realized that I’ve done nothing in the meantime to rectify that situation.  So this Python script popped into my head.  One that I can set to run every morning on my home computer before I wake up, so when I flip my monitor on I’ll have a small geography lesson waiting for me everyday.

Here’s how it works. When I was trying to think of an internet repository of information on countries I immediately thought of Wikipedia.  As that source is often looked down upon, my second thought was the CIA World Factbook.  From the WFB website I was able to find a table with all the countries, territories, sovereignties, etc (even some bailiwicks) that the WFB has entries on and the two letter code they use in the webpage for them.  I was going to limit it to countries, but I thought why not learn about all the interesting places the WFB has entries on. There are 271 entries on the list with, of course, 196 of them being countries.

The script was originally about 10 lines, but as I thought of things to add it’s grown to over 130 (w/out comments).  Error handling took up a good chuck of that.  It will randomly choose a WFB entry based on the table from the WFB, and bring up both the Wikipedia and CIA World Factbook pages in your default browser.  It will also open a window with the name of the place of interest, and if it’s not a country it will attempt to tell you the entry’s country affiliation, and display both its regional map (world locator map) and a close-up map of the country/place of interest. Please see the comments in the code at the bottom of the page for more details on the inner-workings.

Examples of the Python window:

The “Hotlinks” text box at the bottom of the window is for my use. In case I decide to write a post about a particularly interesting place, I can easily copy and paste map image locations.

If it’s a country it will look like the screen shot below.

country_ex

If it’s not a country it will display the name of the place and its affiliation (if available).

non_country_ex

And if it can’t find either map, which is sometimes the case, it will display a generic world map from the CIA WFB with caption.

no_WL_mapno_maps_no_WFB_entry

I’m very new to Python; therefore, I’m putting the code below so maybe someone more knowledgeable can help me make it a bit more elegant.  All my comments are in it so you can hopefully follow my thought process.

If you would like the uncommented script and WFB table data just click the following link to download it:  Python Script for “Country a Day”

## General note: not all entries have a CIA WFB page, but most have WFB maps, 
## and all have Wikipedia entries; therefore, at times the CIA WFB page that
## comes up will be 404 Page Not Found.
 
import webbrowser, random, tkinter, urllib.request, base64, time
 
## Function to select country at random
def roll_country():
## Open text file and reads it into a list.    
    country_file=open("country_cia_code.txt", "r")
    country_line=[]
    for line in country_file:
        country_line.append(line.strip())
    country_file.close()
 
## The 272nd item on the list is a counter that increases by one, therefore, 
## if all countries have been picked the counter will equal 271 and the 
## following check will trip. If user answers yes, then the .txt file will be
## rewitten to delete the word used on all countries and reset the counter to 0.
    if country_line[271]=="271":
        print("You're all out of countries!")
        start_over=input("Do you want to start over? (y/n): ")
        while start_over!="y" and start_over!="Y" and start_over!="N" and start_over!="n":
            start_over=input("Do you want to start over? (y/n): ")
 
        if start_over=="y" or start_over=="Y":
            for i in range(len(country_line)):
                country_line[i]=country_line[i].replace("used","")
            country_line[271]=0
            rewrite(country_line)
        elif start_over=="n" or start_over=="N":
            quit()   
 
## There are 271 items in the list, this generates a random #/country.
    call=random.randrange(0,270)       
 
## This checks to see if the country has been called before and calls a new random number
## if it's been previously used.
## Special case - due to formatting of one name in Wikipedia, the value of
## entry 204 has an extra split, so the usage check is different than the rest.  
    while country_line[call].split(",")[2]=="used" or call==204 and country_line[204].split(",")[3]=="used":
        call=random.randrange(0,270)    
## If it hasn't been called, the items in the list are split at the commas and
## assigned to variables. Split locations: 0=how country looks in wikipedia address,
## 1=CIA two letter code, 2=blank but will be changed to "used". Count at 272
## increased by 1.
    else:
        wiki=country_line[call].split(",")[0]
        cia=country_line[call].split(",")[1]
        country_line[call]+="used"
        country_line[271]=int(country_line[271])+1
 
## Unfortunately, since commas are the delimiters adn wikipedia had one address with a comma in it,
## variable assignment was thrown off a bit if it was chosen.
    if wiki=="Saint_Helena":
        wiki="Saint_Helena,_Ascension_and_Tristan_da_Cunha"
        cia=country_line[call].split(',')[2]
 
## For debugging - will show up in terminal window unless deleted or commented out.
    print("count = ",country_line[271])
    print(wiki, cia, call)
 
## Call rewrite function
    rewrite(country_line)
 
    return wiki,cia
 
## Function to rewrite the .txt file with an updated count and the called country marked as used.
def rewrite(country_line):
    country_file=open("country_cia_code.txt", "w")
 
    for i in range(len(country_line)):
        print(country_line[i],file=country_file)
    country_file.close()
 
 
## Function to determine if entry is a country or not
def is_country(cia):
## Flag set    
    is_country=True
 
## Variable set so there will be something to pass into main even if there is no affiliation.
    affiliation=""
 
## Lines from the CIA WFB page HTML are read into a variable, then iterated over to search for the string
## "<div class="affiliation">" which only appears if the entry is not a country. The line is stripped of
## everything but the affiliation text, associated with a variable and passed into main.    
    try:
        for line in urllib.request.urlopen("https://www.cia.gov/library/publications/the-world-factbook/geos/"+cia+".html"):
            lineStr=str(line, encoding='utf8')
            if '<div class="affiliation">' in lineStr:
                affiliation_line=lineStr.strip()
                affiliation=affiliation_line.replace('<div class="affiliation"><em>','').replace('</em></div>','')
                is_country=False
    except:
        is_country=False
        affiliation=""
 
    return is_country, affiliation
 
## Function for image retrieval 
def image(cia,wiki):
 
## Both images are from the CIA WFB website. The image showing just the country easily retrievable since
## the only variable is the two-letter code, but the world locator full size image uses both the two-letter 
## code and a three-letter region code. The two-letter codes are associated with the country name in a table
## available on the WFB website, but since I couldn't find a list of associated three-letter region codes, this
## basically reads the locator template page HTML (which only requires a two-letter code) into a variable,
## and looks for the string "/locator", which precedes the three-letter code in the HTML.  The three-letter
## slice is assigned to a variable.
    try:
        for line in urllib.request.urlopen("https://www.cia.gov/library/publications/the-world-factbook/maps/"+cia+"_largelocator_template.html"):
            lineStr=str(line, encoding='utf8')
            if "/locator" in lineStr:
                world_area=lineStr.strip()
        loc_index=world_area.find("/locator")
        world_code=world_area[loc_index+9:loc_index+12]
 
        world_image_url="https://www.cia.gov/library/publications/the-world-factbook/graphics/locator/"+world_code+"/"+cia+"_large_locator.gif"
## However, if there is no world locator map, the page will result in a 404 error. In this instance a generic
## world map and code are assigned.
    except urllib.error.HTTPError:
        world_image_url="https://www.cia.gov/library/publications/the-world-factbook/graphics/maps/large/xx-map.gif"
        world_code="xx"
 
 
 
## uncomment the following lines if you want the script to save a copy of the country and world .gif to the folder
## it's running from.
    ##urllib.request.urlretrieve(image_url,wiki+"-map.gif")
    ##urllib.request.urlretrieve(world_image_url,wiki+"-worldmap.gif")
 
## Tkinter GUI can't directly display an image from the internet, so the raw image data is read into a variable,
## encoded into base 64, and then passed into the main function for use by Tkinter.
 
## Flag set - will stay true if country map page doesn't result in a 404 error.
    image_exist=True
 
## Country map - if there is no country map, then it will default to a generic world map and pass
## a flag into main so Tkinter will add a label.
    try:
        image_raw1=urllib.request.urlopen("https://www.cia.gov/library/publications/the-world-factbook/graphics/maps/large/"+cia+"-map.gif")
        raw_data1=image_raw1.read()
        image_raw1.close()
        b64_data1=base64.encodestring(raw_data1)
    except urllib.error.HTTPError:
        image_raw1=urllib.request.urlopen("https://www.cia.gov/library/publications/the-world-factbook/graphics/maps/large/xx-map.gif")
        raw_data1=image_raw1.read()
        image_raw1.close()
        b64_data1=base64.encodestring(raw_data1)
        image_exist=False
## World locator map. Exception put in case of bad/unusable region code.
    try:
        image_raw2=urllib.request.urlopen(world_image_url)
        raw_data2=image_raw2.read()
        image_raw2.close()
        b64_data2=base64.encodestring(raw_data2)
    except urllib.error.HTTPError:
        image_raw2=urllib.request.urlopen("https://www.cia.gov/library/publications/the-world-factbook/graphics/maps/large/xx-map.gif")
        raw_data2=image_raw2.read()
        image_raw2.close()
        b64_data2=base64.encodestring(raw_data2)
        world_code="xx"
 
    return b64_data1, b64_data2, world_code, image_exist
 
def main():
## Get variables for web pages
    wiki,cia=roll_country()
 
## Get variables for Tkinter
    b64_data1, b64_data2, world_code, image_exist=image(cia,wiki)
 
## Get is_country flag
    country, affiliation=is_country(cia)
 
## If a browser window is already open, these will open in new tabs 
    webbrowser.open("http://en.wikipedia.org/wiki/"+wiki, new=0)
 
## If a browser window isn't open, two windows may open instead of two tabs in one window.
## Therefore, the second webpage is delayed by 2 seconds to give the first one time to open a window.    
    time.sleep(2)
    webbrowser.open("https://www.cia.gov/library/publications/the-world-factbook/geos/"+cia+".html", new=2)
 
## Start of Tkinter root window.
    wiki=wiki.replace("_"," ")
    root = tkinter.Tk()
    root.minsize(300,300)
    root.title("Country of the day")
    root["padx"]=10
 
## First label - grid is three rows by two columns. This label spans two columns so it centers over both images.
## The label will change depending on the "country" flag. If False, the label will include the affiliation info from the CIA WFB page.
    if country==True:
        w=tkinter.Label(root,text="Today's Place of Interest is the Country of "+wiki,font=("Helvetica", 16), pady=5).grid(row=0, column=0,columnspan=2)
    if country==False:
        w=tkinter.Label(root,text="Today's Place of Interest: "+wiki+" "+affiliation,font=("Helvetica", 16), pady=5).grid(row=0, column=0,columnspan=2)
 
 
## Both images are in the same row, adjacent columns.
    world_map=tkinter.PhotoImage(data=b64_data2)
    b=tkinter.Label(root,image=world_map).grid(row=1,column=0,padx=6,pady=6)
## If the world locator template page has a 404 error, then this will overlay a label on the world map saying
## it wasn't available.    
    if world_code=="xx":
        d=tkinter.Label(root,text="There is no World Locator map for this entry with the CIA").grid(row=1, column=0) 
 
    cia_map=tkinter.PhotoImage(data=b64_data1)
    c=tkinter.Label(root,image=cia_map).grid(row=1,column=1,padx=6,pady=6)
## If the coutnry map page has a 404 error, then this will overlay a label on the world map saying
## it wasn't available.  
    if image_exist==False:
        f=tkinter.Label(root,text="There is no map for this entry with the CIA").grid(row=1, column=1)
 
## This label spans two columns so it centers under both images.
    a=tkinter.Label(root,text="Hotlinks to maps:").grid(row=2, column=0,columnspan=2)
 
## Places addresses in a text box so they can be copied and pasted to hotlink in my blog.
    e=tkinter.Text(root)
    e.config(width=105,height=2)
    e.insert("insert","https://www.cia.gov/library/publications/the-world-factbook/graphics/maps/large/"+cia+"-map.gif\nhttps://www.cia.gov/library/publications/the-world-factbook/graphics/locator/"+world_code+"/"+cia+"_large_locator.gif")
    e.grid(row=3, column=0, columnspan=2)
 
    root.mainloop()
 
## If there is a long country map(e.g. Chile) it can push the pop-upwindow off the bottom of the screen on lower
## resolution monitors. This can show an incomplete country and block off the link text box.
## I'm not sure if there's a way to add a scrollbar to a grid layout in Tkinter. I think a frame or canvas
## is needed first.  This is my first time using Tkinter so I might update this once I learn more.   
 
main()

 

Leave a Reply