Plotting markers on a map at zip code locations, using GMap or SGplot


Users frequently ask how to plot their data as markers on a map. There are several ways to do this using SAS software. If you're a Visual Analytics user, you can do it using a point-and-click interface. But if you're a coder, you might need a little help... In this example, I show the basics of coding it using both Proc GMap and Proc SGplot.

Zip code Coordinates

The first thing you'll need to do is determine a latitude/longitude coordinate for the locations where you want markers. One way to do this is to use Proc Geocode. Depending on how much detail you have about the spatial location of your markers, you can use Proc Geocode to estimate the lat/long at the city, zip code, or street-level. In this simple example, I'm using zip codes. Here's the basic SAS code:

data locations;
input Zip;
proc geocode data=locations out=locations (rename=(x=long y=lat))
method=ZIP lookup=sashelp.zipcode;

Preparing the Map

We ship several map datasets with SAS/Graph. Each line (row) of data describes 1 point along the border of an area in the map. The density of points along the borders is usually more than you need, and therefore you might want to use the density variable to exclude points above a certain density level. You might also want to exclude certain map areas. Once you have the desired subset of a map dataset, you can combine it with the zip code data, project it (using Proc Gproject), and then split the two datasets apart again. Here's the basic code:

data my_map; set mapsgfk.us_states
(where=(statecode not in ('AK' 'HI') and density<=3) drop=resolution);
data combined; set my_map locations;
proc gproject data=combined out=combined latlong eastlong degrees;
id statecode;
data my_map locations; set combined;
if anno_flag=1 then output locations;
else output my_map;

Proc GMap Version

SAS' traditional mapping software is Proc GMap (found in the SAS/Graph product). It was built for handling maps, and has many nice features included. To plot markers at the zip code locations, you first have to create an annotate dataset. For round markers, I like using the annotate pie function (alternatively, you can annotate characters from any font, or create custom markers). Here's the basic code for creating an annotate dataset:

data anno_locations; set locations;
xsys='2'; ysys='2'; hsys='3'; when='a';
function='pie'; rotate=360; size=1.0;
style='psolid'; color='yellow'; output;
length html $300;
style='pempty'; color='purple'; output;

Once you've created the annotate dataset, you simply specify it using the anno= option when you create your Proc GMap:

proc gmap data=my_map map=my_map anno=anno_locations;
id statecode;
choro segment / levels=1 nolegend

Proc SGplot Version

If you don't have the SAS/Graph product, you can also create maps with Proc SGplot (which is part of Base SAS), by using polygons, and overlay scatter plot markers at the zip code locations. You'll need to rename your markers' X & Y variables, so they're something different from the map's X & Y. And if you want the markers to have html mouse-over text, this is a good time to set that up.

data scatter_locations (rename=(x=zipX y=zipY));
set locations (keep = zip city x y);
label city_zip='00'x;
length city_zip $100;
city_zip='= = = = ='||'0d'x||trim(left(city))||'0d'x||trim(left(zip));

You'll then need to combine the map and marker datasets, and specify both a polygon and scatter statement when you run Proc SGplot:

data my_map; set my_map scatter_locations;
proc sgplot data=my_map noborder noautolegend;
polygon x=x y=y id=statecode_plus_segment / fill outline tip=none lineattrs=(color=gray99) fillattrs=(color=cxe8edd5);
scatter x=zipX y=zipY / tip=(city_zip)
markerattrs=(symbol=circlefilled size=13)
xaxis display=none;
yaxis display=none;

As you might have guessed, I glossed over a few of the finer details in the above code. Here's a link to the complete code, if you'd like to see all the details, and experiment with plotting your own data. Note that you will need a SAS/Graph license to have access to the maps, Proc Geocode, and Proc Gproject. And you'll need a fairly recent version of SAS to be able to run Proc SGplot and generate the polygon map.

Click here to see more examples about creating maps with Proc SGplot!

Dec 2017 Update: An official Proc SGmap is now available!



About Author

Robert Allison

The Graph Guy!

Robert has worked at SAS for over 25 years, and is perhaps the foremost expert in creating custom graphs using SAS/GRAPH. His educational background is in Computer Science, and he holds a BS, MS, and PhD from NC State University. He is the author of several conference papers, has won a few graphic competitions, and has written a book (SAS/GRAPH: Beyond the Basics).

Related Posts


  1. Louise Hadden on

    Cool! Next up is to see if the symbolimage capability in PROC GPLOT will allow use with the POLYGON statement - then I will finally attain my goal of sparklines on SAS-generated maps!!! Too busy right now but it's on my radar - maybe at SGF!

  2. Bharat thakkar on

    Wonderful plots. What modification would be need to add frequency counts to the plot for each zip code? Thanks

    • Robert Allison
      Robert Allison on

      Consider this pseudo-code (since I haven't tested it), but it would only require a few extra commands right after the annotate code for the 'pie's. Something like this would annotate frequency above each dot marker ...

      function='label'; style=''; rotate=.; position='2'; text=trim(left(frequency)); output;

  3. Thanks for these posts. I've learned a lot. Is it possible to connect the dots to show a path through the cities?

  4. SGPLOT maps are growing on me! I've been a long-time fan of GMAP and annotate but I really like the looks of the SGPLOT poly maps. How do I eliminate the index number "1" on the resulting png? I added reset to the "graphics on" statement and it prevents the png from indexing higher and higher with each run but I always get a "1" suffix.

    For example, ods graphics on / reset imagename="My_file_name" height=6.25in width=8.33in imagefmt=staticmap imagemap=on tipmax=2500;
    Result: My_file_name1.png

    • Dan Heath

      Hey Mike,

      Do you, by any chance, have two ODS destinations open, such as ODS LISTING and ODS HTML? If so, one of them will get the filename without the index and the other will get the filename with the "1" index. Be sure to start your program with ODS _ALL_ CLOSE; to close any stray destinations. Hope this helps!

  5. Yikes! I had closed LISTING and HTML but not ALL. I'm not sure what else was "open" but "ALL" fixed it. The ODS business still befuddles me... Thanks so much.

Leave A Reply

Back to Top