|
| 1 | +/****************************************************************************** |
| 2 | + * |
| 3 | + * Project: MapServer |
| 4 | + * Purpose: KernelDensity layer implementation and related functions. |
| 5 | + * Author: Thomas Bonfort and the MapServer team. |
| 6 | + * |
| 7 | + ****************************************************************************** |
| 8 | + * Copyright (c) 2014 Regents of the University of Minnesota. |
| 9 | + * |
| 10 | + * Permission is hereby granted, free of charge, to any person obtaining a |
| 11 | + * copy of this software and associated documentation files (the "Software"), |
| 12 | + * to deal in the Software without restriction, including without limitation |
| 13 | + * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| 14 | + * and/or sell copies of the Software, and to permit persons to whom the |
| 15 | + * Software is furnished to do so, subject to the following conditions: |
| 16 | + * |
| 17 | + * The above copyright notice and this permission notice shall be included in |
| 18 | + * all copies of this Software or works derived from this Software. |
| 19 | + * |
| 20 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 21 | + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 22 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| 23 | + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 24 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 25 | + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| 26 | + * DEALINGS IN THE SOFTWARE. |
| 27 | + *****************************************************************************/ |
| 28 | + |
| 29 | +#include "mapserver.h" |
| 30 | +#include <float.h> |
| 31 | +#ifdef USE_GDAL |
| 32 | + |
| 33 | +#include "gdal.h" |
| 34 | + |
| 35 | + |
| 36 | +void gaussian_blur(float *values, int width, int height, int radius) { |
| 37 | + float *tmp = (float*)msSmallMalloc(width*height*sizeof(float)); |
| 38 | + int length = radius*2+1; |
| 39 | + float *kernel = (float*)msSmallMalloc(length*sizeof(float)); |
| 40 | + float sigma=radius/3.0; |
| 41 | + float a=1.0/ sqrt(2.0*M_PI*sigma*sigma); |
| 42 | + float den=2.0*sigma*sigma; |
| 43 | + int i,x,y; |
| 44 | + |
| 45 | + for (i=0; i<length; i++) { |
| 46 | + float x=i - radius; |
| 47 | + float v=a * exp(-(x*x) / den); |
| 48 | + kernel[i]=v; |
| 49 | + } |
| 50 | + memset(tmp,0,width*height*sizeof(float)); |
| 51 | + |
| 52 | + for(y=0; y<height; y++) { |
| 53 | + float* src_row=values + width*y; |
| 54 | + float* dst_row=tmp + width*y; |
| 55 | + |
| 56 | + for(x=radius; x<width-radius; x++) { |
| 57 | + float accum=0; |
| 58 | + for(i=0; i<length; i++) { |
| 59 | + accum+=src_row[x+i-radius] * kernel[i]; |
| 60 | + } |
| 61 | + dst_row[x]=accum; |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + for(x=0; x<width; x++) { |
| 66 | + float* src_col=tmp+x; |
| 67 | + float* dst_col=values+x; |
| 68 | + |
| 69 | + for(y=radius; y<height-radius; y++) { |
| 70 | + float accum=0; |
| 71 | + for (i=0; i<length; i++) { |
| 72 | + accum+=src_col[width*(y+i-radius)] * kernel[i]; |
| 73 | + } |
| 74 | + dst_col[y*width]=accum; |
| 75 | + } |
| 76 | + } |
| 77 | + free(tmp); |
| 78 | + free(kernel); |
| 79 | +} |
| 80 | + |
| 81 | + |
| 82 | +int msComputeKernelDensityDataset(mapObj *map, imageObj *image, layerObj *kerneldensity_layer, void **hDSvoid, void **cleanup_ptr) { |
| 83 | + |
| 84 | + int status,layer_idx, i,j, nclasses=0, have_sample=0; |
| 85 | + rectObj searchrect; |
| 86 | + shapeObj shape; |
| 87 | + layerObj *layer; |
| 88 | + float *values; |
| 89 | + int radius = 10, im_width = image->width, im_height = image->height; |
| 90 | + int expand_searchrect=1; |
| 91 | + float normalization_scale=0.0; |
| 92 | + double invcellsize = 1.0 / map->cellsize, georadius=0; |
| 93 | + float valmax=FLT_MIN, valmin=FLT_MAX; |
| 94 | + unsigned char *iValues; |
| 95 | + GDALDatasetH hDS; |
| 96 | + const char *pszProcessing; |
| 97 | + int *classgroup = NULL; |
| 98 | + |
| 99 | + assert(kerneldensity_layer->connectiontype == MS_KERNELDENSITY); |
| 100 | + *cleanup_ptr = NULL; |
| 101 | + |
| 102 | + if(!kerneldensity_layer->connection || !*kerneldensity_layer->connection) { |
| 103 | + msSetError(MS_MISCERR, "msComputeKernelDensityDataset()", "KernelDensity layer has no CONNECTION defined"); |
| 104 | + return MS_FAILURE; |
| 105 | + } |
| 106 | + pszProcessing = msLayerGetProcessingKey( kerneldensity_layer, "KERNELDENSITY_RADIUS" ); |
| 107 | + if(pszProcessing) |
| 108 | + radius = atoi(pszProcessing); |
| 109 | + else |
| 110 | + radius = 10; |
| 111 | + |
| 112 | + pszProcessing = msLayerGetProcessingKey( kerneldensity_layer, "KERNELDENSITY_COMPUTE_BORDERS" ); |
| 113 | + if(pszProcessing && strcasecmp(pszProcessing,"OFF")) |
| 114 | + expand_searchrect = 1; |
| 115 | + else |
| 116 | + expand_searchrect = 0; |
| 117 | + |
| 118 | + pszProcessing = msLayerGetProcessingKey( kerneldensity_layer, "KERNELDENSITY_NORMALIZATION" ); |
| 119 | + if(!pszProcessing || !strcasecmp(pszProcessing,"AUTO")) |
| 120 | + normalization_scale = 0.0; |
| 121 | + else { |
| 122 | + normalization_scale = atof(pszProcessing); |
| 123 | + if(normalization_scale != 0) { |
| 124 | + normalization_scale = 1.0 / normalization_scale; |
| 125 | + } else { |
| 126 | + normalization_scale = 1.0; |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + layer_idx = msGetLayerIndex(map,kerneldensity_layer->connection); |
| 131 | + if(layer_idx == -1) { |
| 132 | + int nLayers, *aLayers; |
| 133 | + aLayers = msGetLayersIndexByGroup(map, kerneldensity_layer->connection, &nLayers); |
| 134 | + if(!aLayers || !nLayers) { |
| 135 | + msSetError(MS_MISCERR, "KernelDensity layer (%s) references unknown layer (%s)", "msComputeKernelDensityDataset()", |
| 136 | + kerneldensity_layer->name,kerneldensity_layer->connection); |
| 137 | + return (MS_FAILURE); |
| 138 | + } |
| 139 | + for(i=0; i<nLayers; i++) { |
| 140 | + layer_idx = aLayers[i]; |
| 141 | + layer = GET_LAYER(map, layer_idx); |
| 142 | + if(msScaleInBounds(map->scaledenom, layer->minscaledenom, layer->maxscaledenom)) |
| 143 | + break; |
| 144 | + } |
| 145 | + free(aLayers); |
| 146 | + if(i == nLayers) { |
| 147 | + msSetError(MS_MISCERR, "KernelDensity layer (%s) references no layer for current scale", "msComputeKernelDensityDataset()", |
| 148 | + kerneldensity_layer->name); |
| 149 | + return (MS_FAILURE); |
| 150 | + } |
| 151 | + } else { |
| 152 | + layer = GET_LAYER(map, layer_idx); |
| 153 | + } |
| 154 | + /* open the linked layer */ |
| 155 | + status = msLayerOpen(layer); |
| 156 | + if(status != MS_SUCCESS) return MS_FAILURE; |
| 157 | + |
| 158 | + status = msLayerWhichItems(layer, MS_FALSE, NULL); |
| 159 | + if(status != MS_SUCCESS) { |
| 160 | + msLayerClose(layer); |
| 161 | + return MS_FAILURE; |
| 162 | + } |
| 163 | + |
| 164 | + /* identify target shapes */ |
| 165 | + if(layer->transform == MS_TRUE) { |
| 166 | + searchrect = map->extent; |
| 167 | + if(expand_searchrect) { |
| 168 | + georadius = radius * map->cellsize; |
| 169 | + searchrect.minx -= georadius; |
| 170 | + searchrect.miny -= georadius; |
| 171 | + searchrect.maxx += georadius; |
| 172 | + searchrect.maxy += georadius; |
| 173 | + im_width += 2 * radius; |
| 174 | + im_height += 2 * radius; |
| 175 | + } |
| 176 | + } |
| 177 | + else { |
| 178 | + searchrect.minx = searchrect.miny = 0; |
| 179 | + searchrect.maxx = map->width-1; |
| 180 | + searchrect.maxy = map->height-1; |
| 181 | + } |
| 182 | + |
| 183 | +#ifdef USE_PROJ |
| 184 | + layer->project = msProjectionsDiffer(&(layer->projection), &(map->projection)); |
| 185 | + if(layer->project) |
| 186 | + msProjectRect(&map->projection, &layer->projection, &searchrect); /* project the searchrect to source coords */ |
| 187 | +#endif |
| 188 | + |
| 189 | + status = msLayerWhichShapes(layer, searchrect, MS_FALSE); |
| 190 | + if(status == MS_DONE) { /* no overlap */ |
| 191 | + msLayerClose(layer); |
| 192 | + return MS_SUCCESS; |
| 193 | + } else if(status != MS_SUCCESS) { |
| 194 | + msLayerClose(layer); |
| 195 | + return MS_FAILURE; |
| 196 | + } |
| 197 | + values = (float*) msSmallCalloc(im_width * im_height, sizeof(float)); |
| 198 | + |
| 199 | + if(layer->classgroup && layer->numclasses > 0) |
| 200 | + classgroup = msAllocateValidClassGroups(layer, &nclasses); |
| 201 | + |
| 202 | + msInitShape(&shape); |
| 203 | + while((status = msLayerNextShape(layer, &shape)) == MS_SUCCESS) { |
| 204 | + int l,p,s,c; |
| 205 | + double weight = 1.0; |
| 206 | +#ifdef USE_PROJ |
| 207 | + if(layer->project) |
| 208 | + msProjectShape(&layer->projection, &map->projection, &shape); |
| 209 | +#endif |
| 210 | + |
| 211 | + /* the weight for the sample is set to 1.0 by default. If the |
| 212 | + * layer has some classes defined, we will read the weight from |
| 213 | + * the class->style->size (which can be binded to an attribute) |
| 214 | + */ |
| 215 | + if(layer->numclasses > 0) { |
| 216 | + c = msShapeGetClass(layer, map, &shape, classgroup, nclasses); |
| 217 | + if((c == -1) || (layer->class[c]->status == MS_OFF)) { |
| 218 | + goto nextshape; /* no class matched, skip */ |
| 219 | + } |
| 220 | + for (s = 0; s < layer->class[c]->numstyles; s++) { |
| 221 | + if (msScaleInBounds(map->scaledenom, |
| 222 | + layer->class[c]->styles[s]->minscaledenom, |
| 223 | + layer->class[c]->styles[s]->maxscaledenom)) { |
| 224 | + if(layer->class[c]->styles[s]->bindings[MS_STYLE_BINDING_SIZE].index != -1) { |
| 225 | + weight = atof(shape.values[layer->class[c]->styles[s]->bindings[MS_STYLE_BINDING_SIZE].index]); |
| 226 | + } else { |
| 227 | + weight = layer->class[c]->styles[s]->size; |
| 228 | + } |
| 229 | + break; |
| 230 | + } |
| 231 | + } |
| 232 | + if(s == layer->class[c]->numstyles) { |
| 233 | + /* no style in scale bounds */ |
| 234 | + goto nextshape; |
| 235 | + } |
| 236 | + } |
| 237 | + for(l=0; l<shape.numlines; l++) { |
| 238 | + for(p=0; p<shape.line[l].numpoints; p++) { |
| 239 | + int x = MS_MAP2IMAGE_XCELL_IC(shape.line[l].point[p].x, map->extent.minx - georadius, invcellsize); |
| 240 | + int y = MS_MAP2IMAGE_YCELL_IC(shape.line[l].point[p].y, map->extent.maxy + georadius, invcellsize); |
| 241 | + if(x>=0 && y>=0 && x<im_width && y<im_height) { |
| 242 | + float *value = values + y * im_width + x; |
| 243 | + (*value) += weight; |
| 244 | + have_sample = 1; |
| 245 | + } |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | +nextshape: |
| 250 | + msFreeShape(&shape); |
| 251 | + } |
| 252 | + msLayerClose(layer); |
| 253 | + if(status == MS_DONE) { |
| 254 | + status = MS_SUCCESS; |
| 255 | + } else { |
| 256 | + status = MS_FAILURE; |
| 257 | + } |
| 258 | + |
| 259 | + if(have_sample) { /* no use applying the filtering kernel if we have no samples */ |
| 260 | + gaussian_blur(values,im_width, im_height, radius); |
| 261 | + |
| 262 | + if(normalization_scale == 0.0) { /* auto normalization */ |
| 263 | + for (j=radius; j<im_height-radius; j++) { |
| 264 | + for (i=radius; i<im_width-radius; i++) { |
| 265 | + float val = values[j*im_width + i]; |
| 266 | + if(val >0 && val>valmax) { |
| 267 | + valmax = val; |
| 268 | + } |
| 269 | + if(val>0 && val<valmin) { |
| 270 | + valmin = val; |
| 271 | + } |
| 272 | + } |
| 273 | + } |
| 274 | + } else { |
| 275 | + valmin = 0; |
| 276 | + valmax = normalization_scale; |
| 277 | + } |
| 278 | + } |
| 279 | + |
| 280 | + |
| 281 | + if(have_sample && expand_searchrect) { |
| 282 | + iValues = msSmallMalloc(image->width*image->height*sizeof(unsigned char)); |
| 283 | + for (j=0; j<image->height; j++) { |
| 284 | + for (i=0; i<image->width; i++) { |
| 285 | + float norm=(values[(j+radius)*im_width + i + radius] - valmin) / valmax; |
| 286 | + int v=255 * norm; |
| 287 | + if (v<0) v=0; |
| 288 | + else if (v>255) v=255; |
| 289 | + iValues[j*image->width + i] = v; |
| 290 | + } |
| 291 | + } |
| 292 | + } else { |
| 293 | + iValues = msSmallCalloc(1,image->width*image->height*sizeof(unsigned char)); |
| 294 | + if(have_sample) { |
| 295 | + for (j=radius; j<image->height-radius; j++) { |
| 296 | + for (i=radius; i<image->width-radius; i++) { |
| 297 | + float norm=(values[j*im_width + i] - valmin) / valmax; |
| 298 | + int v=255 * norm; |
| 299 | + if (v<0) v=0; |
| 300 | + else if (v>255) v=255; |
| 301 | + iValues[j*image->width + i]=v; |
| 302 | + } |
| 303 | + } |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + free(values); |
| 308 | + |
| 309 | + { |
| 310 | + char ds_string [1024]; |
| 311 | + double adfGeoTransform[6]; |
| 312 | + snprintf(ds_string,1024,"MEM:::DATAPOINTER=%p,PIXELS=%u,LINES=%u,BANDS=1,DATATYPE=Byte,PIXELOFFSET=1,LINEOFFSET=%u", |
| 313 | + iValues,image->width,image->height,image->width); |
| 314 | + hDS = GDALOpenShared( ds_string, GA_ReadOnly ); |
| 315 | + if(hDS==NULL) { |
| 316 | + msSetError(MS_MISCERR,"msComputeKernelDensityDataset()","failed to create in-memory gdal dataset for interpolated data"); |
| 317 | + status = MS_FAILURE; |
| 318 | + free(iValues); |
| 319 | + } |
| 320 | + adfGeoTransform[0] = map->extent.minx - map->cellsize * 0.5; /* top left x */ |
| 321 | + adfGeoTransform[1] = map->cellsize;/* w-e pixel resolution */ |
| 322 | + adfGeoTransform[2] = 0; /* 0 */ |
| 323 | + adfGeoTransform[3] = map->extent.maxy + map->cellsize * 0.5;/* top left y */ |
| 324 | + adfGeoTransform[4] =0; /* 0 */ |
| 325 | + adfGeoTransform[5] = -map->cellsize;/* n-s pixel resolution (negative value) */ |
| 326 | + GDALSetGeoTransform(hDS,adfGeoTransform); |
| 327 | + *hDSvoid = hDS; |
| 328 | + *cleanup_ptr = (void*)iValues; |
| 329 | + } |
| 330 | + return status; |
| 331 | +} |
| 332 | +#else |
| 333 | + |
| 334 | + |
| 335 | +int msComputeKernelDensityDataset(mapObj *map, imageObj *image, layerObj *layer, void **hDSvoid, void **cleanup_ptr) { |
| 336 | + msSetError(MS_MISCERR,"msComputeKernelDensityDataset()", "KernelDensity layers require GDAL support, however GDAL support is not compiled in this build"); |
| 337 | + return MS_FAILURE; |
| 338 | +} |
| 339 | + |
| 340 | +#endif |
| 341 | + |
| 342 | +int msCleanupKernelDensityDataset(mapObj *map, imageObj *image, layerObj *layer, void *cleanup_ptr) { |
| 343 | + free(cleanup_ptr); |
| 344 | + return MS_SUCCESS; |
| 345 | +} |
0 commit comments