*! -spgrid-: Generates two-dimensional grids for spatial data analysis *! Version 1.0.1 - 4 October 2011 *! Version 1.0.0 - 3 February 2009 *! Author: Maurizio Pisati *! Department of Sociology and Social Research *! University of Milano Bicocca (Italy) *! maurizio.pisati@unimib.it * ---------------------------------------------------------------------------- * 1. Define program * ---------------------------------------------------------------------------- program spgrid version 10.1 * ---------------------------------------------------------------------------- * 2. Define syntax * ---------------------------------------------------------------------------- syntax [using/], /// [SHape(string)] /// hexagonal,square [RESolution(string)] /// [XDim(numlist max=1 >0 integer)] /// [YDim(numlist max=1 >0 integer)] /// [Unit(string)] /// /// [XRange(numlist min=2 max=2 sort)] /// [YRange(numlist min=2 max=2 sort)] /// /// [MAPID(name)] /// [MAPEXclude(string)] /// [IDEXclude] /// /// [Dots] /// [noVERBose] /// /// Cells(string) /// Points(string) /// [COMPRESS] /// [REPLACE] * ---------------------------------------------------------------------------- * 3. Check syntax * ---------------------------------------------------------------------------- /* Check using file */ local map "`using'" if ("`map'" != "") { if (substr(reverse("`map'"),1,4) != "atd.") { local map "`map'.dta" } capture confirm file "`map'" if _rc { di as err "{p}File {bf:`map'} not found {p_end}" exit 601 } preserve qui use "`map'", clear local BAD = 0 cap confirm numeric variable _ID _X _Y local BAD = `BAD' + _rc local SORTVAR: sortedby local SORTVAR : word 1 of `SORTVAR' if ("`SORTVAR'" != "_ID") local BAD = `BAD' + 1 if (`BAD') { di as err "{p}File {bf:`map'} is not a valid " /// "{help spmap##sd_basemap:{it:basemap}} " /// "dataset {p_end}" exit 198 } restore } /* Check option shape() */ if ("`shape'" != "") { local KLIST "square hexagonal" local LEN = length("`shape'") if (`LEN' < 2) { di as err "{p}Keywords specified in option {bf:{ul:sh}ape()} " /// "cannot be abbreviated to less than 2 letters {p_end}" exit 198 } local OK = 0 foreach K of local KLIST { if ("`shape'" == substr("`K'", 1, `LEN')) { local OK = 1 local shape "`K'" continue, break } } if (!`OK') { di as err "{p}Option {bf:{ul:s}hape()} accepts only " /// "one of the following keywords: " /// "{bf:{ul:sq}uare} " /// "{bf:{ul:he}xagonal} " /// "{p_end}" exit 198 } } /* Check options resolution(), xdim(), and ydim() */ if ("`resolution'" == "" & "`xdim'" == "" & "`ydim'" == "") { di as err "{p}You are requested to specify one of the " /// "following options: {bf:{ul:res}olution()}, " /// "{bf:{ul:xd}im()}, or {bf:{ul:yd}im()} {p_end}" exit 198 } if ("`resolution'" != "" & "`xdim'" != "") { di as err "{p}Options {bf:{ul:res}olution()} and {bf:{ul:xd}im()} " /// "cannot be specified together {p_end}" exit 198 } if ("`resolution'" != "" & "`ydim'" != "") { di as err "{p}Options {bf:{ul:res}olution()} and {bf:{ul:yd}im()} " /// "cannot be specified together {p_end}" exit 198 } if ("`xdim'" != "" & "`ydim'" != "") { di as err "{p}Options {bf:{ul:xd}im()} and {bf:{ul:yd}im()} " /// "cannot be specified together {p_end}" exit 198 } /* Check option resolution() */ if ("`resolution'" != "") { local BAD = 0 local RESTYPE = substr("`resolution'",1,1) local RESSIZE = substr("`resolution'",2,.) if !inlist("`RESTYPE'","a","w") local BAD = `BAD' + 1 qui cap confirm number `RESSIZE' local BAD = cond(_rc, `BAD' + 1, `BAD' + (`RESSIZE'<=0)) if (`BAD') { di as err "{p}Option {bf:{ul:res}olution()} accepts only " /// "one of the following keywords: " /// "{bf:a#} " /// "{bf:w#}, " /// "where {bf:#} represents a positive number" /// "{p_end}" exit 198 } } /* Check options xrange() and yrange() */ if ("`map'" == "") & ("`xrange'" == "" | "`yrange'" == "") { di as err "{p}You are requested to specify options " /// "{bf:{ul:xr}ange()} and {bf:{ul:yr}ange()} {p_end}" exit 198 } /* Check option mapid() */ if ("`map'" != "") & /// ("`mapid'" == "spgrid_id" | /// "`mapid'" == "spgrid_xdim" | /// "`mapid'" == "spgrid_ydim" | /// "`mapid'" == "spgrid_xcoord" | /// "`mapid'" == "spgrid_ycoord" | /// "`mapid'" == "spgrid_data") { di as err "{p}Option {bf:{ul:mapid}()} cannot accept as argument " /// "the following reserved words: {bf:spgrid_id}, " /// "{bf:spgrid_xdim}, {bf:spgrid_ydim}, {bf:spgrid_xcoord}, " /// "{bf:spgrid_ycoord}, {bf:spgrid_status} {p_end}" exit 198 } /* Check option mapexclude() */ if ("`map'" != "") & ("`mapexclude'" != "") { if (substr(reverse("`mapexclude'"),1,4) != "atd.") { local mapexclude "`mapexclude'.dta" } capture confirm file "`mapexclude'" if _rc { di as err "{p}File {bf:`mapexclude'} specified in option " /// "{bf:{ul:mapex}clude()} not found {p_end}" exit 601 } preserve qui use "`mapexclude'", clear local BAD = 0 if ("`idexclude'" == "") { cap confirm numeric variable _ID _X _Y local BAD = `BAD' + _rc local SORTVAR: sortedby local SORTVAR : word 1 of `SORTVAR' if ("`SORTVAR'" != "_ID") local BAD = `BAD' + 1 } if ("`idexclude'" != "") { cap confirm numeric variable _ID local BAD = `BAD' + _rc } if (`BAD') { di as err "{p}File {bf:`mapexclude'} specified in option " /// "{bf:{ul:mapex}clude()} is not a valid " /// "{help spmap##sd_basemap:{it:basemap}} " /// "dataset {p_end}" exit 198 } restore } /* Check option cells() */ if (substr(reverse("`cells'"),1,4) != "atd.") { local cells "`cells'.dta" } cap confirm file "`cells'" if (_rc == 0) & ("`replace'" == "") { di as err "{p}File {bf:`cells'} specified in option " /// "{bf:{ul:c}ells()} already exists {p_end}" exit 602 } /* Check option points() */ if (substr(reverse("`points'"),1,4) != "atd.") { local points "`points'.dta" } cap confirm file "`points'" if (_rc == 0) & ("`replace'" == "") { di as err "{p}File {bf:`points'} specified in option " /// "{bf:{ul:p}oints()} already exists {p_end}" exit 602 } if ("`points'" == "`cells'") { di as err "{p}Options {bf:{ul:p}oints()} and {bf:{ul:c}ells()} " /// "cannot accept the same argument {p_end}" exit 198 } * ---------------------------------------------------------------------------- * 4. Define basic objects * ---------------------------------------------------------------------------- /* Set default cell shape */ if ("`shape'" == "") local shape "hexagonal" /* Set default unit of measure */ if ("`unit'" == "") local unit "units" /* Set default mapid name */ if ("`map'" != "" & "`mapid'" == "") local mapid "spgrid_mapid" /* Set option dots */ if ("`verbose'" != "") local dots "" * ---------------------------------------------------------------------------- * 5. Create gridpoints dataset * ---------------------------------------------------------------------------- /* Define grid limits and size */ if ("`map'" == "") { local XMIN : word 1 of `xrange' local XMAX : word 2 of `xrange' local YMIN : word 1 of `yrange' local YMAX : word 2 of `yrange' } if ("`map'" != "") { preserve qui use "`map'", clear su _X, mean local XMIN = r(min) local XMAX = r(max) su _Y, mean local YMIN = r(min) local YMAX = r(max) restore } local XSIZE = `XMAX' - `XMIN' local YSIZE = `YMAX' - `YMIN' /* Define grid resolution and dimensions */ if ("`resolution'" != "") { if ("`shape'" == "square") { if ("`RESTYPE'" == "a") { local AREA = `RESSIZE' local SIDE = sqrt(`AREA') } if ("`RESTYPE'" == "w") { local SIDE = `RESSIZE' local AREA = `SIDE'^2 } local XRES = `SIDE' local YRES = `SIDE' local XDIM = round(`XSIZE' / `XRES') local YDIM = round(`YSIZE' / `YRES') } if ("`shape'" == "hexagonal") { if ("`RESTYPE'" == "a") { local AREA = `RESSIZE' local SIDE = sqrt(`AREA' / 2.5980762) } if ("`RESTYPE'" == "w") { local SIDE = `RESSIZE' / 1.7320508 local AREA = `SIDE'^2 * 2.5980762 } local XRES = `SIDE' * 1.7320508 local YRES = `SIDE' * 1.5 local XDIM = round(`XSIZE' / `XRES') local YDIM = round(`YSIZE' / `YRES') } } if ("`resolution'" == "") { local ADJ = ("`shape'" == "square") + ("`shape'" == "hexagonal") * 0.8660254 if ("`xdim'" != "") { local XRES = `XSIZE' / `xdim' local YRES = `XRES' * `ADJ' local XDIM = `xdim' local YDIM = round(`YSIZE' / `YRES') } if ("`ydim'" != "") { local YRES = `YSIZE' / `ydim' local XRES = `YRES' / `ADJ' local XDIM = round(`XSIZE' / `XRES') local YDIM = `ydim' } if ("`shape'" == "square") { local SIDE = `XRES' local AREA = `SIDE'^2 } if ("`shape'" == "hexagonal") { local SIDE = `XRES' / 1.7320508 local AREA = `SIDE'^2 * 2.5980762 } } local FGC = `XDIM' * `YDIM' /* Preserve */ preserve /* Create gridpoints dataset */ if ("`verbose'" == "") { di "" di as txt "-> Setting up full grid..." } clear // was: clear all -- Modification suggested by Philippe Van Kerm qui set obs `FGC' qui egen spgrid_xdim = seq(), from(1) to(`XDIM') qui egen spgrid_ydim = seq(), from(1) to(`YDIM') block(`XDIM') if ("`shape'" == "square") { qui gen spgrid_xcoord = `XMIN' + (`XRES' / 2) + (spgrid_xdim - 1) * `XRES' qui gen spgrid_ycoord = `YMAX' - (`YRES' / 2) - (spgrid_ydim - 1) * `YRES' } if ("`shape'" == "hexagonal") { qui gen spgrid_xcoord = `XMIN' + (`XRES' / 2) + (spgrid_xdim - 1) * `XRES' + /// (`XRES' / 2) * !mod(spgrid_ydim, 2) qui gen spgrid_ycoord = `YMAX' - (`YRES' - `XRES' / 3.464) - /// (spgrid_ydim - 1) * `YRES' } qui gen spgrid_status = 1 qui gen spgrid_id = _n order spgrid_id lab var spgrid_id "Grid cell: id number" lab var spgrid_xdim "Grid cell: x-dimension" lab var spgrid_ydim "Grid cell: y-dimension" lab var spgrid_xcoord "Grid point: x-coordinate" lab var spgrid_ycoord "Grid point: y-coordinate" lab var spgrid_status "Grid cell: 1=valid 0=null" /* Clip grid cells outside the map boundaries */ if ("`map'" != "") { tempvar CIP MAPID qui gen `CIP' = . qui gen `MAPID' = . tempfile F0 qui save `F0', replace use "`map'", clear keep _ID _X _Y gen POLY = (_X == .) qui replace POLY = sum(POLY) qui drop if _X == . order _X _Y tempfile F1 qui save `F1', replace rename POLY poly gen IDX = _n collapse (mean) MID=_ID /// (min) first=IDX (max) last=IDX /// (min) xmin=_X (max) xmax=_X /// (min) ymin=_Y (max) ymax=_Y /// , by(poly) tempfile F2 qui save `F2', replace use `F0', clear qui merge using `F2' drop _merge qui merge using `F1' drop _merge _ID POLY su poly, mean local NPOLY = r(N) if ("`verbose'" == "") { di as txt "-> Clipping grid cells outside the boundaries " _c di as txt "of the study region..." } if ("`dots'" != "") { di as txt "" } mata: spgridClip(`FGC', `NPOLY', "`dots'") qui replace spgrid_status = 0 if `CIP' == . keep spgrid_* `CIP' `MAPID' qui drop if (spgrid_id == .) qui replace `CIP' = . } /* Clip grid cells within specified map polygons */ if ("`map'" != "") & ("`mapexclude'" != "") { if ("`verbose'" == "") { di as txt "-> Clipping grid cells within specified subareas " _c di as txt "of the study region..." } if ("`idexclude'" != "") { sort `MAPID' tempfile F0 qui save `F0', replace use "`mapexclude'", clear qui collapse _X, by(_ID) ren _ID `MAPID' keep `MAPID' sort `MAPID' tempfile F1 qui save `F1', replace use `F0', clear qui merge `MAPID' using `F1' qui drop if (_merge == 2) qui replace spgrid_status = 0 if _merge == 3 drop _merge } else { tempfile F0 qui save `F0', replace use "`mapexclude'", clear keep _ID _X _Y gen POLY = (_X == .) qui replace POLY = sum(POLY) qui drop if _X == . order _X _Y tempfile F1 qui save `F1', replace rename POLY poly gen IDX = _n collapse (mean) MID=_ID /// (min) first=IDX (max) last=IDX /// (min) xmin=_X (max) xmax=_X /// (min) ymin=_Y (max) ymax=_Y /// , by(poly) tempfile F2 qui save `F2', replace use `F0', clear qui merge using `F2' drop _merge qui merge using `F1' drop _merge _ID POLY su poly, mean local NPOLY = r(N) if ("`dots'" != "") { di as txt "" } mata: spgridClip(`FGC', `NPOLY', "`dots'") qui replace spgrid_status = 0 if `CIP' == 1 keep spgrid_* `CIP' `MAPID' qui drop if (spgrid_id == .) } } /* Save gridpoints dataset */ if ("`verbose'" == "") { di as txt "-> Saving gridpoints dataset..." } if ("`map'" != "") { qui gen `mapid' = `MAPID' lab var `mapid' "Grid cell: corresponding study region polygon identifier" drop `CIP' `MAPID' } su spgrid_status, mean local VGC = r(sum) local CHARLIST : char _dta[] foreach CHAR in local `CHARLIST' { char _dta[`CHAR'] } char _dta[basemap] "`map'" char _dta[exclmap] "`mapexclude'" char _dta[UnitOfMeasure] "`unit'" char _dta[xmin] `XMIN' char _dta[xmax] `XMAX' char _dta[ymin] `YMIN' char _dta[ymax] `YMAX' char _dta[xsize] `XSIZE' char _dta[ysize] `YSIZE' char _dta[xdim] `XDIM' char _dta[ydim] `YDIM' char _dta[xres] `XRES' char _dta[yres] `YRES' char _dta[CellShape] "`shape'" char _dta[CellSide] `SIDE' char _dta[CellArea] `AREA' char _dta[FullGridCells] `FGC' char _dta[ValidGridCells] `VGC' if ("`compress'" != "") qui keep if spgrid_status sort spgrid_id qui compress order spgrid_id spgrid_xdim spgrid_ydim spgrid_status `mapid' /// spgrid_xcoord spgrid_ycoord qui save "`points'", `replace' * ---------------------------------------------------------------------------- * 6. Create gridcells dataset * ---------------------------------------------------------------------------- /* Create gridcells dataset */ if ("`verbose'" == "") { di as txt "-> Saving gridcells dataset..." } keep spgrid_id spgrid_xcoord spgrid_ycoord local NC = cond("`compress'" != "", `VGC', `FGC') local NR = cond("`shape'" == "square", `NC' * 6, `NC' * 8) qui set obs `NR' qui gen _ID=. qui gen _X=. qui gen _Y=. lab var _ID "Grid cell: id number" lab var _X "Grid cell: x-coordinates" lab var _Y "Grid cell: y-coordinates" local ADJ = cond("`shape'" == "square", 2, 3) local X = `XRES' / 2 local Y = `YRES' / `ADJ' mata: spgridCells(`NC', `NR', "`shape'", `X',`Y') keep _* /* Save gridcells dataset */ sort _ID, stable qui compress qui save "`cells'", `replace' * ---------------------------------------------------------------------------- * 7. Display summary grid features * ---------------------------------------------------------------------------- if ("`verbose'" == "") { local STUDRES = cond("`map'" == "", "rectangular", "non-rectangular") if ("`mapexclude'" != "") local STUDRES "`STUDRES' with gaps" di as txt "" di as txt " Study region: " as res "`STUDRES'" di as txt " Full grid size: " as res `XSIZE' as txt " x " /// as res `YSIZE' as txt " `unit'" di as txt " Full grid limits: [" as res `XMIN' as txt "," /// as res `XMAX' as txt "] x [" as res `YMIN' as txt "," /// as res `YMAX' as txt "]" as txt " `unit'" di as txt " Number of full grid cells: " as res `FGC' di as txt " Number of valid grid cells: " as res `VGC' di as txt " Cell shape: " as res "`shape'" di as txt " Cell side: " as res `SIDE' as txt " `unit'" di as txt " Cell area: " as res `AREA' as txt " squared `unit'" } * ---------------------------------------------------------------------------- * 8. End program * ---------------------------------------------------------------------------- restore end * ---------------------------------------------------------------------------- * Mata functions * * : spgridClip() * : spgridCells() * : sp_*() * ---------------------------------------------------------------------------- version 10.1 mata: mata clear mata set matastrict on //***************************************************************************** //* spgridClip() * //* --> sp_dots1() * //* --> sp_dots2() * //* --> sp_pips() * //***************************************************************************** void spgridClip(real scalar nc, real scalar np, string scalar dots) { /* Setup */ real scalar c, x, y, p, xmin, xmax, ymin, ymax, fp, lp real matrix POLY /* Scan grid cells */ if (dots != "") sp_dots1("Grid cells", nc) for (c=1; c<=nc; c++) { if (dots != "") sp_dots2(c, nc) if (_st_data(c, 6) == 1) { x = _st_data(c, 4) y = _st_data(c, 5) for (p=1; p<=np; p++) { xmin = _st_data(p, 13) xmax = _st_data(p, 14) ymin = _st_data(p, 15) ymax = _st_data(p, 16) if (x>=xmin & x<=xmax & y>=ymin & y<=ymax) { fp = _st_data(p, 11) lp = _st_data(p, 12) POLY = st_data((fp,lp), (17,18)) if (sp_pips(x, y, POLY)) { st_store(c, 7, 1) st_store(c, 8, _st_data(p, 10)) break } } } } } } //***************************************************************************** //* spgridCells() * //***************************************************************************** void spgridCells(real scalar nc, real scalar nr, string scalar shape, real scalar x, real scalar y) { /* Setup */ real scalar r, i real colvector ID, X, Y /* Generate working objects */ st_view(ID=., (1,nr), 4) st_view( X=., (1,nr), 5) st_view( Y=., (1,nr), 6) /* Generate grid cells */ r = 1 for (i=1; i<=nc; i++) { if (shape == "square") { /* Dummy record */ ID[r] = _st_data(i, 1) r++ /* South-West */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) - x Y[r] = _st_data(i, 3) - y r++ /* North-West */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) - x Y[r] = _st_data(i, 3) + y r++ /* North-East */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) + x Y[r] = _st_data(i, 3) + y r++ /* South-East */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) + x Y[r] = _st_data(i, 3) - y r++ /* South-West */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) - x Y[r] = _st_data(i, 3) - y r++ } if (shape == "hexagonal") { /* Dummy record */ ID[r] = _st_data(i, 1) r++ /* South */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) Y[r] = _st_data(i, 3) - y * 2 r++ /* South-West */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) - x Y[r] = _st_data(i, 3) - y r++ /* North-West */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) - x Y[r] = _st_data(i, 3) + y r++ /* North */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) Y[r] = _st_data(i, 3) + y * 2 r++ /* North-East */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) + x Y[r] = _st_data(i, 3) + y r++ /* South-East */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) + x Y[r] = _st_data(i, 3) - y r++ /* South */ ID[r] = _st_data(i, 1) X[r] = _st_data(i, 2) Y[r] = _st_data(i, 3) - y * 2 r++ } } } //***************************************************************************** //* sp_*() - Library of Mata functions for spatial data analysis * //* * //* : sp_dots1 * //* : sp_dots2 * //* : sp_pips * //***************************************************************************** /** 200808 *******************************************************************/ /* */ /* sp_dots1 */ /* */ /* Displays header & ruler of dots-type verbose output */ /* */ /*****************************************************************************/ void sp_dots1(string scalar header, real scalar n) { printf("{txt}%s (", header) printf("{res}%g{txt})\n", n) printf("{txt}{hline 4}{c +}{hline 3} 1 {hline 3}{c +}{hline 3} 2 ") printf("{txt}{hline 3}{c +}{hline 3} 3 {hline 3}{c +}{hline 3} 4 ") printf("{txt}{hline 3}{c +}{hline 3} 5\n") } /** 200808 *******************************************************************/ /* */ /* sp_dots2 */ /* */ /* Displays iterations of dots-type verbose output */ /* */ /*****************************************************************************/ void sp_dots2(real scalar i, real scalar n) { /* Setup */ real scalar linenum /* Display */ linenum = mod(i,50) if (linenum != 0 & i < n) { printf("{txt}.") } if (linenum == 0 & i < n) { printf("{txt}. %5.0f\n", i) } if (i == n) { printf("{txt}.\n") printf("\n") } } /** 200808 *******************************************************************/ /* */ /* sp_pips */ /* */ /* Returns a scalar indicating whether the point defined by the coordinate */ /* pair (x,y) lies within (value 1) or without (value 0) the polygon */ /* defined by the R-by-2 coordinate matrix POLY */ /* */ /*****************************************************************************/ real scalar sp_pips(real scalar x, real scalar y, real matrix POLY) { /* Setup */ real scalar pip, nv, iwind, xlastp, ylastp, ioldq, inewq real scalar xthisp, ythisp, i, a, b /* Generate working objects */ nv = rows(POLY) if (POLY[1,1] == POLY[nv,1] & POLY[1,2] == POLY[nv,2]) nv = nv - 1 iwind = 0 xlastp = POLY[nv, 1] ylastp = POLY[nv, 2] ioldq = sp_pips_aux(xlastp, ylastp, x, y) /* Check and report point status */ for (i=1; i<=nv; i++) { xthisp = POLY[i, 1] ythisp = POLY[i, 2] inewq = sp_pips_aux(xthisp, ythisp, x, y) if (ioldq != inewq) { if (mod(ioldq+1, 4) == inewq) { iwind = iwind + 1 } else if (mod(inewq+1, 4) == ioldq) { iwind = iwind - 1 } else { a = (ylastp - ythisp) * (x - xlastp) b = xlastp - xthisp a = a + ylastp * b b = b * y if (a > b) { iwind = iwind + 2 } else { iwind = iwind - 2 } } } xlastp = xthisp ylastp = ythisp ioldq = inewq } pip = abs(iwind / 4) /* Returns results */ return(pip) } /* Auxiliary function */ real scalar sp_pips_aux(real scalar xp, real scalar yp, real scalar xo, real scalar yo) { real scalar iq if(xp < xo) { if(yp < yo) iq = 2 else iq = 1 } else { if(yp < yo) iq = 3 else iq = 0 } return(iq) } //***************************************************************************** //* Exit Mata * //***************************************************************************** end