BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
Cruise
Ammonite | Level 13

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.  

 

1 ACCEPTED SOLUTION

Accepted Solutions
Cruise
Ammonite | Level 13

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);


View solution in original post

7 REPLIES 7
ballardw
Super User

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.

Cruise
Ammonite | Level 13

@ballardw 

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?

Cruise
Ammonite | Level 13

@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;
ballardw
Super User

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.

Cruise
Ammonite | Level 13

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=&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    = &ang;
   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("&ltyp");
      %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    = &ang; );
      %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    = &ang;
   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 &in; 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 &in;
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 &in;
%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=);

 

 

Cruise
Ammonite | Level 13

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;
Cruise
Ammonite | Level 13

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 &in;
%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: Save the Date

 SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!

Save the date!

How to Concatenate Values

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 7 replies
  • 1835 views
  • 1 like
  • 2 in conversation