【问题标题】:Google Maps heat map color by average weight谷歌地图热图颜色平均重量
【发布时间】:2018-01-27 06:44:38
【问题描述】:

Google Maps iOS SDK 的热图(更具体地说是Google-Maps-iOS-Utils framework)主要通过计算该区域中点的密度来决定渲染该区域的颜色。

但是,我想根据该区域中点的平均重量或强度来选择颜色。

据我了解,这种行为不是内置的(但谁知道呢——文档有点烂)。决定颜色选择的文件是我认为 in /src/Heatmap/GMUHeatmapTileLayer.m这是一个相对较短的文件,但我不太精通Objective-C,所以我很难弄清楚做什么做什么。我认为GMUHeatmapTileLayer.m中的-tileForX:y:zoom:是重要的功能,但我不确定,即使是,我也不知道如何修改它。在此方法的最后,数据首先水平“卷积”,然后垂直“卷积”。我认为这是实际计算强度的地方。不幸的是,我不知道它到底在做什么,而且我害怕改变东西,因为我对 obj-c 很烂。这是这个方法的卷积部分的样子:

- (UIImage *)tileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom {

  // ...

  // Convolve data.
  int lowerLimit = (int)data->_radius;
  int upperLimit = paddedTileSize - (int)data->_radius - 1;
  // Convolve horizontally first.
  float *intermediate = calloc(paddedTileSize * paddedTileSize, sizeof(float));
  for (int y = 0; y < paddedTileSize; y++) {
    for (int x = 0; x < paddedTileSize; x++) {
      float value = intensity[y * paddedTileSize + x];
      if (value != 0) {
        // convolve to x +/- radius bounded by the limit we care about.
        int start = MAX(lowerLimit, x - (int)data->_radius);
        int end = MIN(upperLimit, x + (int)data->_radius);
        for (int x2 = start; x2 <= end; x2++) {
          float scaledKernel = value * [data->_kernel[x2 - x + data->_radius] floatValue];
          // I THINK THIS IS WHERE I NEED TO MAKE THE CHANGE
          intermediate[y * paddedTileSize + x2] += scaledKernel;
          // ^
        }
      }
    }
  }
  free(intensity);
  // Convole vertically to get final intensity.
  float *finalIntensity = calloc(kGMUTileSize * kGMUTileSize, sizeof(float));
  for (int x = lowerLimit; x <= upperLimit; x++) {
    for (int y = 0; y < paddedTileSize; y++) {
      float value = intermediate[y * paddedTileSize + x];
      if (value != 0) {
        int start = MAX(lowerLimit, y - (int)data->_radius);
        int end = MIN(upperLimit, y + (int)data->_radius);
        for (int y2 = start; y2 <= end; y2++) {
          float scaledKernel = value * [data->_kernel[y2 - y + data->_radius] floatValue];
          // I THINK THIS IS WHERE I NEED TO MAKE THE CHANGE
          finalIntensity[(y2 - lowerLimit) * kGMUTileSize + x - lowerLimit] += scaledKernel;
          // ^
        }
      }
    }
  }
  free(intermediate);

  // ...

}

这是每次迭代计算强度的方法,对吧?如果是这样,我该如何改变它以达到我想要的效果(平均,而不是总结颜色,我认为它与强度成正比)。

那么:如何通过修改框架来平均而不是求和强度?

【问题讨论】:

    标签: ios objective-c google-maps heatmap


    【解决方案1】:

    我认为你在正确的轨道上。要计算平均值,您将点总和除以点数。由于您已经计算了总和,我认为一个简单的解决方案是同时保存每个点的计数。如果我理解正确,这就是你必须做的。

    在为总和分配内存时,也为计数分配内存

    // At this place
    float *intermediate = calloc(paddedTileSize * paddedTileSize, sizeof(float));
    // Add this line, calloc will initialize them to zero
    int *counts = calloc(paddedTileSize * paddedTileSize, sizeof(int));
    

    然后在每个循环中增加计数。

    // Below this line (first loop)
    intermediate[y * paddedTileSize + x2] += scaledKernel;
    // Add this
    counts[y * paddedTileSize + x2]++;
    
    // And below this line (second loop)
    finalIntensity[(y2 - lowerLimit) * kGMUTileSize + x - lowerLimit] += scaledKernel;
    // Add this
    counts[(y2 - lowerLimit) * kGMUTileSize + x - lowerLimit]++;
    

    在两个循环之后,您应该有两个数组,一个包含您的总和 finalIntensity,另一个包含您的计数 counts。现在检查这些值并计算平均值。

    for (int y = 0; y < paddedTileSize; y++) {
        for (int x = 0; x < paddedTileSize; x++) {
            int n = y * paddedTileSize + x;
            if (counts[n] != 0)
                finalIntensity[n] = finalIntensity[n] / counts[n];
        }
    }
    free(counts);
    

    finalIntensity 现在应该包含您的平均值。

    如果您愿意,并且其余代码使之成为可能,您可以跳过最后一个循环,而是在使用最终强度值时进行除法。只需将任何后续的finalIntensity[n] 更改为counts[n] == 0 ? finalIntensity[n] : finalIntensity[n] / counts[n]

    【讨论】:

    • 通常最好将calloc()free() 配对。由于counts 只是临时使用,它可以在达到其目的后被释放。不这样做会导致每次运行代码片段时都会发生内存泄漏。但是,如果您确定paddedTileSize 在应用程序的生命周期内不会更改,您当然可以在应用程序启动时执行一次calloc(),并且永远不要释放它。这可能会给你带来一点速度提升。
    • 您知道如何甚至如果可以利用这两种不同颜色显示“是”和“否”票之类的东西吗?我曾考虑按原样使用强度,但现在我意识到每个点都包含每种颜色,但半径不同。
    • 好吧,我没有做过这种必须的工作,但我会尝试的一个想法是使用从(例如)蓝色到红色的色标,给出最强的蓝色值为零,最强的红色为最大值。然后对于介于两者之间的值,分配颜色逐渐从一种褪色到另一种。根据它的呈现方式,可以使用黑色或白色的中间颜色。查看此处接受的答案以及页面底部的颜色范围,以了解我的意思。 stackoverflow.com/questions/15032562/…
    • 这里是另一个关于用两种颜色做渐变的链接。 stackoverflow.com/questions/22218140/…
    • 查看热图源,它确实支持使用任意数量的颜色,还可以为您制作渐变。不确定内置的渐变功能是否可以为您完成这项工作,但为什么不试试呢?我建议从两种基色开始。使用GMUGradient 类及其initWithColors:startPoints:colorMapSize: 方法来创建对象。然后使用setGradient: 将其设置在您的瓷砖上。您将colorMapSize 设置为您想要在基色之间插入的颜色数。
    【解决方案2】:

    我可能刚刚解决了 java 版本的相同问题。

    我的问题是自定义渐变有 12 个不同的值。 但我的实际加权数据不一定包含从 1 到 12 的所有强度值。

    问题是,最高强度值被映射到最高颜色。 此外,附近的 10 个强度为 1 的数据点将获得与强度为 12 的单个点相同的颜色。

    所以创建图块的函数是一个很好的起点:

    Java:

    public Tile getTile(int x, int y, int zoom) {
    // ...
    // Quantize points
        int dim = TILE_DIM + mRadius * 2;
        double[][] intensity = new double[dim][dim];
        int[][] count = new int[dim][dim];
        for (WeightedLatLng w : points) {
            Point p = w.getPoint();
            int bucketX = (int) ((p.x - minX) / bucketWidth);
            int bucketY = (int) ((p.y - minY) / bucketWidth);
            intensity[bucketX][bucketY] += w.getIntensity();
            count[bucketX][bucketY]++;
        }
        // Quantize wraparound points (taking xOffset into account)
        for (WeightedLatLng w : wrappedPoints) {
            Point p = w.getPoint();
            int bucketX = (int) ((p.x + xOffset - minX) / bucketWidth);
            int bucketY = (int) ((p.y - minY) / bucketWidth);
            intensity[bucketX][bucketY] += w.getIntensity();
            count[bucketX][bucketY]++;
        }
        for(int bx = 0; bx < dim; bx++)
            for (int by = 0; by < dim; by++)
                if (count[bx][by] != 0)
                    intensity[bx][by] /= count[bx][by];
    //...
    

    我添加了一个计数器并计算强度的每次增加,然后我检查每个强度并计算平均值。

    对于 C:

    - (UIImage *)tileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom {
    //...
    // Quantize points.
        int paddedTileSize = kGMUTileSize + 2 * (int)data->_radius;
        float *intensity = calloc(paddedTileSize * paddedTileSize, sizeof(float));
        int *count = calloc(paddedTileSize * paddedTileSize, sizeof(int));
        for (GMUWeightedLatLng *item in points) {
            GQTPoint p = [item point];
            int x = (int)((p.x - minX) / bucketWidth);
            // Flip y axis as world space goes south to north, but tile content goes north to south.
            int y = (int)((maxY - p.y) / bucketWidth);
            // If the point is just on the edge of the query area, the bucketing could put it outside
            // bounds.
            if (x >= paddedTileSize) x = paddedTileSize - 1;
            if (y >= paddedTileSize) y = paddedTileSize - 1;
            intensity[y * paddedTileSize + x] += item.intensity;
            count[y * paddedTileSize + x] ++;
        }
        for (GMUWeightedLatLng *item in wrappedPoints) {
            GQTPoint p = [item point];
            int x = (int)((p.x + wrappedPointsOffset - minX) / bucketWidth);
            // Flip y axis as world space goes south to north, but tile content goes north to south.
            int y = (int)((maxY - p.y) / bucketWidth);
            // If the point is just on the edge of the query area, the bucketing could put it outside
            // bounds.
            if (x >= paddedTileSize) x = paddedTileSize - 1;
            if (y >= paddedTileSize) y = paddedTileSize - 1;
            // For wrapped points, additional shifting risks bucketing slipping just outside due to
            // numerical instability.
            if (x < 0) x = 0;
            intensity[y * paddedTileSize + x] += item.intensity;
            count[y * paddedTileSize + x] ++;
        }
        for(int i=0; i < paddedTileSize * paddedTileSize; i++)
            if (count[i] != 0)
                intensity[i] /= count[i];
    

    接下来是卷积。 我在那里所做的是确保计算的值不会超过我数据中的最大值。

    Java:

    // Convolve it ("smoothen" it out)
    double[][] convolved = convolve(intensity, mKernel, mMaxAverage);
    
    // the mMaxAverage gets set here:
    public void setWeightedData(Collection<WeightedLatLng> data) {
    // ...
        // Add points to quad tree
        for (WeightedLatLng l : mData) {
            mTree.add(l);
            mMaxAverage = Math.max(l.getIntensity(), mMaxAverage);
        }
    // ...
    // And finally the convolve method:
    static double[][] convolve(double[][] grid, double[] kernel, double max) {
    // ...
        intermediate[x2][y] += val * kernel[x2 - (x - radius)];
        if (intermediate[x2][y] > max) intermediate[x2][y] = max;
    // ...
        outputGrid[x - radius][y2 - radius] += val * kernel[y2 - (y - radius)];
        if (outputGrid[x - radius][y2 - radius] > max ) outputGrid[x - radius][y2 - radius] = max;
    

    对于 C:

    // To get the maximum average you could do that here:
    - (void)setWeightedData:(NSArray<GMUWeightedLatLng *> *)weightedData {
        _weightedData = [weightedData copy];
        for (GMUWeightedLatLng *dataPoint in _weightedData)
            _maxAverage = Math.max(dataPoint.intensity, _maxAverage)
    // ...
    // And then simply in the convolve section
        intermediate[y * paddedTileSize + x2] += scaledKernel;
        if (intermediate[y * paddedTileSize + x2] > _maxAverage) 
            intermediate[y * paddedTileSize + x2] = _maxAverage;
    // ...
       finalIntensity[(y2 - lowerLimit) * kGMUTileSize + x - lowerLimit] += scaledKernel;
       if (finalIntensity[(y2 - lowerLimit) * kGMUTileSize + x - lowerLimit] > _maxAverage)
           finalIntensity[(y2 - lowerLimit) * kGMUTileSize + x - lowerLimit] = _maxAverage;
    

    最后是着色

    Java:

    // The maximum intensity is simply the size of my gradient colors array (or the starting points) 
    Bitmap bitmap = colorize(convolved, mColorMap, mGradient.mStartPoints.length);
    

    对于 C:

    // Generate coloring
    // ...
    float max = [data->_maxIntensities[zoom] floatValue];
    max = _gradient.startPoints.count;
    

    我是用 Java 做的,它对我有用,但不确定 C 代码。

    您必须使用半径,甚至可以编辑内核。因为我发现当我有很多同质数据(即强度变化很小,或者一般数据很多)时,热图会退化为单色叠加,因为边缘的渐变会变得更小并且更小。

    但无论如何希望这会有所帮助。

    //埃里克

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-04
      • 2012-04-15
      • 1970-01-01
      • 1970-01-01
      • 2015-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多