From c019a2309e81e01800fdc3ea2e1d913c8e1bdb51 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 3 May 2014 01:15:39 +1000 Subject: [PATCH 01/38] Stop points bleeding into pixels outside of radius Put in a test to break early from the density calculations when dist>radius. Stops the contribution of a point to pixels outside of it's radius, an extreme version of this is possibility of having very visible squares in the heatmap depending on radius and pixVal calculation constants. --- heatmap/heatmap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/heatmap/heatmap.c b/heatmap/heatmap.c index f85e19a..8238349 100644 --- a/heatmap/heatmap.c +++ b/heatmap/heatmap.c @@ -123,6 +123,8 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints) if (j < 0 || k < 0 || j >= width || k >= height) continue; dist = sqrt( (j-pt.x)*(j-pt.x) + (k-pt.y)*(k-pt.y) ); + + if(dist>radius) continue; // stop point contributing to pixels outside its radius pixVal = (int)(200.0*(dist/radius)+50.0); if (pixVal > 255) pixVal = 255; From 8267ff4c7eb58d159f00e28dfe157e1c1068418c Mon Sep 17 00:00:00 2001 From: kwauchope Date: Mon, 28 Jul 2014 21:29:01 +1000 Subject: [PATCH 02/38] added weight functionality --- heatmap/heatmap.c | 33 ++++++++++++++++++++++++--------- heatmap/heatmap.py | 21 ++++++++++++++------- test/tests.py | 13 +++++++++++++ 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/heatmap/heatmap.c b/heatmap/heatmap.c index 8238349..7af4ea5 100644 --- a/heatmap/heatmap.c +++ b/heatmap/heatmap.c @@ -3,6 +3,9 @@ #include #include +float constant = 50.0; +float multiplier = 200.0; + struct info { float minX; @@ -33,7 +36,7 @@ BOOL WINAPI DllMain( HINSTANCE hinstDLL, // handle to DLL module #endif //walk the list of points, get the boundary values -void getBounds(struct info *inf, float *points, unsigned int cPoints) +void getBounds(struct info *inf, float *points, unsigned int cPoints, int weighted) { unsigned int i = 0; @@ -43,8 +46,11 @@ void getBounds(struct info *inf, float *points, unsigned int cPoints) float maxX = points[i]; float maxY = points[i+1]; + int inc = 2; + if (weighted) inc = 3; + //then iterate over the list and find the max/min values - for(i = 0; i < cPoints; i=i+2) + for(i = 0; i < cPoints; i=i+inc) { float x = points[i]; float y = points[i+1]; @@ -86,7 +92,7 @@ struct point translate(struct info *inf, struct point pt) return pt; } -unsigned char* calcDensity(struct info *inf, float *points, int cPoints) +unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int weighted) { int width = inf->width; int height = inf->height; @@ -110,7 +116,10 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints) pixels[i] = 0xff; } - for(i = 0; i < cPoints; i=i+2) + int inc = 2; + if (weighted) inc = 3; + + for(i = 0; i < cPoints; i=i+inc) { pt.x = points[i]; pt.y = points[i+1]; @@ -126,7 +135,14 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints) if(dist>radius) continue; // stop point contributing to pixels outside its radius - pixVal = (int)(200.0*(dist/radius)+50.0); + if (weighted) + { + pixVal = (int)((multiplier*(dist/radius)+constant)/points[i+2]); + } + else + { + pixVal = (int)(multiplier*(dist/radius)+constant); + } if (pixVal > 255) pixVal = 255; ndx = k*width + j; @@ -149,7 +165,6 @@ unsigned char *colorize(struct info *inf, unsigned char* pixels_bw, int *scheme, { int width = inf->width; int height = inf->height; - int dotsize = inf->dotsize; int i = 0; int pix = 0; @@ -192,7 +207,7 @@ unsigned char *tx(float *points, unsigned char *pix_color, int opacity, int boundsOverride, - float minX, float minY, float maxX, float maxY) + float minX, float minY, float maxX, float maxY, int weighted) { unsigned char *pixels_bw = NULL; @@ -218,7 +233,7 @@ unsigned char *tx(float *points, } else { - getBounds(&inf, points, cPoints); + getBounds(&inf, points, cPoints, weighted); } #ifdef DEBUG @@ -227,7 +242,7 @@ unsigned char *tx(float *points, //iterate through points, place a dot at each center point //and set pix value from 0 - 255 using multiply method for radius [dotsize]. - pixels_bw = calcDensity(&inf, points, cPoints); + pixels_bw = calcDensity(&inf, points, cPoints, weighted); //using provided color scheme and opacity, update pixel value to RGBA values pix_color = colorize(&inf, pixels_bw, scheme, pix_color, opacity); diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 173e5cc..f80a641 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -75,7 +75,7 @@ def __init__(self, libpath=None): if not self._heatmap: raise Exception("Heatmap shared library not found in PYTHONPATH.") - def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None): + def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None, weighted=0): """ points -> an iterable list of tuples, where the contents are the x,y coordinates to plot. e.g., [(1, 1), (2, 2), (3, 3)] @@ -90,6 +90,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c area -> Specify bounding coordinates of the output image. Tuple of tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, these values are calculated based on the input data. + weighted -> Is the data weighted """ self.dotsize = dotsize self.opacity = opacity @@ -108,16 +109,16 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c scheme, self.schemes()) raise Exception(tmp) - arrPoints = self._convertPoints(points) + arrPoints = self._convertPoints(points,weighted) arrScheme = self._convertScheme(scheme) arrFinalImage = self._allocOutputBuffer() ret = self._heatmap.tx( - arrPoints, len(points) * 2, size[0], size[1], dotsize, + arrPoints, len(arrPoints), size[0], size[1], dotsize, arrScheme, arrFinalImage, opacity, self.override, ctypes.c_float(self.area[0][0]), ctypes.c_float( self.area[0][1]), - ctypes.c_float(self.area[1][0]), ctypes.c_float(self.area[1][1])) + ctypes.c_float(self.area[1][0]), ctypes.c_float(self.area[1][1]), weighted) if not ret: raise Exception("Unexpected error during processing.") @@ -129,16 +130,22 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c def _allocOutputBuffer(self): return (ctypes.c_ubyte * (self.size[0] * self.size[1] * 4))() - def _convertPoints(self, pts): + def _convertPoints(self, pts, weighted): """ flatten the list of tuples, convert into ctypes array """ #TODO is there a better way to do this?? flat = [] - for i, j in pts: + if (weighted): + for i, j, k in pts: + flat.append(i) + flat.append(j) + flat.append(k) + else: + for i, j in pts: flat.append(i) flat.append(j) #build array of input points - arr_pts = (ctypes.c_float * (len(pts) * 2))(*flat) + arr_pts = (ctypes.c_float * (len(flat))) (*flat) return arr_pts def _convertScheme(self, scheme): diff --git a/test/tests.py b/test/tests.py index 8720d38..5c75941 100644 --- a/test/tests.py +++ b/test/tests.py @@ -60,6 +60,19 @@ def test_heatmap_single_point(self): def test_invalid_heatmap(self): self.assertRaises(Exception, self.heatmap.heatmap, ([],)) + def test_heatmap_weighted(self): + pts = [(random.random(), random.random()) for x in range(400)] + # this should also generate a warning on stderr of overly dense + img = self.heatmap.heatmap(pts) + img.save("07-400-normal.png") + self.assertTrue(isinstance(img, Image.Image)) + img = self.heatmap.heatmap(map( lambda (x,y) : (x,y,1), pts), weighted=1) + img.save("07-400-100percent.png") + self.assertTrue(isinstance(img, Image.Image)) + img = self.heatmap.heatmap(map( lambda (x,y) : (x,y,.75), pts), weighted=1) + img.save("07-400-75percent.png") + self.assertTrue(isinstance(img, Image.Image)) + class TestColorScheme(unittest.TestCase): def test_schemes(self): keys = colorschemes.valid_schemes() From 52204d6a2ad03e794fd58ea9b6d9789f18939179 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 29 Jul 2014 00:26:29 +1000 Subject: [PATCH 03/38] added option to input flat array so no flattenign required --- heatmap/heatmap.py | 20 +++++++++++--------- test/tests.py | 9 +++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index f80a641..7e3ea3a 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -133,17 +133,19 @@ def _allocOutputBuffer(self): def _convertPoints(self, pts, weighted): """ flatten the list of tuples, convert into ctypes array """ - #TODO is there a better way to do this?? flat = [] - if (weighted): - for i, j, k in pts: - flat.append(i) - flat.append(j) - flat.append(k) + if isinstance(pts[0],tuple) or isinstance(pts[0],list): + if (weighted): + for i, j, k in pts: + flat.append(i) + flat.append(j) + flat.append(k) + else: + for i, j in pts: + flat.append(i) + flat.append(j) else: - for i, j in pts: - flat.append(i) - flat.append(j) + flat = pts #build array of input points arr_pts = (ctypes.c_float * (len(flat))) (*flat) return arr_pts diff --git a/test/tests.py b/test/tests.py index 5c75941..cb4b8af 100644 --- a/test/tests.py +++ b/test/tests.py @@ -73,6 +73,15 @@ def test_heatmap_weighted(self): img.save("07-400-75percent.png") self.assertTrue(isinstance(img, Image.Image)) + def test_heatmap_random_flat(self): + pts = [(random.random(),random.random()) for x in range(400)] + img = self.heatmap.heatmap(pts) + img.save("08-400-normal.png") + self.assertTrue(isinstance(img, Image.Image)) + img = self.heatmap.heatmap(sum(pts,())) + img.save("08-400-flat.png") + self.assertTrue(isinstance(img, Image.Image)) + class TestColorScheme(unittest.TestCase): def test_schemes(self): keys = colorschemes.valid_schemes() From 1eaf322df4131960196adc9fdfac05abc48aff59 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 2 Aug 2014 00:05:55 +1000 Subject: [PATCH 04/38] Update setup.py Fix pip install error --- setup.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4ed87d7..988a610 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,22 @@ import os import glob -from distutils.core import setup, Extension -from distutils.command.install import install -from distutils.command.build_ext import build_ext +with_setuptools = False +if 'USE_SETUPTOOLS' in os.environ or 'pip' in __file__: + try: + from setuptools.command.install import install + from setuptools import setup + from setuptools import Extension + from setuptools.command.build_ext import build_ext + with_setuptools = True + except: + with_setuptools = False + +if with_setuptools is False: + from distutils.command.install import install + from distutils.core import setup + from distutils.core import Extension + from distutils.command.build_ext import build_ext # sorry for this, welcome feedback on the "right" way. # shipping pre-compiled bainries on windows, have From 7a93ae1b588112c22409604c1459d1cf6666ca26 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sun, 3 Aug 2014 14:26:25 +1000 Subject: [PATCH 05/38] fixed error with weights and _range, only found when use KML and weights --- heatmap/heatmap.py | 24 ++++++++++++++++-------- test/tests.py | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 7e3ea3a..135ff94 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -96,6 +96,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.opacity = opacity self.size = size self.points = points + self.weighted = weighted if area is not None: self.area = area @@ -109,7 +110,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c scheme, self.schemes()) raise Exception(tmp) - arrPoints = self._convertPoints(points,weighted) + arrPoints = self._convertPoints(points) arrScheme = self._convertScheme(scheme) arrFinalImage = self._allocOutputBuffer() @@ -130,12 +131,12 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c def _allocOutputBuffer(self): return (ctypes.c_ubyte * (self.size[0] * self.size[1] * 4))() - def _convertPoints(self, pts, weighted): + def _convertPoints(self, pts): """ flatten the list of tuples, convert into ctypes array """ flat = [] if isinstance(pts[0],tuple) or isinstance(pts[0],list): - if (weighted): + if (self.weighted): for i, j, k in pts: flat.append(i) flat.append(j) @@ -170,11 +171,18 @@ def _ranges(self, points): minY = points[0][1] maxX = minX maxY = minY - for x, y in points: - minX = min(x, minX) - minY = min(y, minY) - maxX = max(x, maxX) - maxY = max(y, maxY) + if not self.weighted: + for x, y in points: + minX = min(x, minX) + minY = min(y, minY) + maxX = max(x, maxX) + maxY = max(y, maxY) + else: + for x, y, z in points: + minX = min(x, minX) + minY = min(y, minY) + maxX = max(x, maxX) + maxY = max(y, maxY) return ((minX, minY), (maxX, maxY)) diff --git a/test/tests.py b/test/tests.py index cb4b8af..e148892 100644 --- a/test/tests.py +++ b/test/tests.py @@ -65,6 +65,7 @@ def test_heatmap_weighted(self): # this should also generate a warning on stderr of overly dense img = self.heatmap.heatmap(pts) img.save("07-400-normal.png") + self.heatmap.saveKML("07-400-normal.kml") self.assertTrue(isinstance(img, Image.Image)) img = self.heatmap.heatmap(map( lambda (x,y) : (x,y,1), pts), weighted=1) img.save("07-400-100percent.png") From d1e241c54a38c819554c0f32cfb9b74a5c1d559f Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sun, 3 Aug 2014 22:13:00 +1000 Subject: [PATCH 06/38] fixed issue of other data types with _ranges, only visible when use KML --- heatmap/heatmap.py | 32 ++++++++++++++++++++++++-------- test/tests.py | 18 ++++++++++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 135ff94..9568be2 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -167,22 +167,38 @@ def _convertScheme(self, scheme): def _ranges(self, points): """ walks the list of points and finds the max/min x & y values in the set """ - minX = points[0][0] - minY = points[0][1] - maxX = minX - maxY = minY - if not self.weighted: - for x, y in points: + if isinstance(points[0],tuple) or isinstance(points[0],list): + if self.weighted: + (minX,minY,z) = points[0] + maxX = minX + maxY = minY + for x, y, z in points: minX = min(x, minX) minY = min(y, minY) maxX = max(x, maxX) maxY = max(y, maxY) - else: - for x, y, z in points: + else: + (minX,minY) = points[0] + maxX = minX + maxY = minY + for x, y in points: minX = min(x, minX) minY = min(y, minY) maxX = max(x, maxX) maxY = max(y, maxY) + else: + minX = points[0] + minY = points[1] + maxX = minX + maxY = minY + inc = 2 + if self.weighted: + inc = 3 + for i in range(0,len(points),inc): + minX = min(points[i], minX) + minY = min(points[i+1], minY) + maxX = max(points[i], maxX) + maxY = max(points[i+1], maxY) return ((minX, minY), (maxX, maxY)) diff --git a/test/tests.py b/test/tests.py index e148892..1bcf449 100644 --- a/test/tests.py +++ b/test/tests.py @@ -70,18 +70,28 @@ def test_heatmap_weighted(self): img = self.heatmap.heatmap(map( lambda (x,y) : (x,y,1), pts), weighted=1) img.save("07-400-100percent.png") self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("07-400-100percent.kml") img = self.heatmap.heatmap(map( lambda (x,y) : (x,y,.75), pts), weighted=1) img.save("07-400-75percent.png") self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("07-400-75percent.kml") - def test_heatmap_random_flat(self): + def test_heatmap_random_datatypes(self): + pts = [random.random() for x in range(800)] + img = self.heatmap.heatmap(pts) + img.save("08-400-array.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-array.kml") pts = [(random.random(),random.random()) for x in range(400)] img = self.heatmap.heatmap(pts) - img.save("08-400-normal.png") + img.save("08-400-arrayoftuples.png") self.assertTrue(isinstance(img, Image.Image)) - img = self.heatmap.heatmap(sum(pts,())) - img.save("08-400-flat.png") + self.heatmap.saveKML("08-400-arrayoftuples.kml") + pts = [[random.random(),random.random()] for x in range(400)] + img = self.heatmap.heatmap(pts) + img.save("08-400-arrayofarrays.png") self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-arrayofarrays.kml") class TestColorScheme(unittest.TestCase): def test_schemes(self): From 4c0af003f95ae4ac0f0ec543095cc9bd101f8fbc Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sun, 3 Aug 2014 22:41:48 +1000 Subject: [PATCH 07/38] changed internal class storage of points to flat --- heatmap/heatmap.py | 61 ++++++++++++++++------------------------------ test/tests.py | 8 +++--- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 9568be2..938fc06 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -110,7 +110,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c scheme, self.schemes()) raise Exception(tmp) - arrPoints = self._convertPoints(points) + arrPoints = self._convertPoints() arrScheme = self._convertScheme(scheme) arrFinalImage = self._allocOutputBuffer() @@ -131,22 +131,23 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c def _allocOutputBuffer(self): return (ctypes.c_ubyte * (self.size[0] * self.size[1] * 4))() - def _convertPoints(self, pts): + def _convertPoints(self): """ flatten the list of tuples, convert into ctypes array """ flat = [] - if isinstance(pts[0],tuple) or isinstance(pts[0],list): + if isinstance(self.points[0],tuple) or isinstance(self.points[0],list): if (self.weighted): - for i, j, k in pts: + for i, j, k in self.points: flat.append(i) flat.append(j) flat.append(k) else: - for i, j in pts: + for i, j in self.points: flat.append(i) flat.append(j) + self.points = flat else: - flat = pts + flat = self.points #build array of input points arr_pts = (ctypes.c_float * (len(flat))) (*flat) return arr_pts @@ -164,41 +165,21 @@ def _convertScheme(self, scheme): ctypes.c_int * (len(colorschemes.schemes[scheme]) * 3))(*flat) return arr_cs - def _ranges(self, points): + def _ranges(self): """ walks the list of points and finds the max/min x & y values in the set """ - if isinstance(points[0],tuple) or isinstance(points[0],list): - if self.weighted: - (minX,minY,z) = points[0] - maxX = minX - maxY = minY - for x, y, z in points: - minX = min(x, minX) - minY = min(y, minY) - maxX = max(x, maxX) - maxY = max(y, maxY) - else: - (minX,minY) = points[0] - maxX = minX - maxY = minY - for x, y in points: - minX = min(x, minX) - minY = min(y, minY) - maxX = max(x, maxX) - maxY = max(y, maxY) - else: - minX = points[0] - minY = points[1] - maxX = minX - maxY = minY - inc = 2 - if self.weighted: - inc = 3 - for i in range(0,len(points),inc): - minX = min(points[i], minX) - minY = min(points[i+1], minY) - maxX = max(points[i], maxX) - maxY = max(points[i+1], maxY) + minX = self.points[0] + minY = self.points[1] + maxX = minX + maxY = minY + inc = 2 + if self.weighted: + inc = 3 + for i in range(0,len(self.points),inc): + minX = min(self.points[i], minX) + minY = min(self.points[i+1], minY) + maxX = max(self.points[i], maxX) + maxY = max(self.points[i+1], maxY) return ((minX, minY), (maxX, maxY)) @@ -219,7 +200,7 @@ def saveKML(self, kmlFile): if self.override: ((east, south), (west, north)) = self.area else: - ((east, south), (west, north)) = self._ranges(self.points) + ((east, south), (west, north)) = self._ranges() bytes = self.KML % (tilePath, north, south, east, west) file(kmlFile, "w").write(bytes) diff --git a/test/tests.py b/test/tests.py index 1bcf449..47659ee 100644 --- a/test/tests.py +++ b/test/tests.py @@ -77,17 +77,15 @@ def test_heatmap_weighted(self): self.heatmap.saveKML("07-400-75percent.kml") def test_heatmap_random_datatypes(self): - pts = [random.random() for x in range(800)] - img = self.heatmap.heatmap(pts) + pts = [[random.random(),random.random()] for x in range(400)] + img = self.heatmap.heatmap(sum(pts,[])) img.save("08-400-array.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-array.kml") - pts = [(random.random(),random.random()) for x in range(400)] - img = self.heatmap.heatmap(pts) + img = self.heatmap.heatmap(map(lambda (x,y) : (x,y), pts)) img.save("08-400-arrayoftuples.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayoftuples.kml") - pts = [[random.random(),random.random()] for x in range(400)] img = self.heatmap.heatmap(pts) img.save("08-400-arrayofarrays.png") self.assertTrue(isinstance(img, Image.Image)) From 0bcd40caafc4c5757431669dbdec21a9a67d736d Mon Sep 17 00:00:00 2001 From: kwauchope Date: Mon, 4 Aug 2014 02:15:13 +1000 Subject: [PATCH 08/38] fixed east and west mixed up which breaks KML, added area test which is how found it --- heatmap/heatmap.py | 4 ++-- test/tests.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 938fc06..dab10bf 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -198,9 +198,9 @@ def saveKML(self, kmlFile): self.img.save(tilePath) if self.override: - ((east, south), (west, north)) = self.area + ((west, south), (east, north)) = self.area else: - ((east, south), (west, north)) = self._ranges() + ((west, south), (east, north)) = self._ranges() bytes = self.KML % (tilePath, north, south, east, west) file(kmlFile, "w").write(bytes) diff --git a/test/tests.py b/test/tests.py index 47659ee..82c745a 100644 --- a/test/tests.py +++ b/test/tests.py @@ -91,6 +91,40 @@ def test_heatmap_random_datatypes(self): self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayofarrays.kml") + def test_heatmap_area(self): + MAX_SIZE=8192 + PPD=100 + dotsize=100 + pts = [[x*2,x, 1 if x==0 else 0.75] for x in range(-45,46)] + pts = sum(pts,[]) + west = pts[0] + south = pts[1] + east = west + north = south + for i in range(0,len(pts),3): + west = min(pts[i], west) + south = min(pts[i+1], south) + east = max(pts[i], east) + north = max(pts[i+1], north) + width = int((east - west)*PPD + dotsize/2) + height = int((north - south)*PPD + dotsize/2) + largestVal = max(width,height) + if largestVal > MAX_SIZE: + scale = float(MAX_SIZE)/largestVal + height = int(height*scale) + width = int(width*scale) + PPD = float((width-dotsize/2))/(east-west) + dotDegrees = dotsize/2/PPD + bounds = ((west-dotDegrees, south-dotDegrees),(east+dotDegrees,north+dotDegrees)) + img = self.heatmap.heatmap(pts, size = (width, height), dotsize = dotsize, area=bounds, weighted = 1) + img.save("11-400-areaTest.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("11-400-areaTest.kml") + img = self.heatmap.heatmap(pts, size = (width, height), dotsize = dotsize, weighted = 1) + img.save("11-400-areaTestNormal.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("11-400-areaTestNormal.kml") + class TestColorScheme(unittest.TestCase): def test_schemes(self): keys = colorschemes.valid_schemes() From b51c151e4bbe8ef9c2d2a93d315ee8d3fba077fb Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 29 Jul 2014 02:47:05 +1000 Subject: [PATCH 09/38] better crs support --- heatmap/heatmap.py | 27 ++++++++++++++++++++++++--- setup.py | 4 ++-- test/tests.py | 8 ++++---- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index dab10bf..11d66d9 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -3,6 +3,7 @@ import ctypes import platform import math +import pyproj import colorschemes @@ -75,7 +76,7 @@ def __init__(self, libpath=None): if not self._heatmap: raise Exception("Heatmap shared library not found in PYTHONPATH.") - def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None, weighted=0): + def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None, weighted=0, epsg='EPSG:4326'): """ points -> an iterable list of tuples, where the contents are the x,y coordinates to plot. e.g., [(1, 1), (2, 2), (3, 3)] @@ -91,12 +92,14 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, these values are calculated based on the input data. weighted -> Is the data weighted + epsg -> epsg code of the source """ self.dotsize = dotsize self.opacity = opacity self.size = size self.points = points self.weighted = weighted + self.epsg = epsg if area is not None: self.area = area @@ -110,7 +113,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c scheme, self.schemes()) raise Exception(tmp) - arrPoints = self._convertPoints() + arrPoints = self._convertPoints(weighted,epsg) arrScheme = self._convertScheme(scheme) arrFinalImage = self._allocOutputBuffer() @@ -131,7 +134,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c def _allocOutputBuffer(self): return (ctypes.c_ubyte * (self.size[0] * self.size[1] * 4))() - def _convertPoints(self): + def _convertPoints(self, weighted, epsg): """ flatten the list of tuples, convert into ctypes array """ flat = [] @@ -148,6 +151,19 @@ def _convertPoints(self): self.points = flat else: flat = self.points + + #convert if required + if epsg is not 'EPSG:3785': + source = pyproj.Proj(init=epsg) + dest = pyproj.Proj(init='EPSG:3785') + inc = 2 + if weighted: + inc = 3 + for i in range(0, len(flat), inc): + (x,y) = pyproj.transform(source,dest,flat[i],flat[i+1]) + flat[i] = x + flat[i+1] = y + #build array of input points arr_pts = (ctypes.c_float * (len(flat))) (*flat) return arr_pts @@ -201,6 +217,11 @@ def saveKML(self, kmlFile): ((west, south), (east, north)) = self.area else: ((west, south), (east, north)) = self._ranges() + if self.epsg is not 'EPSG:4326': + source = pyproj.Proj(init=self.epsg) + dest = pyproj.Proj(init='EPSG:4326') + (east,south) = pyproj.transform(source,dest,east,south) + (west,north) = pyproj.transform(source,dest,west,north) bytes = self.KML % (tilePath, north, south, east, west) file(kmlFile, "w").write(bytes) diff --git a/setup.py b/setup.py index 988a610..7765b16 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ def run(self): ext_modules=[cHeatmap, ], cmdclass={'install': post_install, 'build_ext': mybuild}, - requires=["Pillow", ], + requires=["Pillow", "pyproj"], test_suite="test", - tests_require=["Pillow", ], + tests_require=["Pillow", "pyproj"], ) diff --git a/test/tests.py b/test/tests.py index 82c745a..b8eb0fb 100644 --- a/test/tests.py +++ b/test/tests.py @@ -24,13 +24,13 @@ def test_heatmap_random_defaults(self): def test_heatmap_vert_line(self): pts = [(50, x) for x in range(100)] - img = self.heatmap.heatmap(pts, area=((0, 0), (200, 200))) + img = self.heatmap.heatmap(pts, area=((0, 0), (200, 200)),epsg='EPSG:3785') img.save("02-vert-line.png") self.assertTrue(isinstance(img, Image.Image)) def test_heatmap_horz_line(self): pts = [(x, 300) for x in range(600, 700)] - img = self.heatmap.heatmap(pts, size=(800,400), area=((0, 0), (800, 400))) + img = self.heatmap.heatmap(pts, size=(800,400), area=((0, 0), (800, 400)),epsg='EPSG:3785') img.save("03-horz-line.png") self.assertTrue(isinstance(img, Image.Image)) @@ -46,7 +46,7 @@ def test_heatmap_square(self): pts.extend([(4850, x*100) for x in range(2, 50)]) pts.extend([(x*100, 4850) for x in range(2, 50)]) pts.extend([(50, x*100) for x in range(2, 50)]) - img = self.heatmap.heatmap(pts, dotsize=100, area=((0,0), (5000, 5000))) + img = self.heatmap.heatmap(pts, dotsize=100, area=((0,0), (5000, 5000)),epsg='EPSG:3785') img.save("05-square.png") self.assertTrue(isinstance(img, Image.Image)) @@ -61,7 +61,7 @@ def test_invalid_heatmap(self): self.assertRaises(Exception, self.heatmap.heatmap, ([],)) def test_heatmap_weighted(self): - pts = [(random.random(), random.random()) for x in range(400)] + pts = [(random.uniform(30,40), random.uniform(-30,-40)) for x in range(400)] # this should also generate a warning on stderr of overly dense img = self.heatmap.heatmap(pts) img.save("07-400-normal.png") From 9f83ceeef11865f7b6fbd491a6eebfcf333da1c7 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 29 Jul 2014 03:13:12 +1000 Subject: [PATCH 10/38] fixed crs support for overriding area --- heatmap/heatmap.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 11d66d9..463b2eb 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -108,6 +108,13 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.area = ((0, 0), (0, 0)) self.override = 0 + ((east, south), (west, north)) = self.area + if self.epsg is not 'EPSG:3785': + source = pyproj.Proj(init=self.epsg) + dest = pyproj.Proj(init='EPSG:3785') + (east,south) = pyproj.transform(source,dest,east,south) + (west,north) = pyproj.transform(source,dest,west,north) + if scheme not in self.schemes(): tmp = "Unknown color scheme: %s. Available schemes: %s" % ( scheme, self.schemes()) @@ -120,9 +127,9 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c ret = self._heatmap.tx( arrPoints, len(arrPoints), size[0], size[1], dotsize, arrScheme, arrFinalImage, opacity, self.override, - ctypes.c_float(self.area[0][0]), ctypes.c_float( - self.area[0][1]), - ctypes.c_float(self.area[1][0]), ctypes.c_float(self.area[1][1]), weighted) + ctypes.c_float(east), ctypes.c_float( + south), + ctypes.c_float(west), ctypes.c_float(north), weighted) if not ret: raise Exception("Unexpected error during processing.") From 16122420b2aec20a569ac98340e1d16ee92d0648 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 29 Jul 2014 03:37:49 +1000 Subject: [PATCH 11/38] allowed for None setting for epsg --- heatmap/heatmap.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 463b2eb..04e1ffa 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -92,7 +92,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, these values are calculated based on the input data. weighted -> Is the data weighted - epsg -> epsg code of the source + epsg -> epsg code of the source, set to None to ignore """ self.dotsize = dotsize self.opacity = opacity @@ -109,7 +109,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.override = 0 ((east, south), (west, north)) = self.area - if self.epsg is not 'EPSG:3785': + if self.epsg is not None and self.epsg is not 'EPSG:3785': source = pyproj.Proj(init=self.epsg) dest = pyproj.Proj(init='EPSG:3785') (east,south) = pyproj.transform(source,dest,east,south) @@ -160,7 +160,7 @@ def _convertPoints(self, weighted, epsg): flat = self.points #convert if required - if epsg is not 'EPSG:3785': + if epsg is not None and epsg is not 'EPSG:3785': source = pyproj.Proj(init=epsg) dest = pyproj.Proj(init='EPSG:3785') inc = 2 @@ -224,6 +224,7 @@ def saveKML(self, kmlFile): ((west, south), (east, north)) = self.area else: ((west, south), (east, north)) = self._ranges() + #need to error if epsg is not set if self.epsg is not 'EPSG:4326': source = pyproj.Proj(init=self.epsg) dest = pyproj.Proj(init='EPSG:4326') From 7182684a80e4c03181fa08d4b364b9585aaa1d68 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 29 Jul 2014 22:42:06 +1000 Subject: [PATCH 12/38] No projection as default to not break current usage. Selectable output projection --- heatmap/heatmap.py | 29 +++++++++++++++-------------- test/tests.py | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 04e1ffa..b0f8a6e 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -76,7 +76,7 @@ def __init__(self, libpath=None): if not self._heatmap: raise Exception("Heatmap shared library not found in PYTHONPATH.") - def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None, weighted=0, epsg='EPSG:4326'): + def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None, weighted=0, srcepsg=None, dstepsg='EPSG:3857'): """ points -> an iterable list of tuples, where the contents are the x,y coordinates to plot. e.g., [(1, 1), (2, 2), (3, 3)] @@ -92,14 +92,16 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, these values are calculated based on the input data. weighted -> Is the data weighted - epsg -> epsg code of the source, set to None to ignore + srcepsg -> epsg code of the source, set to None to ignore + dstepsg -> epsg code of the destination, set to None to ignore, defaults to EPSG:3857 (Cylindrical Mercator). Due to linear interpolation in heatmap.c it only makes sense to use cylindrical linear projections. If outputting to KML/WGS84 use EPSG:4087 (World Equidistant Cylindrical). """ self.dotsize = dotsize self.opacity = opacity self.size = size self.points = points self.weighted = weighted - self.epsg = epsg + self.srcepsg = srcepsg + self.dstepsg = dstepsg if area is not None: self.area = area @@ -109,9 +111,9 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.override = 0 ((east, south), (west, north)) = self.area - if self.epsg is not None and self.epsg is not 'EPSG:3785': - source = pyproj.Proj(init=self.epsg) - dest = pyproj.Proj(init='EPSG:3785') + if self.srcepsg is not None and self.srcepsg is not self.dstepsg: + source = pyproj.Proj(init=self.srcepsg) + dest = pyproj.Proj(init=self.dstepsg) (east,south) = pyproj.transform(source,dest,east,south) (west,north) = pyproj.transform(source,dest,west,north) @@ -120,7 +122,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c scheme, self.schemes()) raise Exception(tmp) - arrPoints = self._convertPoints(weighted,epsg) + arrPoints = self._convertPoints(weighted) arrScheme = self._convertScheme(scheme) arrFinalImage = self._allocOutputBuffer() @@ -141,7 +143,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c def _allocOutputBuffer(self): return (ctypes.c_ubyte * (self.size[0] * self.size[1] * 4))() - def _convertPoints(self, weighted, epsg): + def _convertPoints(self, weighted): """ flatten the list of tuples, convert into ctypes array """ flat = [] @@ -160,9 +162,9 @@ def _convertPoints(self, weighted, epsg): flat = self.points #convert if required - if epsg is not None and epsg is not 'EPSG:3785': - source = pyproj.Proj(init=epsg) - dest = pyproj.Proj(init='EPSG:3785') + if self.srcepsg is not None and self.dstepsg is not None and self.srcepsg is not self.dstepsg: + source = pyproj.Proj(init=self.srcepsg) + dest = pyproj.Proj(init=self.dstepsg) inc = 2 if weighted: inc = 3 @@ -224,9 +226,8 @@ def saveKML(self, kmlFile): ((west, south), (east, north)) = self.area else: ((west, south), (east, north)) = self._ranges() - #need to error if epsg is not set - if self.epsg is not 'EPSG:4326': - source = pyproj.Proj(init=self.epsg) + if self.srcepsg is not None and self.srcepsg is not 'EPSG:4326': + source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init='EPSG:4326') (east,south) = pyproj.transform(source,dest,east,south) (west,north) = pyproj.transform(source,dest,west,north) diff --git a/test/tests.py b/test/tests.py index b8eb0fb..ef1fa66 100644 --- a/test/tests.py +++ b/test/tests.py @@ -24,13 +24,13 @@ def test_heatmap_random_defaults(self): def test_heatmap_vert_line(self): pts = [(50, x) for x in range(100)] - img = self.heatmap.heatmap(pts, area=((0, 0), (200, 200)),epsg='EPSG:3785') + img = self.heatmap.heatmap(pts, area=((0, 0), (200, 200))) img.save("02-vert-line.png") self.assertTrue(isinstance(img, Image.Image)) def test_heatmap_horz_line(self): pts = [(x, 300) for x in range(600, 700)] - img = self.heatmap.heatmap(pts, size=(800,400), area=((0, 0), (800, 400)),epsg='EPSG:3785') + img = self.heatmap.heatmap(pts, size=(800,400), area=((0, 0), (800, 400))) img.save("03-horz-line.png") self.assertTrue(isinstance(img, Image.Image)) @@ -46,7 +46,7 @@ def test_heatmap_square(self): pts.extend([(4850, x*100) for x in range(2, 50)]) pts.extend([(x*100, 4850) for x in range(2, 50)]) pts.extend([(50, x*100) for x in range(2, 50)]) - img = self.heatmap.heatmap(pts, dotsize=100, area=((0,0), (5000, 5000)),epsg='EPSG:3785') + img = self.heatmap.heatmap(pts, dotsize=100, area=((0,0), (5000, 5000))) img.save("05-square.png") self.assertTrue(isinstance(img, Image.Image)) @@ -125,6 +125,36 @@ def test_heatmap_area(self): self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("11-400-areaTestNormal.kml") + def test_heatmap_random_proj(self): + pts = [(random.uniform(-180,180),random.uniform(-90,90)) for x in range(400)] + img = self.heatmap.heatmap(pts) + img.save("09-400-normal.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("09-400-normal.kml") + img = self.heatmap.heatmap(pts,srcepsg='EPSG:4326') + img.save("09-400-EPSG3857.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("09-400-EPSG3857.kml") + img = self.heatmap.heatmap(pts, srcepsg='EPSG:4326',dstepsg='EPSG:4087') + img.save("09-400-EPSG4087.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("09-400-EPSG4087.kml") + + def test_heatmap_proj(self): + pts = [(x*2,x) for x in range(-89,89)] + img = self.heatmap.heatmap(pts) + img.save("10-400-normal.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("10-400-normal.kml") + img = self.heatmap.heatmap(pts,srcepsg='EPSG:4326') + img.save("10-400-EPSG3857.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("10-400-EPSG3857.kml") + img = self.heatmap.heatmap(pts, srcepsg='EPSG:4326',dstepsg='EPSG:4087') + img.save("10-400-EPSG4087.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("10-400-EPSG4087.kml") + class TestColorScheme(unittest.TestCase): def test_schemes(self): keys = colorschemes.valid_schemes() From 778fd78cdadef3ab2dd7b67f71664f49811eeda1 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Wed, 30 Jul 2014 00:47:14 +1000 Subject: [PATCH 13/38] describe dstepsg and srcepsg better --- heatmap/heatmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index b0f8a6e..673b957 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -92,8 +92,8 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, these values are calculated based on the input data. weighted -> Is the data weighted - srcepsg -> epsg code of the source, set to None to ignore - dstepsg -> epsg code of the destination, set to None to ignore, defaults to EPSG:3857 (Cylindrical Mercator). Due to linear interpolation in heatmap.c it only makes sense to use cylindrical linear projections. If outputting to KML/WGS84 use EPSG:4087 (World Equidistant Cylindrical). + srcepsg -> epsg code of the source, set to None to ignore. If outputting to KML for google earth client overlay and have WGS84 or World Equidistant Cylindrical coords leave as None to save processing. + dstepsg -> epsg code of the destination, ignored if srcepsg is not set. Defaults to EPSG:3857 (Cylindrical Mercator). Due to linear interpolation in heatmap.c it only makes sense to use linear output projections. If outputting to KML for google earth client overlay use EPSG:4087 (World Equidistant Cylindrical). """ self.dotsize = dotsize self.opacity = opacity From d255a53b78802fc4a8aea225416c98447aaa56f0 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 2 Aug 2014 01:27:43 +1000 Subject: [PATCH 14/38] made pyproj optional, not sure if like stderr with tests though, better way? --- heatmap/heatmap.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 673b957..9a0b01e 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -3,7 +3,13 @@ import ctypes import platform import math -import pyproj +use_pyproj = False +try: + import pyproj + use_pyproj = True +except: + pass + import colorschemes @@ -103,6 +109,9 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.srcepsg = srcepsg self.dstepsg = dstepsg + if srcepsg and not use_pyproj: + sys.stderr.write('WARNING: pyproj not avaialble, Source and Destination ESPG will be ignored\n') + if area is not None: self.area = area self.override = 1 @@ -111,7 +120,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.override = 0 ((east, south), (west, north)) = self.area - if self.srcepsg is not None and self.srcepsg is not self.dstepsg: + if use_pyproj and self.srcepsg is not None and self.srcepsg is not self.dstepsg: source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init=self.dstepsg) (east,south) = pyproj.transform(source,dest,east,south) @@ -162,7 +171,7 @@ def _convertPoints(self, weighted): flat = self.points #convert if required - if self.srcepsg is not None and self.dstepsg is not None and self.srcepsg is not self.dstepsg: + if use_pyproj and self.srcepsg is not None and self.dstepsg is not None and self.srcepsg is not self.dstepsg: source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init=self.dstepsg) inc = 2 @@ -226,7 +235,7 @@ def saveKML(self, kmlFile): ((west, south), (east, north)) = self.area else: ((west, south), (east, north)) = self._ranges() - if self.srcepsg is not None and self.srcepsg is not 'EPSG:4326': + if use_pyproj and self.srcepsg is not None and self.srcepsg is not 'EPSG:4326': source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init='EPSG:4326') (east,south) = pyproj.transform(source,dest,east,south) From 0cd1d37397fcb0cc8a53a630d7496693734b1222 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 2 Aug 2014 02:31:57 +1000 Subject: [PATCH 15/38] fix typos --- heatmap/heatmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 9a0b01e..0c0f0f9 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -110,7 +110,7 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.dstepsg = dstepsg if srcepsg and not use_pyproj: - sys.stderr.write('WARNING: pyproj not avaialble, Source and Destination ESPG will be ignored\n') + sys.stderr.write('WARNING: pyproj not available, Source and Destination EPSG will be ignored\n') if area is not None: self.area = area From 4c42ae7b090855c6ea1b733f017a6fe93ae1fb03 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 2 Aug 2014 02:41:05 +1000 Subject: [PATCH 16/38] cleaned up test requirements --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7765b16..cdc9fe0 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,9 @@ def run(self): ext_modules=[cHeatmap, ], cmdclass={'install': post_install, 'build_ext': mybuild}, - requires=["Pillow", "pyproj"], + requires=["Pillow"], + install_requires = ['Pillow'], + extras_require = {'proj' : 'pyproj'}, test_suite="test", - tests_require=["Pillow", "pyproj"], + tests_require=['pyproj'], ) From 8f517bd29fb9ebd7cb1412c20224905243e6fb57 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sun, 3 Aug 2014 23:51:07 +1000 Subject: [PATCH 17/38] fix issue with using internal points for _ranges, need to clone if convert --- heatmap/heatmap.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 0c0f0f9..57115bf 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -170,8 +170,10 @@ def _convertPoints(self, weighted): else: flat = self.points - #convert if required + #convert if required, need to copy as may use points later for _ranges. + #better to just calc all the time? if use_pyproj and self.srcepsg is not None and self.dstepsg is not None and self.srcepsg is not self.dstepsg: + converted =list(flat) source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init=self.dstepsg) inc = 2 @@ -179,11 +181,11 @@ def _convertPoints(self, weighted): inc = 3 for i in range(0, len(flat), inc): (x,y) = pyproj.transform(source,dest,flat[i],flat[i+1]) - flat[i] = x - flat[i+1] = y - - #build array of input points - arr_pts = (ctypes.c_float * (len(flat))) (*flat) + converted[i] = x + converted[i+1] = y + arr_pts = (ctypes.c_float * (len(converted))) (*converted) + else: + arr_pts = (ctypes.c_float * (len(flat))) (*flat) return arr_pts def _convertScheme(self, scheme): From 638c2a14ef61c707e89c9c891ecb0f91f41e9a3e Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sun, 3 Aug 2014 23:53:17 +1000 Subject: [PATCH 18/38] nicer looking test for proj, weight at 0,0 highlights current translation issue --- test/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/tests.py b/test/tests.py index ef1fa66..397c0b9 100644 --- a/test/tests.py +++ b/test/tests.py @@ -141,16 +141,16 @@ def test_heatmap_random_proj(self): self.heatmap.saveKML("09-400-EPSG4087.kml") def test_heatmap_proj(self): - pts = [(x*2,x) for x in range(-89,89)] - img = self.heatmap.heatmap(pts) + pts = [(x*2,x, 1 if x==0 else 0.75) for x in range(-89,89)] + img = self.heatmap.heatmap(pts, size = (2048, 1024), dotsize = 50, weighted = 1) img.save("10-400-normal.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("10-400-normal.kml") - img = self.heatmap.heatmap(pts,srcepsg='EPSG:4326') + img = self.heatmap.heatmap(pts,srcepsg='EPSG:4326', size = (2048, 1024), dotsize = 50, weighted = 1) img.save("10-400-EPSG3857.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("10-400-EPSG3857.kml") - img = self.heatmap.heatmap(pts, srcepsg='EPSG:4326',dstepsg='EPSG:4087') + img = self.heatmap.heatmap(pts, srcepsg='EPSG:4326',dstepsg='EPSG:4087', size = (2048, 1024), dotsize = 50, weighted = 1) img.save("10-400-EPSG4087.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("10-400-EPSG4087.kml") From a370450d392fbd3fdea471c0fe866603913b4e62 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 5 Aug 2014 03:25:58 +1000 Subject: [PATCH 19/38] happy with projection results now --- test/tests.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/tests.py b/test/tests.py index 397c0b9..8da7492 100644 --- a/test/tests.py +++ b/test/tests.py @@ -61,6 +61,7 @@ def test_invalid_heatmap(self): self.assertRaises(Exception, self.heatmap.heatmap, ([],)) def test_heatmap_weighted(self): + #normal should be the same as 100%, 75% should have same pattern but smaller pts = [(random.uniform(30,40), random.uniform(-30,-40)) for x in range(400)] # this should also generate a warning on stderr of overly dense img = self.heatmap.heatmap(pts) @@ -77,6 +78,7 @@ def test_heatmap_weighted(self): self.heatmap.saveKML("07-400-75percent.kml") def test_heatmap_random_datatypes(self): + #all of the below should turn out to be the same, if not there are issues pts = [[random.random(),random.random()] for x in range(400)] img = self.heatmap.heatmap(sum(pts,[])) img.save("08-400-array.png") @@ -126,6 +128,7 @@ def test_heatmap_area(self): self.heatmap.saveKML("11-400-areaTestNormal.kml") def test_heatmap_random_proj(self): + #4087 should be the same as 'normal' pts = [(random.uniform(-180,180),random.uniform(-90,90)) for x in range(400)] img = self.heatmap.heatmap(pts) img.save("09-400-normal.png") @@ -141,7 +144,9 @@ def test_heatmap_random_proj(self): self.heatmap.saveKML("09-400-EPSG4087.kml") def test_heatmap_proj(self): - pts = [(x*2,x, 1 if x==0 else 0.75) for x in range(-89,89)] + #normal should be the same as 4087 + #3857 and 4087 should roughly meet at 0,0 as symetrical around the equator + pts = [(x*2,x, 1 if x==0 else 0.75) for x in range(-89,90)] img = self.heatmap.heatmap(pts, size = (2048, 1024), dotsize = 50, weighted = 1) img.save("10-400-normal.png") self.assertTrue(isinstance(img, Image.Image)) From 5f524eaf162ff810b684be60ffb19b4115d52977 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 5 Aug 2014 04:03:49 +1000 Subject: [PATCH 20/38] cleaned up setup for distutils and setuputils --- setup.py | 64 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/setup.py b/setup.py index cdc9fe0..18d5eca 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,22 @@ import os import glob +#here use a flag so don't automatically use setuptools if available, hard to test otherwise with_setuptools = False -if 'USE_SETUPTOOLS' in os.environ or 'pip' in __file__: +if 'USE_SETUPTOOLS' in os.environ or 'pip' in __file__ or 'easy_install' in __file__: try: from setuptools.command.install import install from setuptools import setup from setuptools import Extension from setuptools.command.build_ext import build_ext with_setuptools = True - except: - with_setuptools = False - -if with_setuptools is False: - from distutils.command.install import install - from distutils.core import setup - from distutils.core import Extension - from distutils.command.build_ext import build_ext + except ImportError: + pass +if not with_setuptools: + from distutils.command.install import install + from distutils.core import setup + from distutils.core import Extension + from distutils.command.build_ext import build_ext # sorry for this, welcome feedback on the "right" way. # shipping pre-compiled bainries on windows, have @@ -29,7 +29,6 @@ def run(self): return build_ext.run(self) - class post_install(install): def run(self): install.run(self) @@ -45,21 +44,30 @@ def run(self): cHeatmap = Extension('cHeatmap', sources=['heatmap/heatmap.c', ]) -setup(name='heatmap', - version="2.2.1", - description='Module to create heatmaps', - author='Jeffrey J. Guy', - author_email='jjg@case.edu', - url='http://jjguy.com/heatmap/', - license='MIT License', - packages=['heatmap', ], - py_modules=['heatmap.colorschemes', ], - ext_modules=[cHeatmap, ], - cmdclass={'install': post_install, - 'build_ext': mybuild}, - requires=["Pillow"], - install_requires = ['Pillow'], - extras_require = {'proj' : 'pyproj'}, - test_suite="test", - tests_require=['pyproj'], - ) +#separate calls to remove errors +basekw = { + 'name' : 'heatmap', + 'version' : "2.2.1", + 'description' : 'Module to create heatmaps', + 'author' : 'Jeffrey J. Guy', + 'author_email' : 'jjg@case.edu', + 'url' : 'http://jjguy.com/heatmap/', + 'license' : 'MIT License', + 'packages' : ['heatmap', ], + 'py_modules' : ['heatmap.colorschemes', ], + 'ext_modules' : [cHeatmap, ], + 'cmdclass' : {'install': post_install, + 'build_ext': mybuild} + } +setuptoolskw = { + 'install_requires' : ['Pillow'], + 'extras_require' : {'proj' : 'pyproj'}, + 'test_suite' : "test", + 'tests_require' : ['pyproj'] + } +distutilskw = { + 'requires' : ["Pillow"] + } + +basekw.update(setuptoolskw) if with_setuptools else basekw.update(distutilskw) +setup(**basekw) From b0e5f30ca789e2dd18ef819f23a8a1cd7213e4ad Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 5 Aug 2014 04:05:14 +1000 Subject: [PATCH 21/38] cleaned up some lengthy chucnks of code --- heatmap/heatmap.py | 80 +++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 57115bf..6e83e2d 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -3,17 +3,15 @@ import ctypes import platform import math +import colorschemes +from PIL import Image + use_pyproj = False try: import pyproj use_pyproj = True except: pass - - -import colorschemes - -from PIL import Image class Heatmap: """ @@ -52,8 +50,6 @@ class Heatmap: """ def __init__(self, libpath=None): - self.minXY = () - self.maxXY = () self.img = None # if you're reading this, it's probably because this # hacktastic garbage failed. sorry. I deserve a jab or two via @jjguy. @@ -109,8 +105,8 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.srcepsg = srcepsg self.dstepsg = dstepsg - if srcepsg and not use_pyproj: - sys.stderr.write('WARNING: pyproj not available, Source and Destination EPSG will be ignored\n') + if self.srcepsg and not use_pyproj: + raise Exception('srcepsg entered but pyproj is not available') if area is not None: self.area = area @@ -119,8 +115,9 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c self.area = ((0, 0), (0, 0)) self.override = 0 + #convert area for heatmap.c if required ((east, south), (west, north)) = self.area - if use_pyproj and self.srcepsg is not None and self.srcepsg is not self.dstepsg: + if use_pyproj and self.srcepsg is not None and self.srcepsg != self.dstepsg: source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init=self.dstepsg) (east,south) = pyproj.transform(source,dest,east,south) @@ -131,15 +128,14 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c scheme, self.schemes()) raise Exception(tmp) - arrPoints = self._convertPoints(weighted) + arrPoints = self._convertPoints() arrScheme = self._convertScheme(scheme) arrFinalImage = self._allocOutputBuffer() ret = self._heatmap.tx( arrPoints, len(arrPoints), size[0], size[1], dotsize, arrScheme, arrFinalImage, opacity, self.override, - ctypes.c_float(east), ctypes.c_float( - south), + ctypes.c_float(east), ctypes.c_float(south), ctypes.c_float(west), ctypes.c_float(north), weighted) if not ret: @@ -152,53 +148,37 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c def _allocOutputBuffer(self): return (ctypes.c_ubyte * (self.size[0] * self.size[1] * 4))() - def _convertPoints(self, weighted): + def _convertPoints(self): """ flatten the list of tuples, convert into ctypes array """ - flat = [] - if isinstance(self.points[0],tuple) or isinstance(self.points[0],list): - if (self.weighted): - for i, j, k in self.points: - flat.append(i) - flat.append(j) - flat.append(k) - else: - for i, j in self.points: - flat.append(i) - flat.append(j) - self.points = flat - else: - flat = self.points + if isinstance(self.points,tuple): + self.points = list(self.points) + if isinstance(self.points[0],tuple): + self.points = list(sum(self.points,())) + elif isinstance(self.points[0],list): + self.points = sum(self.points,[]) - #convert if required, need to copy as may use points later for _ranges. - #better to just calc all the time? - if use_pyproj and self.srcepsg is not None and self.dstepsg is not None and self.srcepsg is not self.dstepsg: - converted =list(flat) + #convert if required, need to copy as may use points later for _range. + if use_pyproj and self.srcepsg is not None and self.srcepsg != self.dstepsg: + converted =list(self.points) source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init=self.dstepsg) - inc = 2 - if weighted: - inc = 3 - for i in range(0, len(flat), inc): - (x,y) = pyproj.transform(source,dest,flat[i],flat[i+1]) + #nicer way? map/lambda will retun 2/3 tuple so need to flatten again + inc = 3 if self.weighted else 2 + for i in range(0, len(self.points), inc): + (x,y) = pyproj.transform(source,dest,self.points[i],self.points[i+1]) converted[i] = x converted[i+1] = y arr_pts = (ctypes.c_float * (len(converted))) (*converted) else: - arr_pts = (ctypes.c_float * (len(flat))) (*flat) + arr_pts = (ctypes.c_float * (len(self.points))) (*self.points) return arr_pts def _convertScheme(self, scheme): """ flatten the list of RGB tuples, convert into ctypes array """ - #TODO is there a better way to do this?? - flat = [] - for r, g, b in colorschemes.schemes[scheme]: - flat.append(r) - flat.append(g) - flat.append(b) - arr_cs = ( - ctypes.c_int * (len(colorschemes.schemes[scheme]) * 3))(*flat) + flat = list(sum(colorschemes.schemes[scheme],())) + arr_cs = (ctypes.c_int * (len(flat)))(*flat) return arr_cs def _ranges(self): @@ -208,9 +188,7 @@ def _ranges(self): minY = self.points[1] maxX = minX maxY = minY - inc = 2 - if self.weighted: - inc = 3 + inc = 3 if self.weighted else 2 for i in range(0,len(self.points),inc): minX = min(self.points[i], minX) minY = min(self.points[i+1], minY) @@ -237,7 +215,9 @@ def saveKML(self, kmlFile): ((west, south), (east, north)) = self.area else: ((west, south), (east, north)) = self._ranges() - if use_pyproj and self.srcepsg is not None and self.srcepsg is not 'EPSG:4326': + + #convert overlay BBOX if required + if use_pyproj and self.srcepsg is not None and self.srcepsg != 'EPSG:4326': source = pyproj.Proj(init=self.srcepsg) dest = pyproj.Proj(init='EPSG:4326') (east,south) = pyproj.transform(source,dest,east,south) From 9933c68ce69f11166dac2d474904d3e6e303a362 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 5 Aug 2014 04:05:54 +1000 Subject: [PATCH 22/38] added test for all possible datatypes --- test/tests.py | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/test/tests.py b/test/tests.py index 8da7492..e06a45c 100644 --- a/test/tests.py +++ b/test/tests.py @@ -79,19 +79,47 @@ def test_heatmap_weighted(self): def test_heatmap_random_datatypes(self): #all of the below should turn out to be the same, if not there are issues - pts = [[random.random(),random.random()] for x in range(400)] - img = self.heatmap.heatmap(sum(pts,[])) - img.save("08-400-array.png") + pts = tuple((random.random(),random.random(),1) for x in range(400)) + img = self.heatmap.heatmap(map(lambda (x,y,z) : (x,y), pts)) + img.save("08-400-tupleoftuples.png") self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-array.kml") - img = self.heatmap.heatmap(map(lambda (x,y) : (x,y), pts)) + self.heatmap.saveKML("08-400-tupleoftuples.kml") + img = self.heatmap.heatmap(pts, weighted=1) + img.save("08-400-tupleoftuplesweighted.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-tupleoftuplesweighted.kml") + img = self.heatmap.heatmap(list(map(lambda (x,y,z) : (x,y), pts))) img.save("08-400-arrayoftuples.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayoftuples.kml") - img = self.heatmap.heatmap(pts) + img = self.heatmap.heatmap(list(pts), weighted=1) + img.save("08-400-arrayoftuplesweighted.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-arrayoftuplesweighted.kml") + img = self.heatmap.heatmap(map(lambda (x,y,z) : [x,y], pts)) + img.save("08-400-tupleofarrays.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-tupleofarrays.kml") + img = self.heatmap.heatmap(map(lambda (x,y,z) : [x,y,z], pts), weighted=1) + img.save("08-400-tupleofarraysweighted.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-tupleofarrayweighted.kml") + img = self.heatmap.heatmap(list(map(lambda (x,y,z) : [x,y], pts))) img.save("08-400-arrayofarrays.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayofarrays.kml") + img = self.heatmap.heatmap(list(map(lambda (x,y,z) : [x,y,z], pts)), weighted=1) + img.save("08-400-arrayofarraysweighted.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-arrayofarraysweighted.kml") + img = self.heatmap.heatmap(sum(map(lambda (x,y,z) : [x,y], pts),[])) + img.save("08-400-flat.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-flat.kml") + img = self.heatmap.heatmap(sum(map(lambda (x,y,z) : [x,y,z], pts),[]), weighted=1) + img.save("08-400-flatweighted.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("08-400-flatweighted.kml") def test_heatmap_area(self): MAX_SIZE=8192 @@ -143,7 +171,7 @@ def test_heatmap_random_proj(self): self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("09-400-EPSG4087.kml") - def test_heatmap_proj(self): + def test_heatmap_weighted_proj(self): #normal should be the same as 4087 #3857 and 4087 should roughly meet at 0,0 as symetrical around the equator pts = [(x*2,x, 1 if x==0 else 0.75) for x in range(-89,90)] From c50a822be673d655753be8f10bc93e8afe25577f Mon Sep 17 00:00:00 2001 From: kwauchope Date: Tue, 5 Aug 2014 04:30:17 +1000 Subject: [PATCH 23/38] added pyproj to requirements for pip/travis --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f917857..2ed43c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ Pillow>=2.1.0 +pyproj From d916f335e66a33695662693ccfce66f6a0b6d140 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 16 Aug 2014 14:53:10 +1000 Subject: [PATCH 24/38] adding python3 support --- .travis.yml | 4 ++++ examples/example.py | 2 +- heatmap/__init__.py | 4 ++-- heatmap/heatmap.py | 6 ++++-- setup.py | 2 +- test/tests.py | 24 ++++++++++++------------ 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec652eb..1a654c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ language: python python: + - "2.6" - "2.7" + - "3.2" + - "3.3" + - "3.4" # command to install dependencies install: diff --git a/examples/example.py b/examples/example.py index 8b0c4ef..588dabe 100644 --- a/examples/example.py +++ b/examples/example.py @@ -5,6 +5,6 @@ pts = [] for x in range(400): pts.append((random.random(), random.random())) - print "Processing %d points..." % len(pts) + print("Processing %d points..." % len(pts)) hm = heatmap.Heatmap() img = hm.heatmap(pts).save("classic.png") diff --git a/heatmap/__init__.py b/heatmap/__init__.py index da77f06..10fd00a 100644 --- a/heatmap/__init__.py +++ b/heatmap/__init__.py @@ -1,6 +1,6 @@ try: __version__ = __import__('pkg_resources').get_distribution(__name__).version -except Exception, e: +except Exception as e: __version__ = 'unknown' -from heatmap import Heatmap \ No newline at end of file +from .heatmap import Heatmap diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 6e83e2d..468d2e7 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -3,7 +3,7 @@ import ctypes import platform import math -import colorschemes +from . import colorschemes from PIL import Image use_pyproj = False @@ -224,7 +224,9 @@ def saveKML(self, kmlFile): (west,north) = pyproj.transform(source,dest,west,north) bytes = self.KML % (tilePath, north, south, east, west) - file(kmlFile, "w").write(bytes) + fh = open(kmlFile, "w") + fh.write(bytes) + fh.close() def schemes(self): """ diff --git a/setup.py b/setup.py index 18d5eca..91de759 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ class mybuild(build_ext): def run(self): if "nt" in os.name: - print "On Windows, skipping build_ext." + print("On Windows, skipping build_ext.") return build_ext.run(self) diff --git a/test/tests.py b/test/tests.py index e06a45c..8509d9a 100644 --- a/test/tests.py +++ b/test/tests.py @@ -68,11 +68,11 @@ def test_heatmap_weighted(self): img.save("07-400-normal.png") self.heatmap.saveKML("07-400-normal.kml") self.assertTrue(isinstance(img, Image.Image)) - img = self.heatmap.heatmap(map( lambda (x,y) : (x,y,1), pts), weighted=1) + img = self.heatmap.heatmap(list(map( lambda x_y : (x_y[0],x_y[1],1), pts)), weighted=1) img.save("07-400-100percent.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("07-400-100percent.kml") - img = self.heatmap.heatmap(map( lambda (x,y) : (x,y,.75), pts), weighted=1) + img = self.heatmap.heatmap(list(map( lambda x_y : (x_y[0],x_y[1],.75), pts)), weighted=1) img.save("07-400-75percent.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("07-400-75percent.kml") @@ -80,7 +80,7 @@ def test_heatmap_weighted(self): def test_heatmap_random_datatypes(self): #all of the below should turn out to be the same, if not there are issues pts = tuple((random.random(),random.random(),1) for x in range(400)) - img = self.heatmap.heatmap(map(lambda (x,y,z) : (x,y), pts)) + img = self.heatmap.heatmap(tuple(map(lambda x_y_z : (x_y_z[0],x_y_z[1]), pts))) img.save("08-400-tupleoftuples.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-tupleoftuples.kml") @@ -88,7 +88,7 @@ def test_heatmap_random_datatypes(self): img.save("08-400-tupleoftuplesweighted.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-tupleoftuplesweighted.kml") - img = self.heatmap.heatmap(list(map(lambda (x,y,z) : (x,y), pts))) + img = self.heatmap.heatmap(list(map(lambda x_y_z : (x_y_z[0],x_y_z[1]), pts))) img.save("08-400-arrayoftuples.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayoftuples.kml") @@ -96,27 +96,27 @@ def test_heatmap_random_datatypes(self): img.save("08-400-arrayoftuplesweighted.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayoftuplesweighted.kml") - img = self.heatmap.heatmap(map(lambda (x,y,z) : [x,y], pts)) + img = self.heatmap.heatmap(tuple(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts))) img.save("08-400-tupleofarrays.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-tupleofarrays.kml") - img = self.heatmap.heatmap(map(lambda (x,y,z) : [x,y,z], pts), weighted=1) + img = self.heatmap.heatmap(tuple(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts)), weighted=1) img.save("08-400-tupleofarraysweighted.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-tupleofarrayweighted.kml") - img = self.heatmap.heatmap(list(map(lambda (x,y,z) : [x,y], pts))) + img = self.heatmap.heatmap(list(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts))) img.save("08-400-arrayofarrays.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayofarrays.kml") - img = self.heatmap.heatmap(list(map(lambda (x,y,z) : [x,y,z], pts)), weighted=1) + img = self.heatmap.heatmap(list(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts)), weighted=1) img.save("08-400-arrayofarraysweighted.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-arrayofarraysweighted.kml") - img = self.heatmap.heatmap(sum(map(lambda (x,y,z) : [x,y], pts),[])) + img = self.heatmap.heatmap(sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts),[])) img.save("08-400-flat.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-flat.kml") - img = self.heatmap.heatmap(sum(map(lambda (x,y,z) : [x,y,z], pts),[]), weighted=1) + img = self.heatmap.heatmap(sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts),[]), weighted=1) img.save("08-400-flatweighted.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("08-400-flatweighted.kml") @@ -191,10 +191,10 @@ def test_heatmap_weighted_proj(self): class TestColorScheme(unittest.TestCase): def test_schemes(self): keys = colorschemes.valid_schemes() - self.assertEqual(keys, ['fire', 'pgaitch', 'pbj', 'omg', 'classic']) + self.assertEqual(sorted(list(keys)), sorted(['fire', 'pgaitch', 'pbj', 'omg', 'classic'])) def test_values(self): - for key, values in colorschemes.schemes.iteritems(): + for key, values in colorschemes.schemes.items(): self.assertTrue(isinstance(values, list)) self.assertEqual(len(values), 256) for value in values: From 66741936351cb386d0f11f3e8d802ee4e6039c03 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 16 Aug 2014 15:52:40 +1000 Subject: [PATCH 25/38] fixed ISO C90 compatabliity so will work on windows, highlighted by 3.4 build --- heatmap/heatmap.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/heatmap/heatmap.c b/heatmap/heatmap.c index 7af4ea5..55f5bf1 100644 --- a/heatmap/heatmap.c +++ b/heatmap/heatmap.c @@ -110,14 +110,15 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei int ndx = 0; struct point pt = {0}; + int inc = 2; + if (weighted) inc = 3; + // initialize image data to white for(i = 0; i < (int)width*height; i++) { pixels[i] = 0xff; } - int inc = 2; - if (weighted) inc = 3; for(i = 0; i < cPoints; i=i+inc) { @@ -210,6 +211,7 @@ unsigned char *tx(float *points, float minX, float minY, float maxX, float maxY, int weighted) { unsigned char *pixels_bw = NULL; + struct info inf = {0}; //basic sanity checks to keep from segfaulting if (NULL == points || NULL == scheme || NULL == pix_color || @@ -219,7 +221,6 @@ unsigned char *tx(float *points, return NULL; } - struct info inf = {0}; inf.dotsize = dotsize; inf.width = w; From 04337bd2131ecc308983c77538c6fa7243386a3b Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sun, 17 Aug 2014 03:15:04 +1000 Subject: [PATCH 26/38] ctypes searching fixes required to run on the travis ci python3 env, travis install copies cHeatmap.cpython-*.so instead of cHeatmap.so --- heatmap/heatmap.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 468d2e7..526f7a2 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -5,6 +5,7 @@ import math from . import colorschemes from PIL import Image +import glob use_pyproj = False try: @@ -61,7 +62,7 @@ def __init__(self, libpath=None): # establish the right library name, based on platform and arch. Windows # are pre-compiled binaries; linux machines are compiled during setup. self._heatmap = None - libname = "cHeatmap.so" + libname = "cHeatmap" if "cygwin" in platform.system().lower(): libname = "cHeatmap.dll" if "windows" in platform.system().lower(): @@ -71,9 +72,17 @@ def __init__(self, libpath=None): # now rip through everything in sys.path to find 'em. Should be in site-packages # or local dir for d in sys.path: - if os.path.isfile(os.path.join(d, libname)): + if os.path.isfile(os.path.join(d, libname+'.so')): self._heatmap = ctypes.cdll.LoadLibrary( - os.path.join(d, libname)) + os.path.join(d, libname+'.so')) + # check for cpython-*.so prefix for object files which seems to be the ones + # copied on install in the travis python3 environment (even with the same version of setuptools) + # may investigate further and do the test based on execution environment + if not self._heatmap: + for d in sys.path: + file = glob.glob(os.path.join(d,libname+'.cpython-*.so')) + if file: + self._heatmap = ctypes.cdll.LoadLibrary(file[0]) if not self._heatmap: raise Exception("Heatmap shared library not found in PYTHONPATH.") From fededde6228323bf94408d30b0e54afd086e4ece Mon Sep 17 00:00:00 2001 From: kwauchope Date: Wed, 20 Aug 2014 21:47:24 +1000 Subject: [PATCH 27/38] added better coverage for tests as well as python version test for >=2.6 --- .coveragerc | 3 +++ .gitignore | 3 ++- heatmap/__init__.py | 8 ++++---- test/tests.py | 16 +++++++++++++--- 4 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..2da778b --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +branch = True +omit="*/PIL/*,*/pyproj/*,*/pkg_resources.py" diff --git a/.gitignore b/.gitignore index a4c6eec..23fc65f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ pip-log.txt .coverage .tox nosetests.xml +/htmlcov # Translations *.mo @@ -44,4 +45,4 @@ nosetests.xml .DS_Store *.png -*.kml \ No newline at end of file +*.kml diff --git a/heatmap/__init__.py b/heatmap/__init__.py index 10fd00a..03373ff 100644 --- a/heatmap/__init__.py +++ b/heatmap/__init__.py @@ -1,6 +1,6 @@ -try: - __version__ = __import__('pkg_resources').get_distribution(__name__).version -except Exception as e: - __version__ = 'unknown' +import sys + +if sys.hexversion < 0x02060000: + raise Exception('Heatmap requires python 2.6 or greater.'); from .heatmap import Heatmap diff --git a/test/tests.py b/test/tests.py index 8509d9a..3401a6b 100644 --- a/test/tests.py +++ b/test/tests.py @@ -57,9 +57,6 @@ def test_heatmap_single_point(self): self.heatmap.saveKML("06-wash-dc.kml") self.assertTrue(isinstance(img, Image.Image)) - def test_invalid_heatmap(self): - self.assertRaises(Exception, self.heatmap.heatmap, ([],)) - def test_heatmap_weighted(self): #normal should be the same as 100%, 75% should have same pattern but smaller pts = [(random.uniform(30,40), random.uniform(-30,-40)) for x in range(400)] @@ -170,6 +167,10 @@ def test_heatmap_random_proj(self): img.save("09-400-EPSG4087.png") self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("09-400-EPSG4087.kml") + img = self.heatmap.heatmap(pts, srcepsg='EPSG:3857',dstepsg='EPSG:4087') + img.save("09-400-EPSG4087.png") + self.assertTrue(isinstance(img, Image.Image)) + self.heatmap.saveKML("09-400-EPSG4087.kml") def test_heatmap_weighted_proj(self): #normal should be the same as 4087 @@ -188,6 +189,15 @@ def test_heatmap_weighted_proj(self): self.assertTrue(isinstance(img, Image.Image)) self.heatmap.saveKML("10-400-EPSG4087.kml") + def test_heatmap_exceptions(self): + #test invalid (empty) heatmap + self.assertRaisesRegex(Exception, 'Unexpected error', self.heatmap.heatmap, ([],)) + #test invalid scheme + self.assertRaisesRegex(Exception, 'Unknown color scheme', self.heatmap.heatmap, ([],),scheme='invalid') + #test saveKML before create heatmap + self.assertRaisesRegex(Exception, 'Must first run heatmap', self.heatmap.saveKML,"test.kml") + + class TestColorScheme(unittest.TestCase): def test_schemes(self): keys = colorschemes.valid_schemes() From 76a3a7553499c8ddee8431dd9ccf874ece2bb357 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Wed, 20 Aug 2014 22:21:39 +1000 Subject: [PATCH 28/38] fixed tests for older versions of unittest, uses function assignment as no __version__ is available --- test/tests.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/test/tests.py b/test/tests.py index 3401a6b..02bdc00 100644 --- a/test/tests.py +++ b/test/tests.py @@ -190,13 +190,32 @@ def test_heatmap_weighted_proj(self): self.heatmap.saveKML("10-400-EPSG4087.kml") def test_heatmap_exceptions(self): - #test invalid (empty) heatmap - self.assertRaisesRegex(Exception, 'Unexpected error', self.heatmap.heatmap, ([],)) + + #test invalid (empty) heatmap, should print error to stdout + emptyHeatmapArgs = [Exception, 'Unexpected error', self.heatmap.heatmap, ([],)] + emptyHeatmapKwargs = {} #test invalid scheme - self.assertRaisesRegex(Exception, 'Unknown color scheme', self.heatmap.heatmap, ([],),scheme='invalid') + invalidColorSchemeArgs = [Exception, 'Unknown color scheme', self.heatmap.heatmap, ([],)] + invalidColourSchemeKwargs = {'scheme' : 'invalid'} #test saveKML before create heatmap - self.assertRaisesRegex(Exception, 'Must first run heatmap', self.heatmap.saveKML,"test.kml") + saveKMLArgs = [Exception, 'Must first run heatmap', self.heatmap.saveKML,"test.kml"] + saveKMLKwargs = {} + + #better way? no __verison__ in unittest + try: + function = self.assertRaisesRegex + except: + try: + function = self.assertRaisesRegexp + except: + function = self.assertRaises + emptyHeatmapArgs.pop(1) + invalidColorSchemeArgs.pop(1) + saveKMLArgs.pop(1) + function(*emptyHeatmapArgs, **emptyHeatmapKwargs) + function(*invalidColorSchemeArgs, **invalidColourSchemeKwargs) + function(*saveKMLArgs, **saveKMLKwargs) class TestColorScheme(unittest.TestCase): def test_schemes(self): From 49bdbce4188227fbeb4fb634d312d0907168f0b0 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Thu, 21 Aug 2014 16:22:59 +1000 Subject: [PATCH 29/38] cleaner to include rather than omit in coveragerc --- .coveragerc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.coveragerc b/.coveragerc index 2da778b..fbae63d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,3 @@ [run] -branch = True -omit="*/PIL/*,*/pyproj/*,*/pkg_resources.py" +branch=True +include=*heatmap* From dbabd096713eb09f50fa0d7944aace74e9617382 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Thu, 21 Aug 2014 17:14:01 +1000 Subject: [PATCH 30/38] added back version code --- heatmap/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/heatmap/__init__.py b/heatmap/__init__.py index 03373ff..50c0f0b 100644 --- a/heatmap/__init__.py +++ b/heatmap/__init__.py @@ -1,5 +1,10 @@ import sys +try: + __version__ = __import__('pkg_resources').get_distribution(__name__).version +except Exception as e: + __version__ = 'unknown' + if sys.hexversion < 0x02060000: raise Exception('Heatmap requires python 2.6 or greater.'); From 5ac16e745add4797cacdbf20b7ed83ffd32d12b0 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Fri, 22 Aug 2014 09:55:38 +1000 Subject: [PATCH 31/38] cleaned up srcepsg and dstepsg docco --- heatmap/heatmap.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 526f7a2..868614a 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -103,8 +103,12 @@ def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="c tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, these values are calculated based on the input data. weighted -> Is the data weighted - srcepsg -> epsg code of the source, set to None to ignore. If outputting to KML for google earth client overlay and have WGS84 or World Equidistant Cylindrical coords leave as None to save processing. - dstepsg -> epsg code of the destination, ignored if srcepsg is not set. Defaults to EPSG:3857 (Cylindrical Mercator). Due to linear interpolation in heatmap.c it only makes sense to use linear output projections. If outputting to KML for google earth client overlay use EPSG:4087 (World Equidistant Cylindrical). + srcepsg -> epsg code of the source, set to None to ignore. + If using KML output make sure this is set otherwise either the image + or the overlay coordinates will be out. + dstepsg -> epsg code of the destination, ignored if srcepsg is not set. Defaults to EPSG:3857 (Cylindrical Mercator). + Due to linear interpolation in heatmap.c it only makes sense to use linear output projections. + If outputting to KML for google earth client overlay use EPSG:4087 (World Equidistant Cylindrical) """ self.dotsize = dotsize self.opacity = opacity From d5d1cf76e29647df8a2c9c3d22061f26217f26a2 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Fri, 22 Aug 2014 09:56:38 +1000 Subject: [PATCH 32/38] added image comaparison tests and split out the repeated saveKML functionality --- test/tests.py | 183 ++++++++++++++++++++------------------------------ 1 file changed, 71 insertions(+), 112 deletions(-) diff --git a/test/tests.py b/test/tests.py index 02bdc00..2271cf8 100644 --- a/test/tests.py +++ b/test/tests.py @@ -10,113 +10,83 @@ import heatmap from heatmap import colorschemes + class TestHeatmap(unittest.TestCase): """unittests for TestHeatmap""" + + def heatmapImage(self,name,pts,kwargs={},saveKML=False): + img = self.heatmap.heatmap(pts, **kwargs) + self.assertTrue(isinstance(img, Image.Image)) + if (saveKML): + self.heatmap.saveKML(name+".kml") + else: + img.save(name+".png") + return img def setUp(self): self.heatmap = heatmap.Heatmap() def test_heatmap_random_defaults(self): pts = [(random.random(), random.random()) for x in range(400)] - img = self.heatmap.heatmap(pts) - img.save("01-400-random.png") - self.assertTrue(isinstance(img, Image.Image)) + self.heatmapImage("01-400-random", pts) def test_heatmap_vert_line(self): pts = [(50, x) for x in range(100)] - img = self.heatmap.heatmap(pts, area=((0, 0), (200, 200))) - img.save("02-vert-line.png") - self.assertTrue(isinstance(img, Image.Image)) + self.heatmapImage("02-vert-line", pts, kwargs={"area" : ((0, 0), (200, 200))}) def test_heatmap_horz_line(self): pts = [(x, 300) for x in range(600, 700)] - img = self.heatmap.heatmap(pts, size=(800,400), area=((0, 0), (800, 400))) - img.save("03-horz-line.png") - self.assertTrue(isinstance(img, Image.Image)) + self.heatmapImage("03-horz-line", pts, kwargs={ "size" : (800,400), "area" : ((0, 0), (800, 400))}) def test_heatmap_random(self): - pts = [(random.random(), random.random()) for x in range(40000)] # this should also generate a warning on stderr of overly dense - img = self.heatmap.heatmap(pts, dotsize=25, opacity=128) - img.save("04-40k-random.png") - self.assertTrue(isinstance(img, Image.Image)) + pts = [(random.random(), random.random()) for x in range(40000)] + self.heatmapImage("04-40k-random", pts , kwargs={ "dotsize" : 25, "opacity" : 128}) def test_heatmap_square(self): pts = [(x*100, 50) for x in range(2, 50)] pts.extend([(4850, x*100) for x in range(2, 50)]) pts.extend([(x*100, 4850) for x in range(2, 50)]) pts.extend([(50, x*100) for x in range(2, 50)]) - img = self.heatmap.heatmap(pts, dotsize=100, area=((0,0), (5000, 5000))) - img.save("05-square.png") - self.assertTrue(isinstance(img, Image.Image)) + self.heatmapImage("05-square", pts , kwargs={ "dotsize" : 100, "area" : ((0,0), (5000, 5000)) }) def test_heatmap_single_point(self): pts = [(random.uniform(-77.012, -77.050), random.uniform(38.888, 38.910)) for x in range(100)] - img = self.heatmap.heatmap(pts) - self.heatmap.saveKML("06-wash-dc.kml") - self.assertTrue(isinstance(img, Image.Image)) + self.heatmapImage("06-wash-dc", pts) def test_heatmap_weighted(self): #normal should be the same as 100%, 75% should have same pattern but smaller pts = [(random.uniform(30,40), random.uniform(-30,-40)) for x in range(400)] - # this should also generate a warning on stderr of overly dense - img = self.heatmap.heatmap(pts) - img.save("07-400-normal.png") - self.heatmap.saveKML("07-400-normal.kml") - self.assertTrue(isinstance(img, Image.Image)) - img = self.heatmap.heatmap(list(map( lambda x_y : (x_y[0],x_y[1],1), pts)), weighted=1) - img.save("07-400-100percent.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("07-400-100percent.kml") - img = self.heatmap.heatmap(list(map( lambda x_y : (x_y[0],x_y[1],.75), pts)), weighted=1) - img.save("07-400-75percent.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("07-400-75percent.kml") + norm = self.heatmapImage("07-400-normal", pts, saveKML=True) + weight = self.heatmapImage("07-400-100percent", list(map( lambda x_y : (x_y[0],x_y[1],1), pts)), kwargs={ "weighted" : 1 }, saveKML=True) + self.assertEqual(norm,weight) + weight2 = self.heatmapImage("07-400-75percent", list(map( lambda x_y : (x_y[0],x_y[1],.75), pts)), kwargs={ "weighted" : 1 }, saveKML=True) + self.assertNotEqual(norm,weight2) def test_heatmap_random_datatypes(self): #all of the below should turn out to be the same, if not there are issues pts = tuple((random.random(),random.random(),1) for x in range(400)) - img = self.heatmap.heatmap(tuple(map(lambda x_y_z : (x_y_z[0],x_y_z[1]), pts))) - img.save("08-400-tupleoftuples.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-tupleoftuples.kml") - img = self.heatmap.heatmap(pts, weighted=1) - img.save("08-400-tupleoftuplesweighted.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-tupleoftuplesweighted.kml") - img = self.heatmap.heatmap(list(map(lambda x_y_z : (x_y_z[0],x_y_z[1]), pts))) - img.save("08-400-arrayoftuples.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-arrayoftuples.kml") - img = self.heatmap.heatmap(list(pts), weighted=1) - img.save("08-400-arrayoftuplesweighted.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-arrayoftuplesweighted.kml") - img = self.heatmap.heatmap(tuple(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts))) - img.save("08-400-tupleofarrays.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-tupleofarrays.kml") - img = self.heatmap.heatmap(tuple(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts)), weighted=1) - img.save("08-400-tupleofarraysweighted.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-tupleofarrayweighted.kml") - img = self.heatmap.heatmap(list(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts))) - img.save("08-400-arrayofarrays.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-arrayofarrays.kml") - img = self.heatmap.heatmap(list(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts)), weighted=1) - img.save("08-400-arrayofarraysweighted.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-arrayofarraysweighted.kml") - img = self.heatmap.heatmap(sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts),[])) - img.save("08-400-flat.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-flat.kml") - img = self.heatmap.heatmap(sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts),[]), weighted=1) - img.save("08-400-flatweighted.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("08-400-flatweighted.kml") + tt = self.heatmapImage("08-400-tupleoftuples", tuple(map(lambda x_y_z : (x_y_z[0],x_y_z[1]), pts)), saveKML=True) + ttw = self.heatmapImage("08-400-tupleoftuplesweighted", pts , kwargs = { "weighted" : True }, saveKML=True) + at = self.heatmapImage("08-400-arrayoftuples", list(map(lambda x_y_z : (x_y_z[0],x_y_z[1]), pts)), saveKML=True) + atw = self.heatmapImage("08-400-arrayoftuplesweighted", list(pts), kwargs = { "weighted" : True }, saveKML=True) + ta = self.heatmapImage("08-400-tupleofarrays", tuple(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts)), saveKML=True) + taw = self.heatmapImage("08-400-tupleofarraysweighted", tuple(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts)), kwargs = { "weighted" : True }, saveKML=True) + aa = self.heatmapImage("08-400-arrayofarrays", list(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts)), saveKML=True) + aaw = self.heatmapImage("08-400-arrayofarraysweighted", list(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts)), kwargs = { "weighted" : True }, saveKML=True) + f = self.heatmapImage("08-400-flat", sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts),[]), saveKML=True) + fw = self.heatmapImage("08-400-flatweighted", sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts),[]), kwargs = { "weighted" : True }, saveKML=True) + self.assertEqual(tt,ttw) + self.assertEqual(tt,ttw) + self.assertEqual(tt,at) + self.assertEqual(tt,atw) + self.assertEqual(tt,ta) + self.assertEqual(tt,taw) + self.assertEqual(tt,aa) + self.assertEqual(tt,aaw) + self.assertEqual(tt,f) + self.assertEqual(tt,fw) def test_heatmap_area(self): MAX_SIZE=8192 @@ -143,52 +113,41 @@ def test_heatmap_area(self): PPD = float((width-dotsize/2))/(east-west) dotDegrees = dotsize/2/PPD bounds = ((west-dotDegrees, south-dotDegrees),(east+dotDegrees,north+dotDegrees)) - img = self.heatmap.heatmap(pts, size = (width, height), dotsize = dotsize, area=bounds, weighted = 1) - img.save("11-400-areaTest.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("11-400-areaTest.kml") - img = self.heatmap.heatmap(pts, size = (width, height), dotsize = dotsize, weighted = 1) - img.save("11-400-areaTestNormal.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("11-400-areaTestNormal.kml") + #these should be the same except less cutin off from manual area + self.heatmapImage("11-400-areaTest", pts, { "size" : (width, height), "dotsize" : dotsize, "area" : bounds, "weighted" : 1}, saveKML = True) + self.heatmapImage("11-400-areaTestNormal", pts , kwargs = { "size" : (width, height), "dotsize" : dotsize, "weighted" : 1}, saveKML = True) def test_heatmap_random_proj(self): - #4087 should be the same as 'normal' pts = [(random.uniform(-180,180),random.uniform(-90,90)) for x in range(400)] - img = self.heatmap.heatmap(pts) - img.save("09-400-normal.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("09-400-normal.kml") - img = self.heatmap.heatmap(pts,srcepsg='EPSG:4326') - img.save("09-400-EPSG3857.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("09-400-EPSG3857.kml") - img = self.heatmap.heatmap(pts, srcepsg='EPSG:4326',dstepsg='EPSG:4087') - img.save("09-400-EPSG4087.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("09-400-EPSG4087.kml") - img = self.heatmap.heatmap(pts, srcepsg='EPSG:3857',dstepsg='EPSG:4087') - img.save("09-400-EPSG4087.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("09-400-EPSG4087.kml") + norm = self.heatmapImage("09-400-normal", pts, saveKML = True) + #4087 should be the same as 'normal' as no conversion required, kml boundary should be different (not tested) though as not converted to 4326 + epsg4087 = self.heatmapImage("90-400-EPSG4087", pts, kwargs = { "srcepsg" : "EPSG:4087", "dstepsg" : "EPSG:4087"}, saveKML = True) + self.assertEqual(norm,epsg4087) + #4326 should be roughly the same as 'normal' but not the same as WGS84 != 4087 + epsg4326 = self.heatmapImage("09-400-EPSG4326", pts, kwargs = { "srcepsg" : "EPSG:4326", "dstepsg" : "EPSG:4087" }, saveKML = True) + self.assertNotEqual(norm,epsg4326) + #3857DST should be well different + epsg3857DST = self.heatmapImage("09-400-EPSG3857DST", pts, kwargs = { "srcepsg" : "EPSG:4326"}, saveKML = True) + self.assertNotEqual(norm,epsg3857DST) + #testing conversion of src epsg, image is possibly similar do to linearity at the equator but KML boundary should be very different (not tested) + epsg3857 = self.heatmapImage("09-400-EPSG3857", pts, kwargs = { "srcepsg" : "EPSG:3857", "dstepsg" : "EPSG:4087" }, saveKML = True) def test_heatmap_weighted_proj(self): - #normal should be the same as 4087 - #3857 and 4087 should roughly meet at 0,0 as symetrical around the equator pts = [(x*2,x, 1 if x==0 else 0.75) for x in range(-89,90)] - img = self.heatmap.heatmap(pts, size = (2048, 1024), dotsize = 50, weighted = 1) - img.save("10-400-normal.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("10-400-normal.kml") - img = self.heatmap.heatmap(pts,srcepsg='EPSG:4326', size = (2048, 1024), dotsize = 50, weighted = 1) - img.save("10-400-EPSG3857.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("10-400-EPSG3857.kml") - img = self.heatmap.heatmap(pts, srcepsg='EPSG:4326',dstepsg='EPSG:4087', size = (2048, 1024), dotsize = 50, weighted = 1) - img.save("10-400-EPSG4087.png") - self.assertTrue(isinstance(img, Image.Image)) - self.heatmap.saveKML("10-400-EPSG4087.kml") - + norm = self.heatmapImage("10-400-normal", pts, kwargs = { "size" : (2048, 1024), "dotsize" : 50, "weighted" : 1}, saveKML = True) + #4087 should be the same as 'normal' as no conversion required, kml boundary should be different (not tested) though as not converted to 4326 + epsg4087 = self.heatmapImage("10-400-EPSG4087", pts, kwargs = { "srcepsg" : "EPSG:4087", "dstepsg" : "EPSG:4087", "size" : (2048, 1024), "dotsize" : 50, "weighted" : 1}, saveKML = True) + self.assertEqual(norm,epsg4087) + #4326 should be roughly the same as 'normal' but not the same as WGS84 != 4087 + norm = self.heatmapImage("10-400-normal", pts, kwargs = { "size" : (2048, 1024), "dotsize" : 50, "weighted" : 1}, saveKML = True) + epsg4326 = self.heatmapImage("10-400-EPSG4326SRC", pts, kwargs = { "srcepsg" : "EPSG:4326", "dstepsg" : "EPSG:4087", "size" : (2048, 1024), "dotsize" : 50, "weighted" : 1}, saveKML = True) + self.assertNotEqual(norm,epsg4326) + #3857DST and 4087 should roughly meet at 0,0 as symetrical around the equator + epsg3857DST = self.heatmapImage("10-400-EPSG3857DST", pts, kwargs = { "srcepsg" : "EPSG:4326", "size" : (2048, 1024), "dotsize" : 50, "weighted" : 1}, saveKML = True) + self.assertNotEqual(norm,epsg3857DST) + #testing conversion of src epsg, image is possibly similar do to linearity at the equator but KML boundary should be very different (not tested) + epsg3857 = self.heatmapImage("10-400-EPSG3857", pts, kwargs = { "srcepsg" : "EPSG:3857", "dstepsg" : "EPSG:4087", "size" : (2048, 1024), "dotsize" : 50, "weighted" : 1 }, saveKML = True) + def test_heatmap_exceptions(self): #test invalid (empty) heatmap, should print error to stdout From c64a7d5947bfaa3bb0afba8ff8aa0ca0a32a964a Mon Sep 17 00:00:00 2001 From: kwauchope Date: Fri, 22 Aug 2014 22:38:23 +1000 Subject: [PATCH 33/38] removed forced exit if not python 2.6+, added trove classifiers --- heatmap/__init__.py | 3 --- setup.py | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/heatmap/__init__.py b/heatmap/__init__.py index 50c0f0b..4755d4d 100644 --- a/heatmap/__init__.py +++ b/heatmap/__init__.py @@ -5,7 +5,4 @@ except Exception as e: __version__ = 'unknown' -if sys.hexversion < 0x02060000: - raise Exception('Heatmap requires python 2.6 or greater.'); - from .heatmap import Heatmap diff --git a/setup.py b/setup.py index 91de759..e7ba458 100644 --- a/setup.py +++ b/setup.py @@ -52,12 +52,22 @@ def run(self): 'author' : 'Jeffrey J. Guy', 'author_email' : 'jjg@case.edu', 'url' : 'http://jjguy.com/heatmap/', - 'license' : 'MIT License', 'packages' : ['heatmap', ], 'py_modules' : ['heatmap.colorschemes', ], 'ext_modules' : [cHeatmap, ], 'cmdclass' : {'install': post_install, - 'build_ext': mybuild} + 'build_ext': mybuild}, + 'classifiers' : [ + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: MIT License', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Scientific/Engineering :: Visualization', + 'Topic :: Scientific/Engineering :: GIS' + ] } setuptoolskw = { 'install_requires' : ['Pillow'], From 45266f82fb4bd887918f79e8dffa95458a0ebc0d Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 23 Aug 2014 00:45:27 +1000 Subject: [PATCH 34/38] removed no longer needed import --- heatmap/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/heatmap/__init__.py b/heatmap/__init__.py index 4755d4d..10fd00a 100644 --- a/heatmap/__init__.py +++ b/heatmap/__init__.py @@ -1,8 +1,6 @@ -import sys - try: - __version__ = __import__('pkg_resources').get_distribution(__name__).version + __version__ = __import__('pkg_resources').get_distribution(__name__).version except Exception as e: - __version__ = 'unknown' + __version__ = 'unknown' from .heatmap import Heatmap From 170173f6c78b9605ce1252445dab33f6dc12edba Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 23 Aug 2014 13:13:50 +1000 Subject: [PATCH 35/38] no need to malloc if initialising anyway --- heatmap/heatmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heatmap/heatmap.c b/heatmap/heatmap.c index 55f5bf1..442a027 100644 --- a/heatmap/heatmap.c +++ b/heatmap/heatmap.c @@ -98,7 +98,7 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei int height = inf->height; int dotsize = inf->dotsize; - unsigned char* pixels = (unsigned char *)calloc(width*height, sizeof(char)); + unsigned char* pixels = (unsigned char *)malloc(width*height*sizeof(char)); float midpt = dotsize / 2.f; double radius = sqrt(midpt*midpt + midpt*midpt) / 2.f; From ec26754e1b9b7826a6d92c8d84fe6faf43d153e6 Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 23 Aug 2014 17:15:31 +1000 Subject: [PATCH 36/38] small cleanup of typos and extra test not needed --- test/tests.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/tests.py b/test/tests.py index 2271cf8..15c9c36 100644 --- a/test/tests.py +++ b/test/tests.py @@ -10,7 +10,6 @@ import heatmap from heatmap import colorschemes - class TestHeatmap(unittest.TestCase): """unittests for TestHeatmap""" @@ -78,7 +77,6 @@ def test_heatmap_random_datatypes(self): f = self.heatmapImage("08-400-flat", sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1]], pts),[]), saveKML=True) fw = self.heatmapImage("08-400-flatweighted", sum(map(lambda x_y_z : [x_y_z[0],x_y_z[1],x_y_z[2]], pts),[]), kwargs = { "weighted" : True }, saveKML=True) self.assertEqual(tt,ttw) - self.assertEqual(tt,ttw) self.assertEqual(tt,at) self.assertEqual(tt,atw) self.assertEqual(tt,ta) @@ -113,7 +111,7 @@ def test_heatmap_area(self): PPD = float((width-dotsize/2))/(east-west) dotDegrees = dotsize/2/PPD bounds = ((west-dotDegrees, south-dotDegrees),(east+dotDegrees,north+dotDegrees)) - #these should be the same except less cutin off from manual area + #these should be the same except less cutting off the corners with the manual area self.heatmapImage("11-400-areaTest", pts, { "size" : (width, height), "dotsize" : dotsize, "area" : bounds, "weighted" : 1}, saveKML = True) self.heatmapImage("11-400-areaTestNormal", pts , kwargs = { "size" : (width, height), "dotsize" : dotsize, "weighted" : 1}, saveKML = True) @@ -121,7 +119,7 @@ def test_heatmap_random_proj(self): pts = [(random.uniform(-180,180),random.uniform(-90,90)) for x in range(400)] norm = self.heatmapImage("09-400-normal", pts, saveKML = True) #4087 should be the same as 'normal' as no conversion required, kml boundary should be different (not tested) though as not converted to 4326 - epsg4087 = self.heatmapImage("90-400-EPSG4087", pts, kwargs = { "srcepsg" : "EPSG:4087", "dstepsg" : "EPSG:4087"}, saveKML = True) + epsg4087 = self.heatmapImage("09-400-EPSG4087", pts, kwargs = { "srcepsg" : "EPSG:4087", "dstepsg" : "EPSG:4087"}, saveKML = True) self.assertEqual(norm,epsg4087) #4326 should be roughly the same as 'normal' but not the same as WGS84 != 4087 epsg4326 = self.heatmapImage("09-400-EPSG4326", pts, kwargs = { "srcepsg" : "EPSG:4326", "dstepsg" : "EPSG:4087" }, saveKML = True) From a74b0783ad213290a6ca64de180fc2fb52c5a77a Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sat, 23 Aug 2014 23:51:48 +1000 Subject: [PATCH 37/38] cleaned up docco and a few tiny tweaks --- heatmap/heatmap.c | 26 ++++++++++---------- heatmap/heatmap.py | 60 ++++++++++++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/heatmap/heatmap.c b/heatmap/heatmap.c index 442a027..209a9fe 100644 --- a/heatmap/heatmap.c +++ b/heatmap/heatmap.c @@ -15,6 +15,7 @@ struct info int width; int height; + int cPixels; int dotsize; }; @@ -96,13 +97,14 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei { int width = inf->width; int height = inf->height; + int cPixels = inf->cPixels; int dotsize = inf->dotsize; - unsigned char* pixels = (unsigned char *)malloc(width*height*sizeof(char)); + unsigned char* pixels = (unsigned char *)malloc(cPixels*sizeof(char)); float midpt = dotsize / 2.f; - double radius = sqrt(midpt*midpt + midpt*midpt) / 2.f; - double dist = 0.0; + float radius = sqrt(midpt*midpt + midpt*midpt) / 2.f; + float dist = 0.0; int pixVal = 0; int j = 0; int k = 0; @@ -114,7 +116,7 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei if (weighted) inc = 3; // initialize image data to white - for(i = 0; i < (int)width*height; i++) + for(i = 0; i < cPixels; i++) { pixels[i] = 0xff; } @@ -136,7 +138,7 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei if(dist>radius) continue; // stop point contributing to pixels outside its radius - if (weighted) + if(weighted) { pixVal = (int)((multiplier*(dist/radius)+constant)/points[i+2]); } @@ -147,7 +149,7 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei if (pixVal > 255) pixVal = 255; ndx = k*width + j; - if(ndx >= (int)width*height) continue; // ndx can be greater than array bounds + if(ndx >= cPixels) continue; // ndx can be greater than array bounds #ifdef DEBUG printf("pt.x: %.2f pt.y: %.2f j: %d k: %d ndx: %d\n", pt.x, pt.y, j, k, ndx); @@ -164,15 +166,14 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei unsigned char *colorize(struct info *inf, unsigned char* pixels_bw, int *scheme, unsigned char* pixels_color, int opacity) { - int width = inf->width; - int height = inf->height; + int cPixels = inf->cPixels; int i = 0; int pix = 0; int highCount = 0; int alpha = opacity; - for(i = 0; i < (int)width*height; i++) + for(i = 0; i < cPixels; i++) { pix = pixels_bw[i]; @@ -188,7 +189,7 @@ unsigned char *colorize(struct info *inf, unsigned char* pixels_bw, int *scheme, pixels_color[i*4+3] = alpha; } - if (highCount > width*height*0.8) + if (highCount > cPixels*0.8) { fprintf(stderr, "Warning: 80%% of output pixels are over 95%% density.\n"); fprintf(stderr, "Decrease dotsize or increase output image resolution?\n"); @@ -215,16 +216,17 @@ unsigned char *tx(float *points, //basic sanity checks to keep from segfaulting if (NULL == points || NULL == scheme || NULL == pix_color || - w <= 0 || h <= 0 || cPoints <= 1 || opacity < 0 || dotsize <= 0) + w <= 0 || h <= 0 || cPoints <= 1+weighted || cPoints % (2+weighted) != 0 || + opacity < 0 || dotsize <= 0) { fprintf(stderr, "Invalid parameter; aborting.\n"); return NULL; } - inf.dotsize = dotsize; inf.width = w; inf.height = h; + inf.cPixels = w*h; // get min/max x/y values from point list if (boundsOverride == 1) diff --git a/heatmap/heatmap.py b/heatmap/heatmap.py index 868614a..89de89f 100644 --- a/heatmap/heatmap.py +++ b/heatmap/heatmap.py @@ -16,9 +16,9 @@ class Heatmap: """ - Create heatmaps from a list of 2D coordinates. + Create heatmaps from a list of 2D coordinates with optional weighting per coordinate pair. - Heatmap requires the Python Imaging Library and Python 2.5+ for ctypes. + Heatmap requires the Python Imaging Library and Python 2.6+ for Python3 backports. Coordinates autoscale to fit within the image dimensions, so if there are anomalies or outliers in your dataset, results won't be what you expect. You @@ -29,6 +29,9 @@ class Heatmap: are lat/long coordinates. Make your own wardriving maps or visualize the footprint of your wireless network. + For accurate geospatial results it is advised to use the optional [proj] install. + This also allows for output in other coordinate systems such as Mercator. + Most of the magic starts in heatmap(), see below for description of that function. """ @@ -87,28 +90,39 @@ def __init__(self, libpath=None): if not self._heatmap: raise Exception("Heatmap shared library not found in PYTHONPATH.") - def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None, weighted=0, srcepsg=None, dstepsg='EPSG:3857'): + def heatmap(self, points, dotsize=150, opacity=128, size=(1024, 1024), scheme="classic", area=None, + weighted=0, srcepsg=None, dstepsg='EPSG:3857'): """ - points -> an iterable list of tuples, where the contents are the - x,y coordinates to plot. e.g., [(1, 1), (2, 2), (3, 3)] - dotsize -> the size of a single coordinate in the output image in - pixels, default is 150px. Tweak this parameter to adjust - the resulting heatmap. - opacity -> the strength of a single coordiniate in the output image. - Tweak this parameter to adjust the resulting heatmap. - size -> tuple with the width, height in pixels of the output PNG - scheme -> Name of color scheme to use to color the output image. - Use schemes() to get list. (images are in source distro) - area -> Specify bounding coordinates of the output image. Tuple of - tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, - these values are calculated based on the input data. - weighted -> Is the data weighted - srcepsg -> epsg code of the source, set to None to ignore. - If using KML output make sure this is set otherwise either the image - or the overlay coordinates will be out. - dstepsg -> epsg code of the destination, ignored if srcepsg is not set. Defaults to EPSG:3857 (Cylindrical Mercator). - Due to linear interpolation in heatmap.c it only makes sense to use linear output projections. - If outputting to KML for google earth client overlay use EPSG:4087 (World Equidistant Cylindrical) + points -> A representation of the points (x,y values) to process. + Can be a flattened array/tuple or any combination of 2 dimensional + array or tuple iterables i.e. [x1,y1,x2,y2], [(x1,y1),(x2,y2)], etc. + If weights are being used there are expected to be 3 'columns' + in the 2 dimensionable iterable or a multiple of 3 points in the + flat array/tuple i.e. (x1,y1,z1,x2,y2,z2), ([x1,y1,z1],[x2,y2,z2]) etc. + The third (weight) value can be anything but it is + best to have a normalised weight between 0 and 1. + For best performance, if convenient use a flattened array + as this is what is used internally and requires no conversion. + dotsize -> the size of a single coordinate in the output image in + pixels, default is 150px. Tweak this parameter to adjust + the resulting heatmap. + opacity -> the strength of a single coordiniate in the output image. + Tweak this parameter to adjust the resulting heatmap. + size -> tuple with the width, height in pixels of the output PNG + scheme -> Name of color scheme to use to color the output image. + Use schemes() to get list. (images are in source distro) + area -> Specify bounding coordinates of the output image. Tuple of + tuples: ((minX, minY), (maxX, maxY)). If None or unspecified, + these values are calculated based on the input data. + weighted -> Is the data weighted (0 or 1) + srcepsg -> epsg code of the source, set to None to ignore. + If using KML output make sure this is set otherwise either the image + or the overlay coordinates will be out. + dstepsg -> epsg code of the destination, ignored if srcepsg is not set. + Defaults to EPSG:3857 (Cylindrical Mercator). + Due to linear interpolation in heatmap.c it only makes sense to use linear + output projections. If outputting to KML for google earth client overlay use + EPSG:4087 (World Equidistant Cylindrical). """ self.dotsize = dotsize self.opacity = opacity From 32a520613f0bc018ba6932b864a9ee0a5497758b Mon Sep 17 00:00:00 2001 From: kwauchope Date: Sun, 24 Aug 2014 00:20:56 +1000 Subject: [PATCH 38/38] add another parameter check and shift break further up --- heatmap/heatmap.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/heatmap/heatmap.c b/heatmap/heatmap.c index 209a9fe..1d84da1 100644 --- a/heatmap/heatmap.c +++ b/heatmap/heatmap.c @@ -138,6 +138,9 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei if(dist>radius) continue; // stop point contributing to pixels outside its radius + ndx = k*width + j; + if(ndx >= cPixels) continue; // ndx can be greater than array bounds + if(weighted) { pixVal = (int)((multiplier*(dist/radius)+constant)/points[i+2]); @@ -148,9 +151,6 @@ unsigned char* calcDensity(struct info *inf, float *points, int cPoints, int wei } if (pixVal > 255) pixVal = 255; - ndx = k*width + j; - if(ndx >= cPixels) continue; // ndx can be greater than array bounds - #ifdef DEBUG printf("pt.x: %.2f pt.y: %.2f j: %d k: %d ndx: %d\n", pt.x, pt.y, j, k, ndx); #endif @@ -217,7 +217,7 @@ unsigned char *tx(float *points, //basic sanity checks to keep from segfaulting if (NULL == points || NULL == scheme || NULL == pix_color || w <= 0 || h <= 0 || cPoints <= 1+weighted || cPoints % (2+weighted) != 0 || - opacity < 0 || dotsize <= 0) + opacity < 0 || opacity > 255 || dotsize <= 0) { fprintf(stderr, "Invalid parameter; aborting.\n"); return NULL;