%let name=wings; filename odsout '.'; /* This example is based loosely on a map found on the following page, Chapter 3, section 3.7, p. 75 http://www.nasa.gov/columbia/caib/PDFS/VOL1/PART01.PDF A screen-capture of this map is in 'debris_wing.gif' */ /* Use unprojected maps.county, rather than maps.uscounty, so you can annotate pies & towers, and re-project it. */ data my_map; set maps.county (where=(fipstate(state) in ('TX' 'LA') )); run; proc sql; /* get 1 obsn for each state/county pair */ create table my_mapdata as select unique state, county, avg(x) as avg_x, avg(y) as avg_y from my_map group by state, county; /* get the county names */ create table my_mapdata as select my_mapdata.*, cntyname.countynm from my_mapdata left join maps.cntyname on my_mapdata.state=cntyname.state and my_mapdata.county=cntyname.county; quit; run; /* Add html title= chart tips with the county name, and the debris count */ data my_mapdata; set my_mapdata; length htmlvar $1024; /* x & y are in radians, convert them to degrees */ avg_long=-1*(avg_x*45)/atan(1); avg_lat=(avg_y*45)/atan(1); /* Now, separate the decimal degrees into degrees & minutes */ longdeg=int(avg_long); longmin=int((avg_long-int(avg_long))*60); latdeg=int(avg_lat); latmin=int((avg_lat-int(avg_lat))*60); htmlvar= 'title='||quote( trim(left(countynm))||' County, '||trim(left(fipstate(state)))|| /* '0D'x|| 'County/State FIPS: '||trim(left(county))||'/'||trim(left(state))|| 'Debris Count:'||trim(left(debriscount))|| */ ' ')|| ' '|| 'href="http://www.mapquest.ca/maps/map.adp?latlongtype=degrees&latdeg='||trim(left(latdeg))||'&latmin='||trim(left(latmin))|| '&latsec=0&longdeg='||trim(left(longdeg))||'&longmin='||trim(left(abs(longmin)))||'&longsec=0"'; run; data debris; infile datalines dlm=','; input idnum latdegrees latminutes longdegrees longminutes category; latitude=latdegrees+(latminutes/60); longitude=longdegrees+(longminutes/60); put latdegrees ',' latminutes ',' longdegrees ',' longminutes; datalines; 10 , 32 , 30.458 , 96 , 59.000 , 1 11 , 32 , 21.458 , 96 , 29.000 , 1 12 , 32 , 20.458 , 96 , 28.500 , 1 13 , 32 , 16.458 , 96 , 19.000 , 1 14 , 32 , 13.958 , 96 , 13.200 , 1 15 , 32 , 10.058 , 96 , 9.100 , 1 16 , 32 , 6.458 , 96 , 4.000 , 1 17 , 32 , 1.458 , 95 , 52.000 , 1 18 , 31 , 58.458 , 95 , 50.000 , 1 19 , 31 , 56.458 , 95 , 45.000 , 1 20 , 31 , 54.458 , 95 , 42.000 , 2 21 , 31 , 52.458 , 95 , 35.000 , 1 22 , 31 , 50.458 , 95 , 33.000 , 2 23 , 31 , 47.458 , 95 , 30.000 , 2 24 , 31 , 46.958 , 95 , 24.000 , 2 25 , 31 , 44.958 , 95 , 21.000 , 2 26 , 31 , 44.958 , 95 , 17.000 , 2 27 , 31 , 42.958 , 95 , 13.000 , 2 28 , 31 , 41.958 , 95 , 10.000 , 3 29 , 31 , 40.958 , 95 , 5.000 , 2 30 , 31 , 38.958 , 94 , 58.000 , 2 31 , 31 , 34.958 , 94 , 53.000 , 2 32 , 31 , 33.958 , 94 , 50.000 , 3 33 , 31 , 34.958 , 94 , 47.000 , 2 34 , 31 , 36.958 , 94 , 57.000 , 3 40 , 31 , 33.531 , 94 , 35.731 , 3 41 , 31 , 33.661 , 94 , 35.454 , 3 42 , 31 , 34.050 , 94 , 35.126 , 3 43 , 31 , 36.958 , 94 , 37.102 , 3 44 , 31 , 36.936 , 94 , 36.329 , 3 47 , 31 , 32.758 , 94 , 33.204 , 3 48 , 31 , 33.481 , 94 , 31.148 , 3 49 , 31 , 33.858 , 94 , 34.189 , 3 50 , 31 , 31.622 , 94 , 37.997 , 3 51 , 31 , 33.818 , 94 , 37.942 , 3 52 , 31 , 32.658 , 94 , 40.115 , 3 53 , 31 , 34.666 , 94 , 37.746 , 3 55 , 31 , 31.041 , 94 , 34.729 , 3 67 , 31 , 30.130 , 94 , 33.967 , 3 83 , 31 , 33.219 , 94 , 28.844 , 3 91 , 31 , 30.859 , 94 , 23.845 , 3 92 , 31 , 32.859 , 94 , 20.845 , 3 93 , 31 , 36.259 , 94 , 40.045 , 3 ; run; /* data debris; set debris; output; latminutes=latminutes+rannor(123); longminutes=longminutes+rannor(123); latitude=latdegrees+(latminutes/60); longitude=longdegrees+(longminutes/60); output; latminutes=latminutes+.5*rannor(12); longminutes=longminutes+.5*rannor(12); latitude=latdegrees+(latminutes/60); longitude=longdegrees+(longminutes/60); output; latminutes=latminutes+.5*rannor(23); longminutes=longminutes+.5*rannor(23); latitude=latdegrees+(latminutes/60); longitude=longdegrees+(longminutes/60); output; run; */ options nocenter; proc print data=debris; format latdegrees comma3.0; format longdegrees comma3.0; format latminutes comma6.3; format longminutes comma6.3; var latdegrees latminutes longdegrees longminutes; run; data debris; set debris; x=atan(1)/45 * longitude; y=atan(1)/45 * latitude; run; /* Create an annotate dataset, containing label for each piece of debris */ data circle_anno; length function style color cbox $ 8 position $ 1 text $ 30 html $1024; retain xsys ysys '2' hsys '3' when 'a'; set debris; /* convert the decimal degrees into degrees & minutes */ longdeg=int(longitude); longmin=(longitude-int(longitude))*60; latdeg=int(latitude); latmin=(latitude-int(latitude))*60; anno_flag=1; /* Create a link & chart-tip for the dot - since I have lots of info I want to show, I hardcode some '0d'x characters (carriage returns) to break the info onto several lines ... these multi-line charttips can only be viewed with Internet Explorer (netscape can only view 1 line). For this proof-of-concept, I'm just hard-coding the same image as the drilldown for all the dots, but if I actually had pictures of all the pieces of debris, I could code this section to drilldown to the actual picture for each one. */ html= 'title='||quote( 'ID Number: '||trim(left(idnum))||'0d'x|| 'Longitude: '||trim(left(put(longitude,comma5.2)))||'0d'x|| 'Latitude: '||trim(left(put(latitude,comma5.2)))|| ' ')|| ' '|| 'href="wing_layout.gif"'; color='black'; if category eq 1 then cbox='red'; else if category eq 2 then cbox='cxfeb24c'; else if category eq 3 then do; cbox='cx253494'; color='white'; end; function='label'; text=trim(left(idnum)); /* function='pie'; style='pempty'; */ position='5'; rotate=360; size=3; output; run; data all_anno; set circle_anno /* grid_anno */; run; /* project the map */ data combined; set my_map all_anno; run; proc gproject data=combined out=combined dupok /* do map-clipping, as described in Mike Zdeb's book "Maps Made Simple", p. 59 */ /* in this particular example, I give enough leeway that I'm not actually "clipping" into the map, but you could tweak these numbers to clip/zoom-in on a certain section of the map. */ longmin=93.85 longmax=97.25 latmin=31 latmax=33 ; id state county; run; data my_map all_anno; set combined; if anno_flag=1 then output all_anno; else output my_map; run; GOPTIONS DEVICE=gif; ODS LISTING CLOSE; ODS HTML path=odsout body="&name..htm" (title="Shuttle Debris Field") style=minimal gtitle gfootnote ; goptions gunit=pct htitle=4.5 htext=3.1 ftitle="arial/bold" ftext="arial" ctitle='black' ctext='black' ; title 'Distribution of Space Shuttle Wing Debris'; title2 "Click on counties to see 'mapquest' map of that area"; title3 'Click on markers to see images of debris'; title4 a=90 f=marker c=red 'U' f="arial" c=black '=Left Wing RCC Panels 8-22'; title5 a=90 f=marker c=cxfeb24c 'U' f="arial" c=black '=Left Wing RCC Panels 1-7'; title6 a=90 f=marker c=cx253494 'U' f="arial" c=black '=Right Wing RCC Panels 1-22'; footnote c=gray "SAS Proof-of-Concept (using contrived data)"; footnote2 c=gray "Based on Chapter 3.7, p.75, Figure 3.7-5 in " link="debris_wing.gif" "[click here]"; /* link="http://www.nasa.gov/columbia/caib/PDFS/VOL1/PART01.PDF" "[click here]"; */ /* I picked certain colors to match the real maps I drilldown to */ pattern1 c=graycc v=s r=5; goptions border cback=white; proc gmap data=my_mapdata map=my_map anno=all_anno; id state county; choro state / coutline=gray55 nolegend discrete html=htmlvar des="" name="&name"; run; /* title " "; footnote " "; proc print data=debris noobs label; var latitude longitude; run; */ quit; ODS HTML CLOSE; ODS LISTING;