Early in July, my attention was drawn by a feature at the ARRL web site by Ken Harker, WM5R, entitled Make a Map of Your Radio Club.
Ken wrote: "More than simple drawings of where club members live, clubs can use maps to help plan activities, assist new members in finding Elmers, coordinate during disasters and more. ..In our particular club, highlighting the DX Cluster locations can help members decide which clusters to try out. In a repeater club, highlighting repeater locations might be a desirable focus of a club map. An ARES organization might highlight member locations, EOCs, hospitals, Red Cross headquarters and other points of interest. Maps can do a lot that is useful for radio clubs....Maps can say so much more about your radio club membership than a simple list of club member names with counties or grid locators. Clubs can use maps to help plan their services and activities, to assist new members in finding Elmers, to coordinate during disasters and more. "
Ken's article outlines the technology he used to produce maps for the Central Texas DX & Contest Club, and this got me to thinking about how the process could be streamlined.
In producing a system map for the Metrocomm W3PS linked repeater system, I had discovered that one of the most flexible (and certainly most inexpensive!) tools for mapping for amateur radio, especially at scales appropriate to VHF/UHF communications is the APRS program XASTIR
What I
envisioned was a program that could be handed a list of amateur
callsigns, that would then contact the QRZ.COM website and record the
latitude and longitude information available there in a format
compatible with the XASTIR object.log
file. This essentially creates a virtual APRS station apparently
located at the location carried in the FCC data base for each member
of PMRC.
It really didn't take as much time as I thought it would to do. The Java programming language is especially well-suited to this task .
So before too long, I had created a list of callsigns from the PMRC 2003 directory, and was using it to interrogate the QRZ.COM site for the necessary information.
I've reproduced some of the results here. We can use this software to produce maps for many different purposes: investigating repeater coverage, planning events (both club events and public service activities). Emeregency services are a fertile field for mapping information as well.
Here's a map from XASTIR fed with data from my program. XASTIR has included a map image drawn by The U.S. Census Bureau TIGER Map Server XASTIR allows you to select what kinds of political or geographic feature you wish to have included in the map image

This is a second map, without the TIGER data, using only vector mapping data for streets withing Philadelphia county, and only country boundaries elsewhere

This is a small driver program to feed data from the PMRC roster and write the results to the standard output stream. A more sophisticated driver would read the callsigns from a file and write the output to another file for XASTIR to read, but this was just an experiment. (And I had a Blurb deadline to meet.)
Click here for the getQRZdata source code without markup
1:import java.io.IOException; 2:import java.net.MalformedURLException; 3:/** 4: * @author Maggie Leber K3XS 5: */ 6:public class getQRZdata { 7:static String[] roster = { 8: "KB3AFH", "W2AJV", "KB3ANO", "W3AOK", "WA3AZS", 9: "W3BBB", "KA3BET", "K2BN", "W3CH", "K3CJ", 10: "KB3CPC", "WA3CRM", "N2CYA", "WE3D", "N3DLV", 11: "N3DO", "W3DO", "KB3DQH", "W3DSG", "K3DTC", 12: "KA3EIP", "KB2ERL", "K3EUI", "N3FD", "KA2FFP", 13: "KC2FIO", "N3FTC", "N3FTY", "N3FTZ", "W3FWI", 14: "K3GBA", "N3GLH", "N3GLU", "W3GQD", "KB3HAQ", 15: "K3HES", "KC2HGC", "K3HIJ", "KB3HIU", "KB3HNB", 16: "KB3HPS", "K3HWE", "KC2HX", "WU3I", "W3IIN", 17: "W3INW", "KB3IV", "KB3JCR", "N3JHR", "W3JRP", 18: "NS3K", "WB3KAC", "W3KG", "WA3KIO", "WA3KLR", 19: "WB3KOH", "WB3KOJ", "WB3KUR", "W3LEN", "K2LK", 20: "W3LKI", "KB3LRA", "N3MGS", "W7MM", "WP4MSX", 21: "N2MT", "N3MT", "W3NE", "WA3NSP", "N3OLK", 22: "N3OOL", "N3OOV", "N3OWM", "NG3P", "WX3PHI", 23: "ND3Q", "KE3QB", "K3QLY", "N3QMH", "N3RBJ", 24: "N3RCQ", "W3RED", "W3RM", "K3SPS", "W3STW", 25: "N3UBY", "W3UI", "K3UJI", "W3UY", "N3VJE", 26: "N3VLC", "KA3VLJ", "K3VU", "W3VVS", "AA3WA", 27: "N3WIQ", "W3WXC", "N3XKE", "K3XS", "N3YGY", 28: "WA3YJR", "W3YLT", "N3YNH", "K3YPH", "WA1ZGE", 29: "N3ZO", "N3QIE" }; 30: 31: public static void main(String[] args) 32: throws IOException, MalformedURLException { 33: QRZdata q; 34: for (int i=0;i<roster.length;i++) 35: { 36: q = new QRZdata(roster[i]); 37: System.out.println(q.getAPRSObject()); 38: } 39: } 40:}
This is the guts of the program. The logic I use to locate the lat/lon info is an ugly hack, which will break when QRZ changes the format of its web pages. One basic rule of "web scraping" like this is: "The page you are using as a data source will change so as to break your program". (This is one reason why the Extensible Markup Language (XML) was invented.)
Click here for the QRZdata source code without markup
1:import java.io.BufferedReader; 2:import java.io.IOException; 3:import java.io.InputStream; 4:import java.io.InputStreamReader; 5:import java.net.MalformedURLException; 6:import java.net.URL; 7:import java.text.DecimalFormat; 8:/** 9: * @author Maggie Leber K3XS 10: */ 11:public class QRZdata { 12: URL u; 13: String content; 14: String call; 15: String[] parse; 16: String[] fields; 17: static DecimalFormat df = new DecimalFormat("00.00"); 18: static DecimalFormat df2 = new DecimalFormat("000"); 19: float lat, lon; 20: 21: /** 22: * Connect to the QRZ website and request the detail page 23: * for this callsign 24: */ 25: public QRZdata(String call) throws IOException, MalformedURLException 26: { 27: this.call = call; 28: u = new URL("http://www.qrz.com/detail/" + call); 29: BufferedReader r = 30: new BufferedReader( 31: new InputStreamReader((InputStream) u.getContent())); 32: content = ""; 33: while (content != null) { 34: if (content.indexOf("Coordinates:") != -1) // is this the location? 35: { 36: parse = content.split("</?font.*?>"); 37: // break out the fields delimited by <font>...</font> 38: 39: for (int i = 0; i < parse.length; i++) 40: { 41: if (i == 3) // this is the line we want 42: { 43: fields = parse[i].split("( )+"); 44: lat = Float.parseFloat(fields[0]); 45: lon = 0 - Float.parseFloat(fields[1]); 46: } 47: }; 48: } 49: content = r.readLine(); 50: } 51: 52: } 53: /** 54: * return this callsign as an XASTIR APRS object 55: * 56: */ 57: public String getAPRSObject() { 58: return ( 59: ";" 60: + call 61: + " ".substring(0, 9 - call.length()) 62: + "*272000z" 63: + // use a timestamp close to your XASTIR runtime 64: (int) Math.floor(lat) 65: + df.format(((lat - Math.floor(lat)) * 60)) 66: + "N/" 67: + df2.format((int) Math.floor(lon)) 68: + df.format(((lon - Math.floor(lon)) * 60)) 69: + "Wy"); 70: }; 71: 72:}