Hi Folks:
I'm conducting spatial analysis using mapsgfk.south_korea data at city-level. The data has 119,675 rows to contain lat/long drawing the borders of each city. And my goal is to calculate the centroids of cities. The SAS map data has a variable named ID for each 250 unique cities which would lead to 250 centroids. The problem seem to have been resolved by Rick Wicklin in the blog below. My SAS version has proc iml. But I have a learning curve to actually use the code applying to the mapsgfk.south_korea data.
https://blogs.sas.com/content/iml/2016/01/13/compute-centroid-polygon-sas.html
/* If the polygon P has two columns, return the centroid. If it has three
columns, assume that the third column is an ID variable that identifies
distinct polygons. Return the centroids of the multiple polygons. */
start PolyCentroid(P);
if ncol(P)=2 then return( _PolyCentroid(P) );
ID = P[,3];
u = uniqueby(ID); /* starting index for each group */
result = j(nrow(u), 2); /* allocate vector to hold results */
u = u // (nrow(ID)+1); /* append (N+1) to end of indices */
do i = 1 to nrow(u)-1; /* for each group... */
idx = u[i]:(u[i+1]-1); /* get rows in group */
result[i,] = _PolyCentroid( P[idx, 1:2] );
end;
return( result );
finish;
/* test it on an example */
rect = {0 0, 2 0, 2 1, 0 1};
ID = j(nrow(L), 1, 1) // j(nrow(rect), 1, 2);
P = (L // rect) || ID;
centroids = PolyCentroid(P);
print centroids;
Could you please show me in the code, as your time allows, applying the suggested proc iml code using the south korean map data?
I don't have to use proc iml if you have alternative suggestions that work. I don't really care what methods.
The desired output is the data with 250 centroids with both ID (city) and ID1(province) variables.
Thank you for your support. I greatly appreciate it.
Hope this would help anybody trying to create centroids using %ANNOMAC and %CENTROID macros. Below is the final correct approach. It would not have been difficult for anybody who has better understanding of how to define macros than me.
data south_korea; set mapsgfk.south_korea; run;
%MACRO ANNOMAC(P1);
/*********************************************************************/
/* */
/* MACRO: ANNOMAC */
/* */
/* USAGE: %ANNOMAC(NOMSG) */
/* */
/* */
/* If ANNOMAC is called without the NOMSG parameter, */
/* a message is displayed on the log that the ANNOTATE */
/* macros are available, and that help is available by */
/* calling the %HELPANO macro. If the NOMSG parameter is */
/* specified, no messages are displayed. */
/* */
/* */
/* DESCRIPTION: */
/* This macro compiles and makes available all of the */
/* ANNOTATE macros documented in the SAS/GRAPH Users Guide. */
/* */
/* NOTES: */
/* Use of %ANNOMAC causes all of the ANNOTATE macros to be */
/* compiled. If this is undesirable, you should select the */
/* macros you want to use and place them in a separate member */
/* in your AUTOCALL library. */
/* */
/*********************************************************************/
%IF %UPCASE(&P1)=HELP %THEN %DO;
%PUT %NRSTR( USAGE: %ANNOMAC(NOMSG););
%PUT %NRSTR( );
%PUT %NRSTR(This macro causes all of the ANNOTATE macros documented);
%PUT %NRSTR(in the SAS/GRAPH USERS GUIDE, Version 6 Edition,);
%PUT %NRSTR(to be made available. Each of the macros can then be);
%PUT %NRSTR(used by calling them as documented in the USERS GUIDE.);
%PUT %NRSTR( );
%PUT %NRSTR(If %ANNOMAC is called without any parameters,);
%PUT %NRSTR(a message is displayed on the log that the ANNOTATE);
%PUT %NRSTR(macros are available, and that help is available by);
%PUT %NRSTR(calling the %HELPANO macro. If the NOMSG parameter is);
%PUT %NRSTR(specified, no messages are displayed.);
%PUT %NRSTR( );
%END;
%if &p1= %then %do;
%PUT %NRSTR( );
%PUT %NRSTR(*** ANNOTATE macros are now available ***) ;
%PUT %NRSTR( );
%PUT %NRSTR( For further information on ANNOTATE macros, enter,) ;
%PUT %NRSTR( %HELPANO(macroname), (for specific macros));
%PUT %NRSTR( %HELPANO(ALL), (for information on all macros));
%PUT %NRSTR( or %HELPANO (for a list of macro names));
%PUT %NRSTR( );
%end;
%MEND ANNOMAC;
%macro centroid( in, out, ids, segonly= );
%local lastid pos hasseg tempds;
%let lastid=&ids;
%let pos=%index(&ids,%str( ));
%do %while(&pos > 0);
%let lastid=%substr(&lastid, &pos, %length(&lastid)-&pos+1);
%let pos=%index(&lastid,%str( ));
%end;
data _null_;
segment=.;
set &in(obs=1);
if segment ne . then call symput("hasseg", "true");
run;
data;
set ∈
%if %length(&hasseg) > 0 and %length(&segonly) > 0 %then %do;
where segment = &segonly;
%end;
run;
%let tempds=&SYSLAST;
proc summary data=&tempds;
by &ids;
var x y;
output out=&out min=_xmin _ymin max=_xmax _ymax n=_npoints;
run;
data &out;
set &out;
_xbar = (_xmin+_xmax)*0.5;
_ybar = (_ymin+_ymax)*0.5;
keep &ids _xbar _ybar _npoints;
run;
data &tempds;
retain _newring 1 _found _seglast _xfirst _yfirst _xlast _ylast _yc _xc;
merge &out(in=_want) &tempds;
by &ids;
if _newring then do;
if _want then do;
_xc = _xbar;
_yc = _ybar;
end;
_xfirst = x;
_yfirst = y;
_xlast = .;
_ylast = .;
_seglast= .;
if first.&lastid then _found = 0;
end;
if x = . | y = . then _newring = 1;
else _newring = 0;
_xlast = lag(x);
_ylast = lag(y);
%if %length(&hasseg) > 0 %then %do;
_seglast = lag(segment);
%end;
if ^first.&lastid & _xlast ne . & _ylast ne . then do;
%if %length(&hasseg) > 0 %then %do;
if _seglast = segment then do;
if _newring then do;
x = _xfirst;
y = _yfirst;
end;
link calc;
end;
else if _seglast ne . then do;
_xkeep = x;
_ykeep = y;
x = _xfirst;
y = _yfirst;
segment = _seglast;
link calc;
_xfirst = _xkeep;
_yfirst = _ykeep;
end;
%end;
%else %do;
link calc;
%end;
end;
if last.&lastid then do;
_xlast = x;
_ylast = y;
x = _xfirst;
y = _yfirst;
_newring = 1;
link calc;
if _found = 0 then output;
end;
return;
calc:
/* Swap coordinates */
if _ylast > y then do;
_x1 = _xlast;
_xlast1 = x;
_y1 = _ylast;
_ylast1 = y;
end;
else do;
_x1 = x;
_xlast1 = _xlast;
_y1 = y;
_ylast1 = _ylast;
end;
if _yc >= _ylast1 & _yc < _y1 then do;
_xc = _xlast1 + (_yc - _ylast1)/(_y1 - _ylast1) * (_x1 - _xlast1);
_found = 1;
output;
end;
return;
keep _xc _yc &ids;
run;
proc sort data=&tempds;
by &ids _xc;
run;
data &out;
retain _xc1 . _dist1 _dist _xckeep 0;
set &tempds;
by &ids;
if _xc1 = . then
_xc1 = _xc;
else do;
_dist1 = abs(_xc-_xc1);
if _dist1 > _dist then do;
_xckeep = (_xc+_xc1)/2;
_dist = _dist1;
end;
_xc1 = .;
end;
if last.&lastid then do;
if first.&lastid then x = _xc;
else x = _xckeep;
y = _yc;
output;
_dist= 0;
_xc1 = .;
end;
keep x y &ids;
run;
%mend;
/*********************************************************************/
/* */
/* MACRO: MAPLABEL */
/* */
/* DESCRIPTION: */
/* This macro is used to create an annotate data set for use with */
/* PROC GMAP output. The resulting data set can be used with ANNO=*/
/* The user can use the default font/size information, or choose */
/* their own. */
/* */
/* USAGE: %HOTSPOTS(<map-ds>, <attribute-ds>, <output-ds>,<var>, */
/* <ids>, FONT=, COLOR=, SIZE=, HSYS=); */
/* */
/* PARAMETERS: */
/* <map-ds> Input Map data set */
/* <attr-ds> Attribute data set that contains var for label */
/* <out-ds> Resulting output data set for use in ANNO= in GMAP */
/* <var> Variable for label on <attr-ds>. Can be text or num */
/* <ids> Space-separated list of IDs that the map and attr */
/* data sets are sorted on. */
/* FONT= Font for the label. Default is SWISS. */
/* COLOR= Color of the label. Devault is BLACK. */
/* SIZE= Size of the label. Default is 1 <unit> */
/* HSYS= UNIT system for SIZE=. Default is 3 (PERCENT) */
/* SEGONLY= Only process the segment specified of each polygon */
/* set (map area). Useful to not have centroid placed */
/* in non-polygon area */
/* */
/* NOTES: */
/* */
/*********************************************************************/
%macro maplabel(mapds,attrds,outds,textvar,ids,
font=swiss,color=black,size=2,hsys=3,segonly=);
%local tempds;
data;
set &attrds;
run;
%let tempds=&SYSLAST;
proc sort data=&tempds;
by &ids;
run;
data &outds;
length position xsys ysys when position hsys $1 text $40;
retain function 'label'
xsys ysys '2'
hsys "&hsys"
when 'a'
position '5'
style "&font"
color "&color"
size &size;
merge &outds(in=want) &tempds(keep=&textvar &ids);
by &ids;
if vtype(&textvar) = 'C' then
text=&textvar;
else
text=left(input(&textvar,BEST.));
if want then output;
run;
proc sort data=&outds nodupkey;by &ids; run;
%mend;
%centroid(south_korea,centroids,ID,segonly=1);
Have you looked at the SAS supplied %centroid macro? You should have that as part of the map annotate macros. The code should be in your SASFoundation\9.4\core\sasmacro folder in the Annomac.sas file.
It wasn't hard to modify the macro code to use BY processing to get centroids of subregions, counties in states in my case.
I'm not sure if the IML version "better" fits depending on your need since I don't have IML.
I was using it for placing text and found out that for very concave polygons (think Florida shaped instead of Colorado ) the algorithm may place the center in locations actually outside the shape or in a location without room for much text. So for my purpose I fudged things to an area of the county that provided a bit more room for reading. That is possible by creating an output data set and modifying the X,Y coordinates a "bit". That may not be an issue for your purpose.
Thanks a lot. I'm trying to find the SAS macro you mentioned. Meanwhile, I found someone posted the SAS code to calculate centroids.
https://philihp.com/2013/finding-the-center-of-us-counties-in-sas.html
I don't know if this would be a viable approach because geography is not my strong suit. Could you please take a look at the code in the link above whether this makes sense to you?
@ballardw where am I making mistake in calling the macro? the code below leads to no action. no error message even. do you mind to try the macro for me on your SAS if possible?
data south_korea; set mapsgfk.south_korea; run;
data south_korea_attr; set mapsgfk.south_korea_attr; run;
%macro maplabel(south_korea,south_korea_attr,outds,ID1NAME,ID1 ID,
font=swiss,color=black,size=2,hsys=3,segonly=);
%local tempds;
%centroid(&mapds,&outds,&ids,segonly=&segonly);
data;
set &attrds;
run;
%let tempds=&SYSLAST;
proc sort data=&tempds;
by &ids;
run;
data &outds;
length position xsys ysys when position hsys $1 text $40;
retain function 'label'
xsys ysys '2'
hsys "&hsys"
when 'a'
position '5'
style "&font"
color "&color"
size &size;
merge &outds(in=want) &tempds(keep=&textvar &ids);
by &ids;
if vtype(&textvar) = 'C' then
text=&textvar;
else
text=left(input(&textvar,BEST.));
if want then output;
run;
proc sort data=&outds nodupkey;by &ids; run;
%mend;
LIkely the first thing is that you have to execute the %annomac; macro to make the other annotate macros available. All of the macros are in that macro, along with some help on how to use those.
I read the macro at its entirety starting with
%MACRO ANNOMAC(P1);
and ran below.
data south_korea; set mapsgfk.south_korea; run;
data south_korea_attr; set mapsgfk.south_korea_attr; run;
%macro maplabel(south_korea,south_korea_attr,maggie,ID1NAME,ID1 ID,
font=swiss,color=black,size=2,hsys=3,segonly=);
This gave me an error:
ERROR: Expecting comma (to separate macro parameters) or close parenthesis (to end parameter
list) but found: ID
ERROR: A dummy macro will be compiled.
However, the instruction in the Macro said
/* <ids> Space-separated list of IDs that the map and attr */
and i specified ID1 ID separated by space in the macro call as shown below. I still don't understand.
%macro maplabel(south_korea,south_korea_attr,maggie,ID1NAME,ID1 ID,
font=swiss,color=black,size=2,hsys=3,segonly=);
the whole macro below;
data south_korea; set mapsgfk.south_korea; run;
data south_korea_attr; set mapsgfk.south_korea_attr; run;
%MACRO ANNOMAC(P1);
/*********************************************************************/
/* */
/* MACRO: ANNOMAC */
/* */
/* USAGE: %ANNOMAC(NOMSG) */
/* */
/* */
/* If ANNOMAC is called without the NOMSG parameter, */
/* a message is displayed on the log that the ANNOTATE */
/* macros are available, and that help is available by */
/* calling the %HELPANO macro. If the NOMSG parameter is */
/* specified, no messages are displayed. */
/* */
/* */
/* DESCRIPTION: */
/* This macro compiles and makes available all of the */
/* ANNOTATE macros documented in the SAS/GRAPH Users Guide. */
/* */
/* NOTES: */
/* Use of %ANNOMAC causes all of the ANNOTATE macros to be */
/* compiled. If this is undesirable, you should select the */
/* macros you want to use and place them in a separate member */
/* in your AUTOCALL library. */
/* */
/*********************************************************************/
%IF %UPCASE(&P1)=HELP %THEN %DO;
%PUT %NRSTR( USAGE: %ANNOMAC(NOMSG););
%PUT %NRSTR( );
%PUT %NRSTR(This macro causes all of the ANNOTATE macros documented);
%PUT %NRSTR(in the SAS/GRAPH USERS GUIDE, Version 6 Edition,);
%PUT %NRSTR(to be made available. Each of the macros can then be);
%PUT %NRSTR(used by calling them as documented in the USERS GUIDE.);
%PUT %NRSTR( );
%PUT %NRSTR(If %ANNOMAC is called without any parameters,);
%PUT %NRSTR(a message is displayed on the log that the ANNOTATE);
%PUT %NRSTR(macros are available, and that help is available by);
%PUT %NRSTR(calling the %HELPANO macro. If the NOMSG parameter is);
%PUT %NRSTR(specified, no messages are displayed.);
%PUT %NRSTR( );
%END;
%if &p1= %then %do;
%PUT %NRSTR( );
%PUT %NRSTR(*** ANNOTATE macros are now available ***) ;
%PUT %NRSTR( );
%PUT %NRSTR( For further information on ANNOTATE macros, enter,) ;
%PUT %NRSTR( %HELPANO(macroname), (for specific macros));
%PUT %NRSTR( %HELPANO(ALL), (for information on all macros));
%PUT %NRSTR( or %HELPANO (for a list of macro names));
%PUT %NRSTR( );
%end;
%MEND ANNOMAC;
%MACRO ARROW( X1, Y1, X2, Y2, COLIN, LINE, SIZE, ANGLE, STYLE);
/*--------------------------------------------------------------------*/
/* DRAW an arrow from (X1,Y1) to (X2,Y2). */
/* SIZE determines the arrow width */
/* STYLE determines the arrow head style (open, closed, filled) */
/* COLIN determines the arrow color */
/* LINE determines the length of the arrow head lines */
/* ANLGE determines the angle of the arrow */
/*--------------------------------------------------------------------*/
%move ( &x1, &y1 );
x=&x2; y=&y2; size=&size; style="&style"; color="&colin"; line=&line; angle=∠
FUNCTION="ARROW"; output;
%MEND ARROW;
%MACRO sequenc( seq );
/*--------------------------------------------------------------------*/
/* Definition of ANNOTATE generation sequence. */
/*--------------------------------------------------------------------*/
IF UPCASE("&seq")='AFTER' OR UPCASE("&seq")=:'A' THEN WHEN = "A";
ELSE WHEN = "B";
%MEND sequenc;
%MACRO sequence( seq );
/*-------------------------------------------------------------------*/
/* Definition of ANNOTATE generation sequence. */
/*-------------------------------------------------------------------*/
IF UPCASE("&seq")='AFTER' OR UPCASE("&seq")=:'A' THEN WHEN = "A";
ELSE WHEN = "B";
%MEND sequence;
%MACRO system( xs, ys, hs );
/*--------------------------------------------------------------------*/
/* Definition of ANNOTATE reference systems. */
/*--------------------------------------------------------------------*/
XSYS = "&xs"; YSYS = "&ys"; HSYS = "&hs";
%MEND system;
%MACRO comment( txt );
/*--------------------------------------------------------------------*/
/* Places descriptive text string into ANNOTATE observation stream. */
/* */
/* NOTE: This function will cause a LENGTH to be assigned to the TEXT */
/* variable, due to the nature of the DATA step. Insert a */
/* LENGTH TEXT $ nnn. ; */
/* statement into the DATA step if this is undesirable. */
/* */
/*--------------------------------------------------------------------*/
TEXT=&txt;
FUNCTION = "COMMENT "; output;
TEXT=" ";
%MEND comment;
%MACRO dclanno;
/*--------------------------------------------------------------------*/
/* Set up required variable lengths. */
/* Assure X and Y variables are in the dataset. */
/* Assure all variables in LENGTH statements are referenced. */
/*--------------------------------------------------------------------*/
LENGTH STYLE $32;
LENGTH FUNCTION COLOR $16;
LENGTH XSYS YSYS HSYS $ 1;
LENGTH WHEN POSITION $ 1;
RETAIN XSYS YSYS HSYS;
X = . ;
Y = . ;
STYLE = " ";
POSITION = "5";
COLOR = " ";
FUNCTION = " ";
%system( 4, 4, 4 );
%sequence( BEFORE );
%MEND dclanno;
%MACRO move( x1, y1 );
/*--------------------------------------------------------------------*/
/* MOVE to the requested ( X1,Y1 ) coordinate. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
FUNCTION = "MOVE "; output;
%MEND move;
%MACRO draw( x1, y1, colin, lintyp, width );
/*--------------------------------------------------------------------*/
/* DRAW a line to the requested ( X1,Y1 ) coordinate. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
LINE = &lintyp;
SIZE = &width;
IF "&colin" =: '*' THEN ; ELSE color = "&colin" ;
FUNCTION = "DRAW "; output;
%MEND draw;
%MACRO label( x1, y1, txt, coltxt, ang, rot, hgt, font, pos );
/*--------------------------------------------------------------------*/
/* Place the TXT string at ( X1,Y1 ). */
/* */
/* NOTE: Literal text MUST be enclosed in quotes, otherwise the TEXT */
/* variable will be assigned the value of the variable named in */
/* the symbolic macro parameter. DATA step errors will occur if */
/* one of these two conditions is not met. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
ANGLE = ∠
ROTATE = &rot;
SIZE = &hgt;
STYLE = "&font";
TEXT = &txt;
IF "&pos" =: '*' THEN ; ELSE POSITION = "&pos" ;
IF "&coltxt" =: '*' THEN ; ELSE color = "&coltxt";
FUNCTION = "LABEL "; output;
%MEND label;
%MACRO rect( x1, y1, x2, y2, colin, lintyp, width );
/*--------------------------------------------------------------------*/
/* Draw a rectangle. Non-fillable definition. */
/* */
/* NOTE: ( X1,Y1 ) and ( X2,Y2 ) are opposing corners. */
/*--------------------------------------------------------------------*/
IF "&colin" =: '*' THEN ; ELSE color = "&colin" ;
LINE = &lintyp;
SIZE = &width;
x=&x1; y=&y1; function="POLYLINE"; output;
x=&x2; y=&y1; function="POLYCONT"; output;
x=&x2; y=&y2; function="POLYCONT"; output;
x=&x1; y=&y2; function="POLYCONT"; output;
x=&x1; y=&y1; function="POLYCONT"; output;
%MEND rect;
%MACRO bar( x1, y1, x2, y2, color, bartyp, pattern );
/*--------------------------------------------------------------------*/
/* Draw a rectangle. Fillable definition. */
/* */
/* NOTE: ( X1,Y1 ) and ( X2,Y2 ) are opposing corners. */
/*--------------------------------------------------------------------*/
IF "&color" =: '*' THEN ; ELSE color = "&color" ;
%move ( &x1, &y1 );
X = &x2;
Y = &y2;
D8U2M1M6 = -1;
LINE = &bartyp;
STYLE = "&pattern";
IF ( SIZE > 0 ) THEN DO; D8U2M1M6 = SIZE; SIZE = 0; END;
ELSE SIZE = 0;
FUNCTION = "BAR "; output;
IF ( D8U2M1M6 > 0 ) THEN SIZE = D8U2M1M6; DROP D8U2M1M6;
%MEND bar;
%MACRO bar2( x1, y1, x2, y2, color, bartyp, pattern, width );
/*--------------------------------------------------------------------*/
/* Draw a rectangle. Fillable definition. */
/* */
/* NOTE: ( X1,Y1 ) and ( X2,Y2 ) are opposing corners. */
/*--------------------------------------------------------------------*/
IF "&color" =: '*' THEN ; ELSE color = "&color" ;
%move ( &x1, &y1 );
X = &x2;
Y = &y2;
LINE = &bartyp;
STYLE = "&pattern";
SIZE = &width;
FUNCTION = "BAR "; output;
%MEND bar2;
%MACRO circle( x1, y1, rad, colin );
/*--------------------------------------------------------------------*/
/* Draw a circle with center at ( X1,Y1 ) of radius RAD. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
LINE = 0;
ANGLE = 0.00;
ROTATE = 360.00;
SIZE = &rad;
STYLE = "EMPTY";
IF "&colin" =: '*' THEN ; ELSE color = "&colin";
FUNCTION = "PIE"; output;
%MEND circle;
%MACRO slice( x1, y1, ang, rot, rad, color, pattern, ltyp );
/*--------------------------------------------------------------------*/
/* Define a pie slice. */
/* Center at ( X1,Y1 ), radius of RAD, starting angle of ANG, with a */
/* traverse angle of ROT. LTYP defines type of boundary to draw. */
/* */
/* NOTE: LTYP must be defined by literal strings as listed below */
/* */
/* Setting LTYP to WHOLE is a special case in the macro, */
/* and causes the overriding of specified ANG and ROT values. */
/* */
/* See documentation for special ANG/ROT value handling. */
/* */
/*--------------------------------------------------------------------*/
%LET lntp= %UPCASE("<yp");
%IF %INDEX(&lntp,WHOLE) > 0 %THEN %STR( LINE = 0;) ;
%ELSE %IF %INDEX(&lntp,LEAD) > 0 %THEN %STR( LINE = 1;) ;
%ELSE %IF %INDEX(&lntp,TRAIL) > 0 %THEN %STR( LINE = 2;) ;
%ELSE %IF %INDEX(&lntp,BOTH) > 0 %THEN %STR( LINE = 3;) ;
%ELSE %IF %INDEX(&lntp,NONE) > 0 %THEN %STR( LINE = 0;) ;
%ELSE %STR( LINE = 3;) ;
X = &x1;
Y = &y1;
%IF %INDEX(&lntp,WHOLE) > 0
%THEN %DO;
%STR( ANGLE = 0; );
%STR( ROTATE = 360; );
%END;
%ELSE %DO;
%STR( ANGLE = ∠ );
%STR( ROTATE = &rot; );
%END;
SIZE = &rad;
STYLE = "&pattern";
IF "&color" =: '*' THEN ; ELSE color = "&color";
FUNCTION = "PIE "; output;
%MEND slice;
%MACRO piexy( ang, mul );
/*--------------------------------------------------------------------*/
/* Return coordinates along the radius of a previously defined pie. */
/*--------------------------------------------------------------------*/
X = .;
Y = .;
SIZE = &mul;
ANGLE = ∠
FUNCTION = "PIEXY "; output;
%MEND piexy;
%MACRO poly ( x1, y1, color, pattern, lintyp );
/*--------------------------------------------------------------------*/
/* Begin definition of a polygon. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
D8U2M1M6= -1;
LINE = &lintyp;
STYLE = "&pattern";
IF ( SIZE > 0 ) THEN DO; D8U2M1M6 = SIZE; SIZE = 0; END;
ELSE SIZE = 0;
IF "&color" =: '*' THEN ; ELSE color = "&color" ;
FUNCTION = "POLY "; output;
IF ( D8U2M1M6 > 0 ) THEN SIZE = D8U2M1M6; DROP D8U2M1M6;
%MEND poly;
%MACRO poly2 ( x1, y1, color, pattern, lintyp, width );
/*--------------------------------------------------------------------*/
/* Begin definition of a polygon. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
LINE = &lintyp;
STYLE = "&pattern";
SIZE = &width;
IF "&color" =: '*' THEN ; ELSE color = "&color" ;
FUNCTION = "POLY "; output;
%MEND poly2;
%MACRO polylin ( x1, y1, color, lintyp, width);
/*--------------------------------------------------------------------*/
/* Begin definition of a polyline. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
LINE = &lintyp;
SIZE = &width;
IF "&color" =: '*' THEN ; ELSE color = "&color" ;
FUNCTION = "POLYLINE"; output;
%MEND polylin;
%MACRO polyline ( x1, y1, color, lintyp, width);
/*--------------------------------------------------------------------*/
/* Begin definition of a polyline. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
LINE = &lintyp;
SIZE = &width;
IF "&color" =: '*' THEN ; ELSE color = "&color" ;
FUNCTION = "POLYLINE"; output;
%MEND polyline;
%MACRO polycon ( x1, y1, colin );
/*--------------------------------------------------------------------*/
/* Continue definition of a polygon. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
IF "&colin" =: '*' THEN ; ELSE color = "&colin";
FUNCTION = "POLYCONT"; output;
%MEND polycon;
%MACRO polycont ( x1, y1, colin );
/*--------------------------------------------------------------------*/
/* Continue definition of a polygon. */
/*--------------------------------------------------------------------*/
X = &x1;
Y = &y1;
IF "&colin" =: '*' THEN ; ELSE color = "&colin";
FUNCTION = "POLYCONT"; output;
%MEND polycont;
%MACRO frame ( colin, lintyp, width, pattern );
/*--------------------------------------------------------------------*/
/* Draw a frame around active coordinate system. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
IF "&colin" =: '*' THEN ; ELSE color = "&colin";
STYLE = "&pattern";
LINE= &lintyp;
SIZE= &width;
FUNCTION = "FRAME "; output;
%MEND frame;
%MACRO scale( ptx,pty, xmin,ymin, xmax,ymax, vxmin,vymin, vxmax,vymax );
/*--------------------------------------------------------------------*/
/* */
/* scales input coordinates ( ptx,pty ) into a requested range */
/* */
/* vxmin,vymin are the output range minimums ( target ) */
/* vxmax,vymax are the output range maximums ( target ) */
/* */
/* xmin,ymin are the input range minimums ( source ) */
/* xmax,ymax are the input range maximums ( source ) */
/* */
/* NOTE: ABS(minimum) MAY NOT EQUAL ABS(maximum). */
/* */
/* NO OBSERVATION IS OUTPUT BY THIS MACRO. */
/* */
/*--------------------------------------------------------------------*/
%LET f1 = ( (&vxmax-&vxmin) / (&xmax-&xmin) );
%LET f2 = ( (&vymax-&vymin) / (&ymax-&ymin) );
X = &f1 * (&ptx-&xmin);
Y = &f2 * (&pty-&ymin);
%MEND scale;
%MACRO scalet(ptx,pty, xmin,ymin, xmax,ymax, vxmin,vymin, vxmax,vymax);
/*--------------------------------------------------------------------*/
/* */
/* scales input coordinates ( ptx,pty ) into a requested range and */
/* automatically controls origin displacement */
/* */
/* */
/* vxmin,vymin are the output range minimums ( target ) */
/* vxmax,vymax are the output range maximums ( target ) */
/* */
/* xmin,ymin are the input range minimums ( source ) */
/* xmax,ymax are the input range maximums ( source ) */
/* */
/* NOTE: ABS(minimum) MAY NOT EQUAL ABS(maximum). */
/* */
/* NO OBSERVATION IS OUTPUT BY THIS MACRO. */
/* */
/*--------------------------------------------------------------------*/
%LET f1 = ( (&vxmax-&vxmin) / (&xmax-&xmin) );
%LET f2 = ( (&vymax-&vymin) / (&ymax-&ymin) );
X = &f1 * (&ptx-&xmin) + &vxmin ;
Y = &f2 * (&pty-&ymin) + &vymin ;
%MEND scalet;
%MACRO push;
/*--------------------------------------------------------------------*/
/* PUSH values (XLAST,YLAST),(XLSTT,YLSTT) onto a LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
FUNCTION="PUSH "; output;
%MEND push;
%MACRO pop;
/*--------------------------------------------------------------------*/
/* POP values (XLAST,YLAST),(XLSTT,YLSTT) from the LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
FUNCTION="POP "; output;
%MEND pop;
%MACRO swap;
/*--------------------------------------------------------------------*/
/* SWAP values (XLAST,YLAST) and (XLSTT,YLSTT). */
/* Does NOT affect values on LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
FUNCTION="SWAP "; output;
%MEND swap;
%MACRO txt2cnt;
/*--------------------------------------------------------------------*/
/* COPY values (XLSTT,YLSTT) into (XLAST,YLAST). */
/* Does NOT affect values on LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
FUNCTION = "TXT2CNTL"; output;
%MEND txt2cnt;
%MACRO txt2cntl;
/*--------------------------------------------------------------------*/
/* COPY values (XLSTT,YLSTT) into (XLAST,YLAST). */
/* Does NOT affect values on LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
FUNCTION = "TXT2CNTL"; output;
%MEND txt2cntl;
%MACRO cntl2tx;
/*--------------------------------------------------------------------*/
/* COPY values (XLAST,YLAST) into (XLSTT,YLSTT). */
/* Does NOT affect values on LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
FUNCTION = "CNTL2TXT"; output;
%MEND cntl2tx;
%MACRO cntl2txt;
/*--------------------------------------------------------------------*/
/* COPY values (XLAST,YLAST) into (XLSTT,YLSTT). */
/* Does NOT affect values on LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
FUNCTION = "CNTL2TXT"; output;
%MEND cntl2txt;
%MACRO draw2tx ( colin, lintyp, width );
/*--------------------------------------------------------------------*/
/* DRAW a line from (XLAST,YLAST) to (XLSTT,YLSTT). */
/* Does NOT affect values on LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
SIZE = &width;
LINE = &lintyp;
IF "&colin" =: '*' THEN ; ELSE color = "&colin" ;
FUNCTION = "DRAW2TXT"; output;
%MEND draw2tx;
%MACRO draw2txt ( colin, lintyp, width );
/*--------------------------------------------------------------------*/
/* DRAW a line from (XLAST,YLAST) to (XLSTT,YLSTT). */
/* Does NOT affect values on LIFO system stack. */
/*--------------------------------------------------------------------*/
X = . ;
Y = . ;
SIZE = &width;
LINE = &lintyp;
IF "&colin" =: '*' THEN ; ELSE color = "&colin" ;
FUNCTION = "DRAW2TXT"; output;
%MEND draw2txt;
%MACRO line( x1, y1, x2, y2, colin, lintyp, width );
/*--------------------------------------------------------------------*/
/* DRAW a line from (X1,Y1) to (X2,Y2). */
/* Simplified version supplying the invisible move instruction. */
/*--------------------------------------------------------------------*/
IF "&colin" =: '*' THEN ; ELSE color = "&colin" ;
%move( &x1, &y1 );
%draw( &x2, &y2, &colin, &lintyp, &width );
%MEND line;
/*********************************************************************/
/* WEBOUT Utility macros */
/*********************************************************************/
/*********************************************************************/
/* */
/* MACRO: AREATAG */
/* */
/* DESCRIPTION: */
/* This macro uses a SAS dataset produced by the WEBOUT dataset */
/* option of SAS/GRAPH procedures to write HTML AREA tags fo r */
/* a HTML imagemap. */
/* */
/* USAGE: %AREATAG; */
/* */
/* NOTES: */
/* For use in a data step processing a WEBOUT dataset. This macro */
/* writes a HTML AREA tag based on the current WEBOUT dataset */
/* observation to the current output file */
/* */
/*********************************************************************/
%macro areatag;
do;
if shape='RECT'
then
put '<AREA SHAPE=RECT COORDS="'
x1 4. ',' y1 4. ',' x2 4. ',' y2 4. '" '
link $varying. length '>';
else
if shape='POLY'
then do;
put '<AREA SHAPE=POLY COORDS="' @;
do i=1 to nxy - 1;
new=compress(put(x{i},4.)||','||put(y{i},4.)||',');
put new @;
end;new=compress(put(x{nxy},4.)||','||put(y{nxy},4.));
put new '"'
link $varying. length '>';
end;
end;
%mend areatag;
/*********************************************************************/
/* MACRO: IMAGEMAP */
/* */
/* DESCRIPTION: */
/* This macro uses a SAS dataset produced by the WEBOUT dataset */
/* option of SAS/GRAPH procedures to write HTML imagemap tags. */
/* */
/* USAGE: %IMAGEMAP(IN, OUT, EXT); */
/* */
/* PARAMETERS: */
/* IN -> WEBOUT dataset */
/* OUT -> HTML output file */
/* NAME -> Optional imagemap name */
/* */
/* NOTES: */
/* This macro requires the AREATAG macro. */
/* */
/* If the optional imagemap name is not given, the imagemap names */
/* are the value of the GRAPH variable in the WEBOUT dataset. */
/* */
/* The optional imagemap name should no be used if the WEBOUT */
/* dataset contains information more than one image as in the case */
/* when by groups used by the procedure that created the WEBOUT */
/* dataset. */
/* */
/* The user is responsible for writing the HTML IMG tag for the */
/* image that uses this imagemap. */
/* */
/*********************************************************************/
%macro imagemap(in, out, name='');
/* Be sure that our NAME= value is 32 characters or less*/
%if %length(&name) > 32
%then %let name = %substr( &name, 1, 32 );
data _null_; set ∈ by graph; file &out mod;
array x{100} x1-x100;
array y{100} y1-y100;
length mapname $ 45;
if first.graph
then do;
if &name = '' then
mapname = '<MAP NAME="' || trim(graph) || '">';
else
mapname = '<MAP NAME="' || &name || '">';
put mapname;
end;
%areatag;
if last.graph then put '</MAP>';
run;
%mend imagemap;
/*********************************************************************/
/* */
/* MACRO: HOTSPOTS */
/* */
/* DESCRIPTION: */
/* This macro uses a SAS dataset produced by the WEBOUT dataset */
/* option of SAS/GRAPH procedures to write an annotate dataset */
/* ofthe areas described in the WEBOUT dataset. */
/* */
/* USAGE: %HOTSPOTS(IN=webout, OUT=annotate, XPIXELS=nx, YPIXELS=ny, */
/* COLOR=name, WIDTH=n, FILL=type */
/* */
/* PARAMETERS: */
/* IN= WEBOUT dataset (default _LAST_) */
/* OUT= Annotate dataset (default HOTSPOTS) */
/* XPIXELS= Number of x pixels in the graph associate with the */
/* WEBOUT dataset (default 1280) */
/* YPIXELS= Number of y pixels in the graph associate with the */
/* WEBOUT dataset (default 1024) */
/* COLOR= Color to be used when drawing the WEBOUT areas */
/* (default RED) */
/* FILL= Fill type to be used when drawing the WEBOUT dataset */
/* areas (default EMPTY) */
/* */
/* NOTES: */
/* Goptions XPIXELS and YPIXELS should be used to insure that the */
/* numbers pixels use in the graph are the same as used with this */
/* macro. */
/* */
/*********************************************************************/
%macro hotspots
(
in=_last_,
out=hotspots,
xpixels=1280,
ypixels=1024,
color=red,
width=1,
fill=empty
);
data &out; set ∈
drop shape length link nxy x1-x100 y1-y100 xscale yscale xmax ymax;
array xarray x1-x100;
array yarray y1-y100;
length function color style $ 16;
retain hsys xsys ysys '3'
color "&color"
style "&fill"
when 'a'
size &width
;
xmax = &xpixels - 1;
ymax = &ypixels - 1;
xscale = 100 / xmax;
yscale = 100 / ymax;
if shape = 'RECT' then do;
x1 = x1 * xscale;
x2 = x2 * xscale;
y1 = (ymax - y1) * yscale;
y2 = (ymax - y2) * yscale;
function = 'poly'; x = x1; y = y1; output;
function = 'polycont'; x = x2; y = y1; output;
function = 'polycont'; x = x2; y = y2; output;
function = 'polycont'; x = x1; y = y2; output;
function = 'polycont'; x = x1; y = y1; output;
end;
if shape = 'POLY' then do;
function = 'poly';
x = x1 * xscale;
y = (ymax - y1) * yscale;
output;
function = 'polycont';
do _i_ = 2 to nxy;
x = xarray * xscale;
y = (ymax - yarray) * yscale;
output;
end;
end;
run;
%mend hotspots;
%macro centroid( in, out, ids, segonly= );
%local lastid pos hasseg tempds;
%let lastid=&ids;
%let pos=%index(&ids,%str( ));
%do %while(&pos > 0);
%let lastid=%substr(&lastid, &pos, %length(&lastid)-&pos+1);
%let pos=%index(&lastid,%str( ));
%end;
data _null_;
segment=.;
set &in(obs=1);
if segment ne . then call symput("hasseg", "true");
run;
data;
set ∈
%if %length(&hasseg) > 0 and %length(&segonly) > 0 %then %do;
where segment = &segonly;
%end;
run;
%let tempds=&SYSLAST;
proc summary data=&tempds;
by &ids;
var x y;
output out=&out min=_xmin _ymin max=_xmax _ymax n=_npoints;
run;
data &out;
set &out;
_xbar = (_xmin+_xmax)*0.5;
_ybar = (_ymin+_ymax)*0.5;
keep &ids _xbar _ybar _npoints;
run;
data &tempds;
retain _newring 1 _found _seglast _xfirst _yfirst _xlast _ylast _yc _xc;
merge &out(in=_want) &tempds;
by &ids;
if _newring then do;
if _want then do;
_xc = _xbar;
_yc = _ybar;
end;
_xfirst = x;
_yfirst = y;
_xlast = .;
_ylast = .;
_seglast= .;
if first.&lastid then _found = 0;
end;
if x = . | y = . then _newring = 1;
else _newring = 0;
_xlast = lag(x);
_ylast = lag(y);
%if %length(&hasseg) > 0 %then %do;
_seglast = lag(segment);
%end;
if ^first.&lastid & _xlast ne . & _ylast ne . then do;
%if %length(&hasseg) > 0 %then %do;
if _seglast = segment then do;
if _newring then do;
x = _xfirst;
y = _yfirst;
end;
link calc;
end;
else if _seglast ne . then do;
_xkeep = x;
_ykeep = y;
x = _xfirst;
y = _yfirst;
segment = _seglast;
link calc;
_xfirst = _xkeep;
_yfirst = _ykeep;
end;
%end;
%else %do;
link calc;
%end;
end;
if last.&lastid then do;
_xlast = x;
_ylast = y;
x = _xfirst;
y = _yfirst;
_newring = 1;
link calc;
if _found = 0 then output;
end;
return;
calc:
/* Swap coordinates */
if _ylast > y then do;
_x1 = _xlast;
_xlast1 = x;
_y1 = _ylast;
_ylast1 = y;
end;
else do;
_x1 = x;
_xlast1 = _xlast;
_y1 = y;
_ylast1 = _ylast;
end;
if _yc >= _ylast1 & _yc < _y1 then do;
_xc = _xlast1 + (_yc - _ylast1)/(_y1 - _ylast1) * (_x1 - _xlast1);
_found = 1;
output;
end;
return;
keep _xc _yc &ids;
run;
proc sort data=&tempds;
by &ids _xc;
run;
data &out;
retain _xc1 . _dist1 _dist _xckeep 0;
set &tempds;
by &ids;
if _xc1 = . then
_xc1 = _xc;
else do;
_dist1 = abs(_xc-_xc1);
if _dist1 > _dist then do;
_xckeep = (_xc+_xc1)/2;
_dist = _dist1;
end;
_xc1 = .;
end;
if last.&lastid then do;
if first.&lastid then x = _xc;
else x = _xckeep;
y = _yc;
output;
_dist= 0;
_xc1 = .;
end;
keep x y &ids;
run;
%mend;
/*********************************************************************/
/* */
/* MACRO: MAPLABEL */
/* */
/* DESCRIPTION: */
/* This macro is used to create an annotate data set for use with */
/* PROC GMAP output. The resulting data set can be used with ANNO=*/
/* The user can use the default font/size information, or choose */
/* their own. */
/* */
/* USAGE: %HOTSPOTS(<map-ds>, <attribute-ds>, <output-ds>,<var>, */
/* <ids>, FONT=, COLOR=, SIZE=, HSYS=); */
/* */
/* PARAMETERS: */
/* <map-ds> Input Map data set */
/* <attr-ds> Attribute data set that contains var for label */
/* <out-ds> Resulting output data set for use in ANNO= in GMAP */
/* <var> Variable for label on <attr-ds>. Can be text or num */
/* <ids> Space-separated list of IDs that the map and attr */
/* data sets are sorted on. */
/* FONT= Font for the label. Default is SWISS. */
/* COLOR= Color of the label. Devault is BLACK. */
/* SIZE= Size of the label. Default is 1 <unit> */
/* HSYS= UNIT system for SIZE=. Default is 3 (PERCENT) */
/* SEGONLY= Only process the segment specified of each polygon */
/* set (map area). Useful to not have centroid placed */
/* in non-polygon area */
/* */
/* NOTES: */
/* */
/*********************************************************************/
%macro maplabel(mapds,attrds,outds,textvar,ids,
font=swiss,color=black,size=2,hsys=3,segonly=);
%local tempds;
%centroid(&mapds,&outds,&ids,segonly=&segonly);
data;
set &attrds;
run;
%let tempds=&SYSLAST;
proc sort data=&tempds;
by &ids;
run;
data &outds;
length position xsys ysys when position hsys $1 text $40;
retain function 'label'
xsys ysys '2'
hsys "&hsys"
when 'a'
position '5'
style "&font"
color "&color"
size &size;
merge &outds(in=want) &tempds(keep=&textvar &ids);
by &ids;
if vtype(&textvar) = 'C' then
text=&textvar;
else
text=left(input(&textvar,BEST.));
if want then output;
run;
proc sort data=&outds nodupkey;by &ids; run;
%mend;
%macro maplabel(south_korea,south_korea_attr,maggie,ID1NAME,ID1 ID,
font=swiss,color=black,size=2,hsys=3,segonly=);
I couldn't figure out the macro. But following worked. At least this had created 250 centroids for 250 cities. Not verified the validity of it yet.
proc sort data=mapsgfk.south_korea out=centroids;
by ID1 ID segment;
run;
data centroids_temp;
retain yi yj xi xj a cx cy x0 y0 0;
set centroids(keep=ID1 ID segment lat long rename=(lat=yj long=xj));
by ID1 ID segment;
if(first.segment) then do;
cx = 0;
cy = 0;
a = 0;
x0 = xj;
y0 = yj;
end;
else if(not first.segment) then do;
ta = (xi*yj - xj*yi);
cx + ((xi+xj)*ta);
cy + ((yi+yj)*ta);
a + ta;
end;
if(last.segment) then do;
ta = (xj*y0 - x0*yj);
cx + ((xj+x0)*ta);
cy + ((yj+y0)*ta);
a = ta + a * 0.5;
cx = cx / (6*a);
cy = cy / (6*a);
output;
end;
xi = xj;
yi = yj;
run;
proc sql;
create table centroid_weight as
select
ID1, ID, sum(a) as sum
from centroids_temp
group by ID1, ID;
quit;
proc sql;
create table centroids as
select a.ID1, a.ID,
sum(cx*(a / sum)) as lat,
sum(cy*(a / sum)) as long
from centroids_temp a
inner join centroid_weight b
on (a.ID1 = b.ID1 and a.ID = b.ID)
group by a.ID1, a.ID;
quit;
proc sql;
drop table centroids_temp;
drop table centroid_weight;
quit;
Hope this would help anybody trying to create centroids using %ANNOMAC and %CENTROID macros. Below is the final correct approach. It would not have been difficult for anybody who has better understanding of how to define macros than me.
data south_korea; set mapsgfk.south_korea; run;
%MACRO ANNOMAC(P1);
/*********************************************************************/
/* */
/* MACRO: ANNOMAC */
/* */
/* USAGE: %ANNOMAC(NOMSG) */
/* */
/* */
/* If ANNOMAC is called without the NOMSG parameter, */
/* a message is displayed on the log that the ANNOTATE */
/* macros are available, and that help is available by */
/* calling the %HELPANO macro. If the NOMSG parameter is */
/* specified, no messages are displayed. */
/* */
/* */
/* DESCRIPTION: */
/* This macro compiles and makes available all of the */
/* ANNOTATE macros documented in the SAS/GRAPH Users Guide. */
/* */
/* NOTES: */
/* Use of %ANNOMAC causes all of the ANNOTATE macros to be */
/* compiled. If this is undesirable, you should select the */
/* macros you want to use and place them in a separate member */
/* in your AUTOCALL library. */
/* */
/*********************************************************************/
%IF %UPCASE(&P1)=HELP %THEN %DO;
%PUT %NRSTR( USAGE: %ANNOMAC(NOMSG););
%PUT %NRSTR( );
%PUT %NRSTR(This macro causes all of the ANNOTATE macros documented);
%PUT %NRSTR(in the SAS/GRAPH USERS GUIDE, Version 6 Edition,);
%PUT %NRSTR(to be made available. Each of the macros can then be);
%PUT %NRSTR(used by calling them as documented in the USERS GUIDE.);
%PUT %NRSTR( );
%PUT %NRSTR(If %ANNOMAC is called without any parameters,);
%PUT %NRSTR(a message is displayed on the log that the ANNOTATE);
%PUT %NRSTR(macros are available, and that help is available by);
%PUT %NRSTR(calling the %HELPANO macro. If the NOMSG parameter is);
%PUT %NRSTR(specified, no messages are displayed.);
%PUT %NRSTR( );
%END;
%if &p1= %then %do;
%PUT %NRSTR( );
%PUT %NRSTR(*** ANNOTATE macros are now available ***) ;
%PUT %NRSTR( );
%PUT %NRSTR( For further information on ANNOTATE macros, enter,) ;
%PUT %NRSTR( %HELPANO(macroname), (for specific macros));
%PUT %NRSTR( %HELPANO(ALL), (for information on all macros));
%PUT %NRSTR( or %HELPANO (for a list of macro names));
%PUT %NRSTR( );
%end;
%MEND ANNOMAC;
%macro centroid( in, out, ids, segonly= );
%local lastid pos hasseg tempds;
%let lastid=&ids;
%let pos=%index(&ids,%str( ));
%do %while(&pos > 0);
%let lastid=%substr(&lastid, &pos, %length(&lastid)-&pos+1);
%let pos=%index(&lastid,%str( ));
%end;
data _null_;
segment=.;
set &in(obs=1);
if segment ne . then call symput("hasseg", "true");
run;
data;
set ∈
%if %length(&hasseg) > 0 and %length(&segonly) > 0 %then %do;
where segment = &segonly;
%end;
run;
%let tempds=&SYSLAST;
proc summary data=&tempds;
by &ids;
var x y;
output out=&out min=_xmin _ymin max=_xmax _ymax n=_npoints;
run;
data &out;
set &out;
_xbar = (_xmin+_xmax)*0.5;
_ybar = (_ymin+_ymax)*0.5;
keep &ids _xbar _ybar _npoints;
run;
data &tempds;
retain _newring 1 _found _seglast _xfirst _yfirst _xlast _ylast _yc _xc;
merge &out(in=_want) &tempds;
by &ids;
if _newring then do;
if _want then do;
_xc = _xbar;
_yc = _ybar;
end;
_xfirst = x;
_yfirst = y;
_xlast = .;
_ylast = .;
_seglast= .;
if first.&lastid then _found = 0;
end;
if x = . | y = . then _newring = 1;
else _newring = 0;
_xlast = lag(x);
_ylast = lag(y);
%if %length(&hasseg) > 0 %then %do;
_seglast = lag(segment);
%end;
if ^first.&lastid & _xlast ne . & _ylast ne . then do;
%if %length(&hasseg) > 0 %then %do;
if _seglast = segment then do;
if _newring then do;
x = _xfirst;
y = _yfirst;
end;
link calc;
end;
else if _seglast ne . then do;
_xkeep = x;
_ykeep = y;
x = _xfirst;
y = _yfirst;
segment = _seglast;
link calc;
_xfirst = _xkeep;
_yfirst = _ykeep;
end;
%end;
%else %do;
link calc;
%end;
end;
if last.&lastid then do;
_xlast = x;
_ylast = y;
x = _xfirst;
y = _yfirst;
_newring = 1;
link calc;
if _found = 0 then output;
end;
return;
calc:
/* Swap coordinates */
if _ylast > y then do;
_x1 = _xlast;
_xlast1 = x;
_y1 = _ylast;
_ylast1 = y;
end;
else do;
_x1 = x;
_xlast1 = _xlast;
_y1 = y;
_ylast1 = _ylast;
end;
if _yc >= _ylast1 & _yc < _y1 then do;
_xc = _xlast1 + (_yc - _ylast1)/(_y1 - _ylast1) * (_x1 - _xlast1);
_found = 1;
output;
end;
return;
keep _xc _yc &ids;
run;
proc sort data=&tempds;
by &ids _xc;
run;
data &out;
retain _xc1 . _dist1 _dist _xckeep 0;
set &tempds;
by &ids;
if _xc1 = . then
_xc1 = _xc;
else do;
_dist1 = abs(_xc-_xc1);
if _dist1 > _dist then do;
_xckeep = (_xc+_xc1)/2;
_dist = _dist1;
end;
_xc1 = .;
end;
if last.&lastid then do;
if first.&lastid then x = _xc;
else x = _xckeep;
y = _yc;
output;
_dist= 0;
_xc1 = .;
end;
keep x y &ids;
run;
%mend;
/*********************************************************************/
/* */
/* MACRO: MAPLABEL */
/* */
/* DESCRIPTION: */
/* This macro is used to create an annotate data set for use with */
/* PROC GMAP output. The resulting data set can be used with ANNO=*/
/* The user can use the default font/size information, or choose */
/* their own. */
/* */
/* USAGE: %HOTSPOTS(<map-ds>, <attribute-ds>, <output-ds>,<var>, */
/* <ids>, FONT=, COLOR=, SIZE=, HSYS=); */
/* */
/* PARAMETERS: */
/* <map-ds> Input Map data set */
/* <attr-ds> Attribute data set that contains var for label */
/* <out-ds> Resulting output data set for use in ANNO= in GMAP */
/* <var> Variable for label on <attr-ds>. Can be text or num */
/* <ids> Space-separated list of IDs that the map and attr */
/* data sets are sorted on. */
/* FONT= Font for the label. Default is SWISS. */
/* COLOR= Color of the label. Devault is BLACK. */
/* SIZE= Size of the label. Default is 1 <unit> */
/* HSYS= UNIT system for SIZE=. Default is 3 (PERCENT) */
/* SEGONLY= Only process the segment specified of each polygon */
/* set (map area). Useful to not have centroid placed */
/* in non-polygon area */
/* */
/* NOTES: */
/* */
/*********************************************************************/
%macro maplabel(mapds,attrds,outds,textvar,ids,
font=swiss,color=black,size=2,hsys=3,segonly=);
%local tempds;
data;
set &attrds;
run;
%let tempds=&SYSLAST;
proc sort data=&tempds;
by &ids;
run;
data &outds;
length position xsys ysys when position hsys $1 text $40;
retain function 'label'
xsys ysys '2'
hsys "&hsys"
when 'a'
position '5'
style "&font"
color "&color"
size &size;
merge &outds(in=want) &tempds(keep=&textvar &ids);
by &ids;
if vtype(&textvar) = 'C' then
text=&textvar;
else
text=left(input(&textvar,BEST.));
if want then output;
run;
proc sort data=&outds nodupkey;by &ids; run;
%mend;
%centroid(south_korea,centroids,ID,segonly=1);
SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.