从给定的数据集 [纬度、经度、密度] 生成 KML 热图

发布于 2024-08-24 05:27:03 字数 575 浏览 5 评论 0原文

我希望构建一个静态 KML(Google 地球标记)文件,该文件以 [lat、lon、densis] 元组的形式显示一些给定数据集的热图样式渲染。

我拥有的一个非常简单的数据集是人口密度。

我的要求是:

  • 必须能够输入给定纬度、经度的数据
  • 必须能够指定该纬度、经度的数据密度
  • 必须导出到 KML

这些要求与该项目的语言无关,因为我将生成这些离线文件以构建在其他地方使用的 KML。

我看过一些项目,最值得注意的是 heatmap.py,它是 gheat 使用 Python 进行 KML 导出。我已经碰壁了,因为我迄今为止发现的项目都依赖于根据输入算法的 [lat, lon] 点的密度来构建热图。

如果我缺少一种明显的方法来调整我的数据集以仅输入 [lat, lon] 元组,但使用我拥有的密度值调整我输入它们的方式,我很想知道!

I am looking to build a static KML (Google Earth markup) file which displays a heatmap-style rendering of a few given data sets in the form of [lat, lon, density] tuples.

A very straightforward data set I have is for population density.

My requirements are:

  • Must be able to feed in data for a given lat, lon
  • Must be able to specify the density of the data at that lat, lon
  • Must export to KML

The requirements are language agnostic for this project as I will be generating these files offline in order to build the KML used elsewhere.

I have looked at a few projects, most notably heatmap.py, which is a port of gheat in Python with KML export. I have hit a brick wall in the sense that the projects I have found to date all rely on building the heatmap from the density of [lat, lon] points fed into the algorithm.

If I am missing an obvious way to adapt my data set to feed in just the [lat, lon] tuples but adjusting how I feed them using the density values I have, I would love to know!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

时光无声 2024-08-31 05:27:03

嘿,威尔,heatmap.py 是我。你的请求是一个很常见的请求,并且在我要解决的事情清单上。我还不太确定如何以一般方式做到这一点;用 heatmap.py 的说法,使用每点的点大小而不是现在的全局点大小会很简单,但我不确定这是否能满足真正的需求。我的目标是在 2010 年夏季发布,但你也可以自己制作这个模组。

您可以尝试搜索核密度估计器工具;这就是统计学家所说的热图。 R 有一些很好的内置工具,您可以使用它们来更快地满足您的需求。

祝你好运!

Hey Will, heatmap.py is me. Your request is a common-enough one and is on my list of things to address. I'm not quite sure yet how to do so in a general fashion; in heatmap.py parlance, it would be straightforward to have a per-point dotsize instead of a global dotsize as it is now, but I'm not sure that will address the true need. I'm aiming for a summer 2010 release, but you could probably make this mod yourself.

You may try searching for Kernel Density Estimator tools; that's what the statisticians call heatmaps. R has some good built-in tools you can use that might satisfy your need more quickly.

good luck!

毅然前行 2024-08-31 05:27:03

我更新了 heatmap.py 脚本,以便您可以指定每个点的密度。我将我的更改上传到我的博客。但不确定它是否会完全满足您的要求!

干杯,
Alex

更新 [2020 年 11 月 13 日]
我不久前存档了我的博客,因此链接不再有效,因此以下是更改供参考:

diff 文件:

--- __init__.py 2010-09-14 08:40:35.829079482 +0100
+++ __init__.py.mynew   2010-09-06 14:50:10.394447647 +0100
@@ -1,5 +1,5 @@
 #heatmap.py v1.0 20091004
-from PIL import Image,ImageChops
+from PIL import Image,ImageChops,ImageDraw
 import os
 import random
 import math
@@ -43,10 +43,13 @@
     Most of the magic starts in heatmap(), see below for description of that function.
     """
     def __init__(self):
+        self.minIntensity = 0
+        self.maxIntensity = 0
         self.minXY = ()
         self.maxXY = ()
+       
 
-    def heatmap(self, points, fout, dotsize=150, opacity=128, size=(1024,1024), scheme="classic"):
+    def heatmap(self, points, fout, dotsize=150, opacity=128, size=(4048,1024), scheme="classic", area=(-180,180,-90,90)):
         """
         points  -> an iterable list of tuples, where the contents are the 
                    x,y coordinates to plot. e.g., [(1, 1), (2, 2), (3, 3)]
@@ -59,33 +62,41 @@
         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 the coordinates covered by the resulting image 
+                   (could create an image to cover area larger than the max/
+                   min values given in the points list) 
         """
-        
+        print("Starting heatmap")
         self.dotsize = dotsize
         self.opacity = opacity
         self.size = size
         self.imageFile = fout
- 
+
         if scheme not in self.schemes():
             tmp = "Unknown color scheme: %s.  Available schemes: %s"  % (scheme, self.schemes())                           
             raise Exception(tmp)
 
-        self.minXY, self.maxXY = self._ranges(points)
-        dot = self._buildDot(self.dotsize)
+        self.minXY = (area[0],area[2])
+        self.maxXY = (area[1],area[3])
 
+        self.minIntensity, self.maxIntensity = self._intensityRange(points)
+        
         img = Image.new('RGBA', self.size, 'white')
-        for x,y in points:
+        for x,y,z in points:
+            dot = self._buildDot(self.dotsize,z)
             tmp = Image.new('RGBA', self.size, 'white')
             tmp.paste( dot, self._translate([x,y]) )
             img = ImageChops.multiply(img, tmp)
 
-
+        print("All dots built")
         colors = colorschemes.schemes[scheme]
         img.save("bw.png", "PNG")
+        print("Saved temp b/w image")
+        print("Colourising")
         self._colorize(img, colors)
 
         img.save(fout, "PNG")
-
+        print("Completed colourising and saved final image %s" % fout)
     def saveKML(self, kmlFile):
         """ 
         Saves a KML template to use with google earth.  Assumes x/y coordinates 
@@ -110,17 +121,19 @@
         """
         return colorschemes.schemes.keys() 
 
-    def _buildDot(self, size):
+    def _buildDot(self, size,intensity):
         """ builds a temporary image that is plotted for 
             each point in the dataset"""
+        
+        intsty = self._calcIntensity(intensity)
+        print("building dot... %d: %f" % (intensity,intsty))
+
         img = Image.new("RGB", (size,size), 'white')
-        md = 0.5*math.sqrt( (size/2.0)**2 + (size/2.0)**2 )
-        for x in range(size):
-            for y in range(size):
-                d = math.sqrt( (x - size/2.0)**2 + (y - size/2.0)**2 )
-                rgbVal = int(200*d/md + 50)
-                rgb = (rgbVal, rgbVal, rgbVal)
-                img.putpixel((x,y), rgb)
+        draw  =  ImageDraw.Draw(img)
+        shade = 256/(size/2)
+        for x in range (int(size/2)):
+            colour = int(256-(x*shade*intsty))
+            draw.ellipse((x,x,size-x,size-x),(colour,colour,colour))
         return img
 
     def _colorize(self, img, colors):
@@ -139,7 +152,7 @@
                 rgba.append(alpha) 
 
                 img.putpixel((x,y), tuple(rgba))
-            
+     
     def _ranges(self, points):
         """ walks the list of points and finds the 
         max/min x & y values in the set """
@@ -153,6 +166,23 @@
             
         return ((minX, minY), (maxX, maxY))
 
+    def _calcIntensity(self,z):
+        return (z/self.maxIntensity)        
+               
+    def _intensityRange(self, points):
+        """ walks the list of points and finds the 
+        max/min points of intensity
+        """   
+        minZ = points[0][2]
+        maxZ = minZ
+        
+        for x,y,z in points:
+            minZ = min(z, minZ)
+            maxZ = max(z, maxZ)
+        
+        print("(minZ, maxZ):(%d, %d)" % (minZ,maxZ))
+        return (minZ, maxZ)
+        
     def _translate(self, point):
         """ translates x,y coordinates from data set into 
         pixel offsets."""

和演示脚本:

import heatmap
import random
import MySQLdb
import math

print "starting script..."

db = MySQLdb.connect(host="localhost", # your host, usually localhost
                     user="username", # your username
                      passwd="password", # your password
                      db="database") # name of the data base
cur = db.cursor() 

minLng = -180
maxLng = 180
minLat = -90
maxLat = 90

# create and execute the query
query = "SELECT lat, lng, intensity FROM mytable \
            WHERE %f<=tllat AND tllat<=%f \
            AND %f<=tllng AND tllng<=%f" % (minLat,maxLat,minLng,maxLng)

cur.execute(query)

pts = []
# print all the first cell of all the rows
for row in cur.fetchall() :
    print (row[1],row[0],row[2])
    # work out the mercator projection for latitute x = asinh(tan(x1))
    proj = math.degrees(math.asinh(math.tan(math.radians(row[0]))))
    print (row[1],proj,row[2])
    print "-"*15
    if (minLat < proj and proj < maxLat):
        pts.append((row[1],proj,row[2]))

print "Processing %d points..." % len(pts)

hm = heatmap.Heatmap()
hm.heatmap(pts, "bandwidth2.png",30,155,(1024,512),'fire',(minLng,maxLng,minLat,maxLat))

I updated the heatmap.py script so you can specify a density for each point. I uploaded my changes to my blog. Not sure if it'll do exactly what you want though!

Cheers,
Alex

Update [13 Nov 2020]
I archived my blog a while back, so the link no longer works, so for reference here are the changes:

the diff file:

--- __init__.py 2010-09-14 08:40:35.829079482 +0100
+++ __init__.py.mynew   2010-09-06 14:50:10.394447647 +0100
@@ -1,5 +1,5 @@
 #heatmap.py v1.0 20091004
-from PIL import Image,ImageChops
+from PIL import Image,ImageChops,ImageDraw
 import os
 import random
 import math
@@ -43,10 +43,13 @@
     Most of the magic starts in heatmap(), see below for description of that function.
     """
     def __init__(self):
+        self.minIntensity = 0
+        self.maxIntensity = 0
         self.minXY = ()
         self.maxXY = ()
+       
 
-    def heatmap(self, points, fout, dotsize=150, opacity=128, size=(1024,1024), scheme="classic"):
+    def heatmap(self, points, fout, dotsize=150, opacity=128, size=(4048,1024), scheme="classic", area=(-180,180,-90,90)):
         """
         points  -> an iterable list of tuples, where the contents are the 
                    x,y coordinates to plot. e.g., [(1, 1), (2, 2), (3, 3)]
@@ -59,33 +62,41 @@
         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 the coordinates covered by the resulting image 
+                   (could create an image to cover area larger than the max/
+                   min values given in the points list) 
         """
-        
+        print("Starting heatmap")
         self.dotsize = dotsize
         self.opacity = opacity
         self.size = size
         self.imageFile = fout
- 
+
         if scheme not in self.schemes():
             tmp = "Unknown color scheme: %s.  Available schemes: %s"  % (scheme, self.schemes())                           
             raise Exception(tmp)
 
-        self.minXY, self.maxXY = self._ranges(points)
-        dot = self._buildDot(self.dotsize)
+        self.minXY = (area[0],area[2])
+        self.maxXY = (area[1],area[3])
 
+        self.minIntensity, self.maxIntensity = self._intensityRange(points)
+        
         img = Image.new('RGBA', self.size, 'white')
-        for x,y in points:
+        for x,y,z in points:
+            dot = self._buildDot(self.dotsize,z)
             tmp = Image.new('RGBA', self.size, 'white')
             tmp.paste( dot, self._translate([x,y]) )
             img = ImageChops.multiply(img, tmp)
 
-
+        print("All dots built")
         colors = colorschemes.schemes[scheme]
         img.save("bw.png", "PNG")
+        print("Saved temp b/w image")
+        print("Colourising")
         self._colorize(img, colors)
 
         img.save(fout, "PNG")
-
+        print("Completed colourising and saved final image %s" % fout)
     def saveKML(self, kmlFile):
         """ 
         Saves a KML template to use with google earth.  Assumes x/y coordinates 
@@ -110,17 +121,19 @@
         """
         return colorschemes.schemes.keys() 
 
-    def _buildDot(self, size):
+    def _buildDot(self, size,intensity):
         """ builds a temporary image that is plotted for 
             each point in the dataset"""
+        
+        intsty = self._calcIntensity(intensity)
+        print("building dot... %d: %f" % (intensity,intsty))
+
         img = Image.new("RGB", (size,size), 'white')
-        md = 0.5*math.sqrt( (size/2.0)**2 + (size/2.0)**2 )
-        for x in range(size):
-            for y in range(size):
-                d = math.sqrt( (x - size/2.0)**2 + (y - size/2.0)**2 )
-                rgbVal = int(200*d/md + 50)
-                rgb = (rgbVal, rgbVal, rgbVal)
-                img.putpixel((x,y), rgb)
+        draw  =  ImageDraw.Draw(img)
+        shade = 256/(size/2)
+        for x in range (int(size/2)):
+            colour = int(256-(x*shade*intsty))
+            draw.ellipse((x,x,size-x,size-x),(colour,colour,colour))
         return img
 
     def _colorize(self, img, colors):
@@ -139,7 +152,7 @@
                 rgba.append(alpha) 
 
                 img.putpixel((x,y), tuple(rgba))
-            
+     
     def _ranges(self, points):
         """ walks the list of points and finds the 
         max/min x & y values in the set """
@@ -153,6 +166,23 @@
             
         return ((minX, minY), (maxX, maxY))
 
+    def _calcIntensity(self,z):
+        return (z/self.maxIntensity)        
+               
+    def _intensityRange(self, points):
+        """ walks the list of points and finds the 
+        max/min points of intensity
+        """   
+        minZ = points[0][2]
+        maxZ = minZ
+        
+        for x,y,z in points:
+            minZ = min(z, minZ)
+            maxZ = max(z, maxZ)
+        
+        print("(minZ, maxZ):(%d, %d)" % (minZ,maxZ))
+        return (minZ, maxZ)
+        
     def _translate(self, point):
         """ translates x,y coordinates from data set into 
         pixel offsets."""

and a demo script:

import heatmap
import random
import MySQLdb
import math

print "starting script..."

db = MySQLdb.connect(host="localhost", # your host, usually localhost
                     user="username", # your username
                      passwd="password", # your password
                      db="database") # name of the data base
cur = db.cursor() 

minLng = -180
maxLng = 180
minLat = -90
maxLat = 90

# create and execute the query
query = "SELECT lat, lng, intensity FROM mytable \
            WHERE %f<=tllat AND tllat<=%f \
            AND %f<=tllng AND tllng<=%f" % (minLat,maxLat,minLng,maxLng)

cur.execute(query)

pts = []
# print all the first cell of all the rows
for row in cur.fetchall() :
    print (row[1],row[0],row[2])
    # work out the mercator projection for latitute x = asinh(tan(x1))
    proj = math.degrees(math.asinh(math.tan(math.radians(row[0]))))
    print (row[1],proj,row[2])
    print "-"*15
    if (minLat < proj and proj < maxLat):
        pts.append((row[1],proj,row[2]))

print "Processing %d points..." % len(pts)

hm = heatmap.Heatmap()
hm.heatmap(pts, "bandwidth2.png",30,155,(1024,512),'fire',(minLng,maxLng,minLat,maxLat))
那小子欠揍 2024-08-31 05:27:03

我认为做到这一点的一种方法是创建一个(更大的)元组列表,其中每个点根据该点的密度重复。高密度点由许多彼此重叠的点表示,而低密度点则由很少的点表示。因此,您可以使用 [(120.7, 82.5), (120.7, 82.5), (130.6, 81.5)](一个相当乏味的数据集)。

一个可能的问题是您的密度很可能是浮点数,而不是整数,因此您应该对数据进行标准化和舍入。进行转换的一种方法是这样的:(

def dens2points (dens_tups):
    min_dens = dens_tups[0][2]
    for tup in dens_tups:
        if (min_dens > tup[2]):
           min_dens = tup[2]
    print min_dens

    result = []
    for tup in dens_tups:
        for i in range(int(tup[2]/min_dens)):
            result.append((tup[0],tup[1]))
    return result

if __name__ == "__main__":
    input = [(10, 10, 20.0),(5, 5, 10.0),(10,10,0.9)]
    output = dens2points(input)
    print input
    print output

这不是很Pythonic,但似乎适用于简单的测试用例)。该子例程应将您的数据转换为 heatmap.py 接受的形式。经过一点努力,我认为子例程可以减少到两行。

I think one way to do this is to create a (larger) list of tuples with each point repeated according to the density at that point. A point with a high density is represented by lots of points on top of each other while a point with a low density has few points. So instead of: [(120.7, 82.5, 2), (130.6, 81.5, 1)] you would use [(120.7, 82.5), (120.7, 82.5), (130.6, 81.5)] (a fairly dull dataset).

One possible issue is that your densities may well be floats, not integers, so you should normalize and round the data. One way to do the conversion is something like this:

def dens2points (dens_tups):
    min_dens = dens_tups[0][2]
    for tup in dens_tups:
        if (min_dens > tup[2]):
           min_dens = tup[2]
    print min_dens

    result = []
    for tup in dens_tups:
        for i in range(int(tup[2]/min_dens)):
            result.append((tup[0],tup[1]))
    return result

if __name__ == "__main__":
    input = [(10, 10, 20.0),(5, 5, 10.0),(10,10,0.9)]
    output = dens2points(input)
    print input
    print output

(which isn't very pythonic, but seems to work for the simple test case). This subroutine should convert your data into a form that is accepted by heatmap.py. With a little effort I think the subroutine can be reduced to two lines.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文