I have been trying to aggregate points in QGIS for couple of weeks now. I am developing a small plugin that should allow simple Hexabinning using Uber's h3 plugin. Upon clicking the hexabinning button, the following output should be shown. The output is achieved using the qgis-h3-toolkit-plugin: https://github.com/arongergely/qgis-h3-toolkit-plugin

enter image description here

However, I have found it very hard to integrate code from this plugin. I have been instantiating all the three classes and calling their functions, but there are so many different parameters. See the three classes and functions here https://github.com/arongergely/qgis-h3-toolkit-plugin/blob/main/h3_toolkit/processing/algorithms.py

In my plugin, I have a slider which takes the hexabinning resolution input from the user. Then upon clicking the 'Bin' button, it should call a script/function and the above output should be shown. I am trying to write is a simple script that shows the density of points in hexagons.

I have run the following script: it displays the hexagons, but does not show density (from https://github.com/maphew/mhwcode/blob/1ef8338e20ff24ddbe741af225e556b9d81ec416/gis/qgis/h3-grid-from-layer.py)

def binning_function(self):
    # self.hextest = HexTest()
    # ed = self.hextest.begin()
    # er = self.hextest.run()        
    print('binning function')

    self.debug = False

    ###---------- Edit these variables ----------
    # Min & max h3 resolution levels, from 0 to 15 (global to sub-meter)
    # High resolutions over broad areas can be slow and consume a lot of storage space
    # https://h3geo.org/docs/core-library/restable
    # Resolution 7 is ~2,000m across, 9 is ~320m across, 11 is ~45m (in YT Albers)
    self.min_resolution = 0
    self.max_resolution = 9

    # Output files are {prefix}_{resolution}: Hex_3, Hex_4, ...
    self.out_name_prefix = "Hex"

    self.geographic_coordsys = "EPSG:4617"  # e.g. WGS84, NAD83(CSRS)
    self.output_projection = "EPSG:3579"  # placeholder, not currently used
    # --------------------------------------------

    projectPath = os.path.dirname(QgsProject.instance().fileName())
    self.geo_csrs = QgsCoordinateReferenceSystem(self.geographic_coordsys)
    self.out_csrs = QgsCoordinateReferenceSystem(self.output_projection)

    dataPath = os.path.join(projectPath, "data/")
    if not os.path.exists(dataPath):

    #instead of chooser, just use active layer, and selected features within that layer
    self.mylayer = iface.activeLayer()
    if self.mylayer.selectedFeatures():
        params = {'INPUT':self.mylayer, 'OUTPUT':'memory:sel'}
        self.mylayer = processing.run("qgis:saveselectedfeatures", params)["OUTPUT"]
        if self.debug:


def log(self, item):
    return QgsMessageLog.logMessage(str(item))

def proj_to_geo(self, in_layer):
    """Project to geographic coordinate system, in memory.
    H3 needs all coordinates in decimal degrees"""
    params = {
        "INPUT": self.mylayer,
        "TARGET_CRS": self.geographic_coordsys,
        "OUTPUT": "memory:dd_",
    geo_lyr = processing.run("native:reprojectlayer", params)["OUTPUT"]
    if self.debug:
    return geo_lyr

def poly_from_extent(self, layer):
    """Return polygon as coordinate list from layer's extent
        [(-142.0, 74.0), (-115.0, 74.0), (-115.0, 54.0), (-142.0, 54.0)]

    Adapted from
    ext = layer.extent()
    xmin = ext.xMinimum()
    xmax = ext.xMaximum()
    ymin = ext.yMinimum()
    ymax = ext.yMaximum()
    return [(xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin)]

def hexes_within_layer_extent(self, layer, level):
    """Return list of HexID within layer's extent
    In: qgis layer object, hex resolution level (0-15)
    Out: ['8412023ffffffff', '84029d5ffffffff', '8413a93ffffffff']
    ext_poly = self.poly_from_extent(layer)
    hex_ids = set(h3.polyfill_polygon(ext_poly, res=level, lnglat_order=True))
    self.log(f"Hex IDs within extent poly: {str(len(hex_ids))}")
    return hex_ids

def run(self):
    geo_layer = self.proj_to_geo(self.mylayer)

    # For each resolution level fetch geometry of each hex feature and write to shapefile with id
    for res in range(self.min_resolution, self.max_resolution + 1):
        self.log("Resolution: {res}")
        fields = QgsFields()
        fields.append(QgsField("id", QVariant.String))
        shpfile = os.path.join(self.dataPath, f"{self.out_name_prefix}_{res}.shp")
        writer = QgsVectorFileWriter(
            shpfile, "UTF8", fields, QgsWkbTypes.Polygon, driverName="ESRI Shapefile"
        features = []
        for id in set(self.hexes_within_layer_extent(geo_layer, res)):
            f = QgsFeature()
                        # note reversing back to X,Y
                        [QgsPointXY(c[1], c[0]) for c in h3.h3_to_geo_boundary(id)]
            if self.debug:
                self.log(f"Hex: {id} " + str(h3.h3_to_geo_boundary(id)))
        del writer
        self.log("Features out: " + str(len(features)))

        processing.run("qgis:definecurrentprojection", {"INPUT": shpfile, "CRS": self.geo_csrs})

        layer = QgsVectorLayer(shpfile, f"{self.out_name_prefix} {res}", "ogr")

When the above binning_function() procedure is called from my plugin, the following is output. Only the hexabins are shown. I would like to see the counts for each hexabin, and the output should be the same as the first output I have posted.

The count function There is count performed in lines 54-62 of this script: https://github.com/ThomasG77/30DayMapChallenge/blob/master/day4_hexagons/data/h3-processing.py

There is also count performed starting from line 497 of the H3-toolkit here https://github.com/arongergely/qgis-h3-toolkit-plugin/blob/main/h3_toolkit/processing/algorithms.py (see lines 666-715)

Also, I just want one layer created as output - not the 0-9 layers. The output should be based on hexabinning value chosen by the user through a QT horizontal slider.

enter image description here

My dataset: https://mapsafe.xyz/all_clusters_kamloops.zip

Related Post: Using Uber H3 hexagon grids in QGIS


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.