Building a WiFi spots Interactive Map of Networks with WiGLE and Python
Recently, one of my LinkedIn connections shared an article on generating this Map of Networks with WiGLE and R, and Python enthusiasts inquisitively inquired if this can be achieved with Python as well. So let us try to get this done for Pune as well as Bangalore location!
Let us now move ahead and replicate his work in Python. We are going to identify the open WiFi Networks around us and map them elegantly in order to visualize. Now there is a Catch-22 here but let me not jump the gun, and let us wait for it till the end. We wait for an entire year to watch next season of GoT, so I guess another 3–4 minutes is a fair ask. To start with, we need to ensure we’ve following packages in our Toolkit and if you don’t, then just create a virtual environment & follow instructions shown below:
- requests : To send HTTP requests because sometimes WiGLE authentication process gets really wiggly and throws bunch of errors.
- WiGLE API : To search & collect data from their database.
- Pandas : To analyze and manipulate data.
- Geoplotlib : To visualize our WiFi Network results (you can use leaflet as well, but then list of alternatives are endless).
Again a big shout-out to all those AWESOME contributors who do so much for our open-source community by developing & maintaining such brilliant packages. THANK YOU GUYS!
By now, I believe you already have requests, pandas and geoplotlib installed. So let us start by importing these libraries:
# IMPORTING REQUIRED LIBRARIES:
import requests
from requests.auth import HTTPBasicAuth
import pandas as pd
from pandas.io.json import json_normalize
import matplotlib.pyplot as plt
import geoplotlib as gp
Meanwhile, let us also head to our WiGLE account, and make a note of our authentication Username and Password. Ensure to sign in with your credentials, click on ‘Tools’ in the top navigation bar, and then select ‘Account’, that shall look like:
Next page shall have a general End User License Agreement, where you need to click on ‘Show my token’. From all the information displayed on the very next page, we need to safely copy
- API Name that we will use as our Username, and
- API Token that we shall use as our Password.
By the way, while importing geoplotlib, if you receive any errors regarding pyglet, then install that library as well, using pip install pyglet
. Also our target location in this illustration is Pune (Maharashtra, India) but if you want it to reflect your city, just change the name in below code (and associated details) for it to work. With that, we’re good to start writing our actual code:
# SETTING WiGLE USERNAME & PASSWORD FOR API CALL:
wigle_username = 'enter_your_api_name_here'
wigle_password = 'enter_your_api_token_here'# SETTING PARAMETERS:
payload = {'first': '0', 'freenet': 'false', 'paynet': 'false', 'addresscode': 'Pune', 'api_key': (wigle_username + wigle_password).encode()}# FETCHING JSON RESPONSE FROM WiGLE:
pune_details = requests.get(url='https://api.wigle.net/api/v2/network/geocode', params=payload, auth=HTTPBasicAuth(wigle_username, wigle_password)).json()# PRINTING TO CHECK RESPONSE:
print(pune_details)
So our hit was successful and we got a response from which we shall make a note of Pune Bounding Box, shown as a combination of latitudes and longitudes within boundingbox
scope. Response shall look like a typical JSON output:
Now we shall use this boundingbox
values within our payload
, as WiGLE Search acceptable parameters to fetch 100 similar results for this illustration. These parameters are latrange1, latrange2, longrange1 & longrange2
. Note that by default, COMMAPI queries are bounded at 100 pages but if you want more data out of it, alter first
parameter within your payload
:
# SETTING PARAMETERS:
payload = {'latrange1':'18.3603062', 'latrange2':'18.6803062', 'longrange1':'73.6943185', 'longrange2':'74.0143185', 'api_key': (wigle_username + wigle_password).encode()}# FETCHING JSON RESPONSE FROM WiGLE:
pune_results = requests.get(url='https://api.wigle.net/api/v2/network/search', params=payload, auth=HTTPBasicAuth(wigle_username, wigle_password)).json()# PRINTING TO CHECK PUNE RESULTS:
print(pune_results)
As desired, entire list of 100 nearby WiFi SSIDs with many other relevant details are right in front of us, but details that catch our attention here are the coordinates in trilat
and trilong
. I sincerely apologize to those who thought we gonna get passwords to the secure/encrypted networks, but apart from that, everything else right there.
Let us quickly get these responses converted into a human readable format, so let’s take help from mighty Pandas for a JSON conversion to DataFrame. Also let us perform little column renaming & then preview outcome:
# EXTRACTING 'RESULTS' AS A PANDAS DATAFRAME TO WORK WITH:
df = json_normalize(pune_results['results'])# RENAMING COLUMNS FOR GEOPLOTLIB:
df = df.rename(columns={'trilat': 'lat', 'trilong': 'lon'})
cols = list(df.columns)# PREVIEWING AVAILABLE INFORMATION:
print(f"Result obtained has {df.shape[0]} rows and {df.shape[1]} columns in it. \n\nThe list of columns include {cols}")print(df)
geoplotlib expects input DataFrame to contain location co-ordinates as lat
and lon
columns, and rest of data gets automatically shaped up. We had it as trilat
and trilong
, hence needed to rename those columns.
As far as data is concerned, we already have what we needed. So without further adieu, let us plot it as well:
# GENERATING WIFI SPOTS MAP OF NETWORKS:
gp.dot(df)
gp.show()
And yes! That is all the code we need, to create a (default) static visualization on top of OpenStreetMap theme (Tiles), that shall appear as:
There are multiple other Themes to be chosen from! With all of that done, now let us discuss our Catch-22, which actually you would’ve guessed by now. The plot we have here is static, but what if we want it to be interactive? Well geoplotlib also offers custom options like interactivity, colors, tiles, etc. To play further with this map, you can import rest of the components as:
# FOR INTERACTIVITY & OTHER OPTIONS WITH GEOPLOTLIB:
from geoplotlib.layers import BaseLayer
from geoplotlib.core import BatchPainter
from geoplotlib.colors import colorbrewer
from geoplotlib.utils import epoch_to_str, BoundingBox
Then create a CustomClass
based on their BaseLayer
to make it even more appealing. In fact, they have a very nice and intuitive User Guide as well that you may like to refer to. If someone extends further to make this visualization interactive, please do share a link in the Comments section below for other learners to benefit.
Meanwhile if that wait doesn’t sound justified, then let us look at a piece of work by superhero Abdul, and he has all the code ready in his Notebook to make plot interactive as well. And the fun doesn’t end there because we also get to learn another fabulous geoplotting library, Folium. Let us follow along to comprehend to what he tries to achieve. So let us begin by importing the library and then make an addition to our current Pandas DataFrame df
. In case you don’t have Folium already installed in your environment, all you need to do is pip install folium
.
# IMPORT LIBRARY:
import folium# CREATING AN EMPTY NEW COLUMN FOR FOLIUM TO LATER USE:
df['color'] = ''# PICKING 'UNKNOWN' & 'NONE' VALUES FROM 'ENCRYPTION' COLUMN TO FEED 'COLOR' COLUMN:
df['color'] = df['encryption'].map({'unknown':'yellow', 'none':'red', 'wep':'blue', 'wpa':'blue', 'wpa2':'blue'})# VERIFYING 'ENCRYPTION' & 'COLOR' COLUMNS IN OUR DATAFRAME:
df[['encryption', 'color']].tail(10)
With our Geoplotlib output, we noticed solid red markers for all the 100 SSIDs on our map. Although not mandatory, we plan to add a column named color
that shall cluster all SSIDs based on the network encryption type. There are 5 of them in our encryption
column of df
DataFrame, namely wep
, wpa
, wpa2
, unknown
and none
. So we assigned different colors to known & unknown encryption types of network. And that fetches us:
With color
column in place, we shall now set a Base map for Pune location, and for a change, let us modify the default theme/tile of OpenStreetMap to cartodbdark_matter. Once that is done, we shall plot all 100 records clustered in differently colored circle markers on our Basemap. Finally, we shall also save a local copy of it. Note that this copy will get saved in the current working directory of your IDE. If you’re working on Jupyter Notebook, run pwd
to know your directory.
# SETTING BASE MAP FOR PUNE WITH A THEME:
pune_map = folium.Map([18.3603062, 74.0143185], zoom_start=6, tiles='cartodbdark_matter')# USING 'COLORS' COLUMN TO SEGREGATE 100 SSIDs
# WITH CIRCLE MARKS, AND ALSO ADD A POPUP MESSAGE:
df.apply(lambda row: folium.CircleMarker(location= [row["lat"], row["lon"]], radius= 5, color= row['color'], popup = "SSID: %s /n Encrpytion %s" %(row["ssid"],row["encryption"])).add_to(pune_map), axis=1)# CHECKING OUTCOME & SAVING A COPY OF MAP:
pune_map.save('Pune Map')
pune_map
Still there are many other options that can be applied to enhance aesthetics, like various markers, filling in colors within those markers, etc. For example, to fill in color to our markers on plot, we could have just added to our .CircleMaker()
another parameter as fill=True
(by default, it is set to False) and fill_color=’your color_choice’
. And all of these efforts shall lead to an Output pretty much like:
https://vimeo.com/user95440991/review/319149844/38ddba0335
Appreciate your time and patience guys! Enjoy Learning. :)