# Node to wrap h3-js from https://github.com/uber/h3-js

**URL:** <https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598>\
**Category:** Developing Nodes\
**Created:** [14 November 2022 17:26 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598 "2022-11-14T17:26:58Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![mrdvt92](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/mrdvt92/32/33122_2.png) [@mrdvt92](https://discourse.nodered.org/u/mrdvt92)\
**Post date:** [14 November 2022 17:26 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598/1 "2022-11-14T17:26:58Z")

</div>

I'd like to add Uber H3 cells to my node-red-contrib-web-worldmap display. But, I'm not quite sure how to take a JavaScript lib and then wrap it with a flow node. Ideally, what I like to figure out is how to take the lib and then make a few nodes from it. In my mind, I see these nodes are needed.

Wrapper for

- polygonToCells (c api: polygonToCells) (e.g. take a bounding box and return all of the h3 cells in that box)
  - msg.polygon, msg.level to =\> msg.payload = [{cell}, {cell}, ...]
  - where polygon = [[lat, lon], ...] or [{lat:?, lon:?}, ...] or both

- latLngToCell (geoToH3) (e.g. take coordinates and return the h3 cells that contains that point)
  - msg.lat, msg.lon, msg.level to =\> msg.payload = {cell}

- H3Index (e.g. take h3 index (as string or 64-bit int and return the h3 cells that contains that point)
  - msg.index =\> msg.payload = {cell} (index = 64-bit int)
  - msg.string =\> msg.payload = {cell} (stringToH3 string = hexadecimal string e.g. 8a2aaaa2e747fff)

- cellToParent (c lib:h3ToParent)
  - msg.payload(cell object), msg.level(default = +1) =\> msg.payload = {cell}

- cellToChildren (c lib:h3ToChildren)
  - msg.payload(cell object), msg.level(default = -1) =\> msg.payload = [{cell},{cell},...]

Where the h3 cell object would have

- .index = 64-bit Integer
- .string = String formatted index (c api: h3ToString)
- .boundary = [point, point, point, point, point, point] (c api: h3ToGeoBoundary)
- .center = {lat:11, lon:22} (c api: h3ToGeo)
- .resolution = 7 (c api: h3GetResolution)
- others?

Where the point object would be

- {lat:? lon:?} but any points as inputs would accept both [lat,lon] as well as {lat:?, lon:?}

What do you think? Where would I even start?

---

<div class="post-metadata">

**Author:** ![dceejay](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dceejay/32/38_2.png) [@dceejay](https://discourse.nodered.org/u/dceejay)\
**Post date:** [14 November 2022 19:22 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598/2 "2022-11-14T19:22:00Z")

</div>

How do you see this integrating with worldmap for example ? (I don't know anything about h3-js) . Do you have something that gives you h3 "co-ordinates" you want to plot on the map ? or are you trying to draw the hexagons on the map ? or is it the other way round ? you want to take things from the map (like clicks) and convert them into h3 cells ?

---

<div class="post-metadata">

**Author:** ![mrdvt92](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/mrdvt92/32/33122_2.png) [@mrdvt92](https://discourse.nodered.org/u/mrdvt92)\
**Post date:** [14 November 2022 23:14 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598/3 "2022-11-14T23:14:37Z")

</div>

I need to show h3 level 6,7,8,9 hex grids when the zoom is appropriate. I plan to use the bounding box to pull the appropriate hex cells.

- on boundary change
- when zoom between x and y set level z
- if new level clear layer
- get cells for level from boundary
- diff list (+new -old)
- format
- plot

---

<div class="post-metadata">

**Author:** ![dceejay](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/dceejay/32/38_2.png) [@dceejay](https://discourse.nodered.org/u/dceejay)\
**Post date:** [15 November 2022 09:27 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598/4 "2022-11-15T09:27:32Z")

</div>

To start with rather than writing a load of nodes I would use the worldmap in node can report the bounds and zoom on every change of view. Feed that to a function node that called the h3-js library (add the library to the setup tab in the function) - and create whatever polylines are required... and then feed those back to the map.

Something like this

```auto
[{"id":"15f8b79cf5fd3f8a","type":"worldmap","z":"27d4cd61f8c2d5ed","name":"","lat":"","lon":"","zoom":"","layer":"","cluster":"","maxage":"","usermenu":"show","layers":"show","panit":"false","panlock":"false","zoomlock":"false","hiderightclick":"false","coords":"false","showgrid":"false","showruler":"false","allowFileDrop":"false","path":"/worldmap","overlist":"DR,CO,RA,DN,HM","maplist":"OSMG,OSMC,EsriC,EsriS,EsriT,EsriDG,UKOS","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":570,"y":60,"wires":[]},{"id":"48fa1be596e9db93","type":"worldmap in","z":"27d4cd61f8c2d5ed","name":"","path":"/worldmap","events":"bounds","x":195,"y":135,"wires":[["c7b326e1ad1e914b","13bdc2149aee0ab3"]]},{"id":"c7b326e1ad1e914b","type":"debug","z":"27d4cd61f8c2d5ed","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":570,"y":135,"wires":[]},{"id":"13bdc2149aee0ab3","type":"function","z":"27d4cd61f8c2d5ed","name":"function 2","func":"\nnode.send({ payload: { \"command\": { \"clear\": \"HEX\" } }})\n\nvar p = msg.payload;\nif (p.zoom < 5) { return; }\n\n// Get the set of hexagons within a polygon\nconst polygon = [\n [p.south, p.west],\n [p.north, p.west],\n [p.north, p.east],\n [p.south, p.east]\n];\nconst hexagons = h3.polygonToCells(polygon, parseInt(p.zoom - 5));\n// -> ['872830828ffffff', '87283082effffff', ...]\nnode.warn(hexagons)\n\n\nvar allhex = hexagons.map(x => {\n const hex = h3.cellToBoundary(x);\n hex.push(hex[0]);\n return hex;\n})\n\nnode.warn(allhex);\n\nallhex.forEach((x,i) => {\n const p = {\n name: \"Hex\"+i,\n layer: \"HEX\",\n line: x\n };\n node.send({payload:p});\n})\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"h3","module":"h3-js"}],"x":375,"y":105,"wires":[["15f8b79cf5fd3f8a"]]}]

```

---

<div class="post-metadata">

**Author:** ![mrdvt92](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/mrdvt92/32/33122_2.png) [@mrdvt92](https://discourse.nodered.org/u/mrdvt92)\
**Post date:** [15 November 2022 21:44 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598/5 "2022-11-15T21:44:58Z")

</div>

> Feed that to a function node that called the h3-js library (add the library to the setup tab in the function)

I don't understand how you "add the library to the setup tab in the function". Whatever you did it let you use `h3` in the main scope of your function. I'm not a programmer but an engineer with a lot or Perl experience so all of this JavaScript is new to me.

I initially got this error

```auto
"ReferenceError: h3 is not defined (line 14, col 18)"

```

After googling a bunch all I could ever figure out was to add the h3 to the global variables not to the main function scope like you did.

What I did was to add `h3:require('h3-js')` to the `setting.js` file under the `functionGlobalContext` section and then `let h3 = global.get("h3");` in the function. But, your solution (whatever it was) is so much more elegant. I wish I could figure that out. But mine solution works too!

```auto
./settings.js functionGlobalContext: {
./settings.js h3:require('h3-js')

```

Recap:

1. Install the h3-js lib under node-red root installation

```auto
[root@server .node-red]# pwd
/root/.node-red
[root@server .node-red]# npm install h3-js
added 1 package, and audited 411 packages in 11s

```

1. Edited the setings.js file to add the h3:require('h3-js')

```auto
[root@server .node-red]# vi ./settings.js
[root@server .node-red]# grep -B 4 -A 1 h3 ./settings.js
    functionGlobalContext: {
        // os:require('os'),
        // jfive:require("johnny-five"),
        // j5board:require("johnny-five").Board({repl:false})
        h3:require('h3-js')
    },

```

1. In the function node to pull h3 into my namespace (I needed to but you did not).

```auto
let h3 = global.get("h3");

```

1. The results before any tuning

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/7/171d4b9e3b98c19379d418369644c0184f247685.jpeg)

---

<div class="post-metadata">

**Author:** ![mrdvt92](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/mrdvt92/32/33122_2.png) [@mrdvt92](https://discourse.nodered.org/u/mrdvt92)\
**Post date:** [17 November 2022 03:41 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598/6 "2022-11-17T03:41:07Z")

</div>

After a bit of refactoring and to be more my style, this is what I have come up with for `h3.polygonToCells` and `h3.cellToChildren`

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/e/a/eaa4578d295259931f14221d863b235a370cc281.jpeg)

#### Flow

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/e/c/ecf1c09c203ca5876a53b8ec74e77ca678fe5d6a.png)

```auto
[{"id":"79fb6c16.c8d374","type":"change","z":"e02f29e1.67d3d8","name":"polygon/resolution","rules":[{"t":"set","p":"payload.polygon","pt":"msg","to":"[\t [payload.south, payload.west],\t [payload.north, payload.west],\t [payload.north, payload.east],\t [payload.south, payload.east],\t [payload.south, payload.west]\t]","tot":"jsonata"},{"t":"set","p":"payload.resolution","pt":"msg","to":"8","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1030,"y":380,"wires":[["3c794efe.320c22","1289562b.53082a"]]},{"id":"3c794efe.320c22","type":"function","z":"e02f29e1.67d3d8","name":"h3.polygonToCells","func":"let h3 = global.get(\"h3\");\nvar resolution = msg.payload.resolution;\nvar polygon = msg.payload.polygon\nvar strings = h3.polygonToCells(polygon, resolution);\nmsg.payload = []; //isa [{string:str, boundry:[[lat,lon], ...]}, ...]\n\nstrings.forEach((string) => {\n var boundry = h3.cellToBoundary(string); //isa [[], [], ...]\n boundry.push(boundry[0]); // OGC closed linear ring\n var cell = {\n \"string\": string, \n \"resolution\": resolution,\n \"boundry\": boundry,\n };\n msg.payload.push(cell);\n});\n\nreturn msg;","outputs":1,"noerr":0,"x":1250,"y":380,"wires":[["d9370a1d.c94488"]]},{"id":"d9370a1d.c94488","type":"split","z":"e02f29e1.67d3d8","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1430,"y":380,"wires":[["5c806c3d.f9f4c4","2e52a07d.6a9c5"]]},{"id":"2e52a07d.6a9c5","type":"function","z":"e02f29e1.67d3d8","name":"h3.cellToChildren","func":"let h3 = global.get(\"h3\");\nvar cell = msg.payload;\nvar resolution = cell.resolution + 1; //TODO: allow override\n//node.warn(cell.string);\n//node.warn(resolution);\n\nvar strings = h3.cellToChildren(cell.string, resolution);\n//node.warn(strings);\n\nmsg.payload = []; //isa [{string:str, boundry:[[lat,lon], ...]}, ...]\n\nstrings.forEach((string) => {\n var boundry = h3.cellToBoundary(string); //isa [[], [], ...]\n boundry.push(boundry[0]); // OGC closed linear ring\n var cell = {\n \"string\": string, \n \"resolution\": resolution,\n \"boundry\": boundry,\n };\n msg.payload.push(cell);\n});\n\nreturn msg;","outputs":1,"noerr":0,"x":1610,"y":340,"wires":[["f904cb0d.2228a8"]]},{"id":"f904cb0d.2228a8","type":"split","z":"e02f29e1.67d3d8","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1790,"y":340,"wires":[["90e56f86.a1f5"]]},{"id":"90e56f86.a1f5","type":"change","z":"e02f29e1.67d3d8","name":"color","rules":[{"t":"set","p":"cell","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.weight","pt":"msg","to":"1","tot":"num"},{"t":"set","p":"payload.color","pt":"msg","to":"#CCCCCC","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1930,"y":340,"wires":[["580b1557.f6291c"]]},{"id":"5c806c3d.f9f4c4","type":"change","z":"e02f29e1.67d3d8","name":"color","rules":[{"t":"set","p":"cell","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.weight","pt":"msg","to":"2","tot":"num"},{"t":"set","p":"payload.color","pt":"msg","to":"#BBBBBB","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1930,"y":380,"wires":[["580b1557.f6291c"]]},{"id":"580b1557.f6291c","type":"change","z":"e02f29e1.67d3d8","name":"format","rules":[{"t":"set","p":"payload.layer","pt":"msg","to":"hex","tot":"str"},{"t":"set","p":"payload.name","pt":"msg","to":"cell.string","tot":"msg"},{"t":"set","p":"payload.line","pt":"msg","to":"cell.boundry","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":2070,"y":360,"wires":[["ac5a14e6.2859f8"]]},{"id":"ac5a14e6.2859f8","type":"change","z":"e02f29e1.67d3d8","name":"","rules":[],"action":"","property":"","from":"","to":"","reg":false,"x":2195,"y":440,"wires":[["4c9dc44a.908fdc"]],"l":false},{"id":"4c9dc44a.908fdc","type":"ui_worldmap","z":"e02f29e1.67d3d8","group":"b216f4f.7d69c08","order":1,"width":"20","height":"10","name":"","lat":"38.2","lon":"-97.2","zoom":"4.5","layer":"","cluster":"","maxage":"","usermenu":"hide","layers":"show","panit":"false","panlock":"false","zoomlock":"false","hiderightclick":"true","coords":"deg","showgrid":"false","allowFileDrop":"false","path":"/worldmap","overlist":"","maplist":"OSMG,OSMC,EsriC,EsriS,EsriT,EsriDG","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":2340,"y":260,"wires":[]},{"id":"9b2015b6.65a058","type":"worldmap in","z":"e02f29e1.67d3d8","name":"worldmap boundary","path":"/worldmap","events":"bounds","x":110,"y":260,"wires":[["9470a517.f4e368","f53a4b1b.56d988"]]},{"id":"f53a4b1b.56d988","type":"trigger","z":"e02f29e1.67d3d8","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"500","extend":true,"units":"ms","reset":"","bytopic":"all","name":"","x":420,"y":260,"wires":[["3a7daa20.4a5456"]]},{"id":"3a7daa20.4a5456","type":"change","z":"e02f29e1.67d3d8","name":"set center","rules":[{"t":"set","p":"payload.center","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.center.lat","pt":"msg","to":"$average([payload.north, payload.south])\t","tot":"jsonata"},{"t":"set","p":"payload.center.lon","pt":"msg","to":"$average([payload.east, payload.west])\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":600,"y":260,"wires":[["495f29b2.e5e508","9ed54ec.ce141b"]]},{"id":"495f29b2.e5e508","type":"switch","z":"e02f29e1.67d3d8","name":"zoom >= 15.5","property":"payload.zoom","propertyType":"msg","rules":[{"t":"gte","v":"15.5","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":780,"y":260,"wires":[["d306486c.fc1f58","28ef495f.224f26","800fae11.27ece","79fb6c16.c8d374"],["65916287.60cedc","fe6930f5.e980f"]]},{"id":"b216f4f.7d69c08","type":"ui_group","z":"","name":"World Map","tab":"a4e26d94.4446","order":1,"disp":true,"width":"20","collapse":false},{"id":"a4e26d94.4446","type":"ui_tab","z":"","name":"World Map","icon":"dashboard","order":6,"disabled":false,"hidden":false}]

```

#### Event Summary:

1. on map change of bounding box
2. format bbox as polygon (OGC closed ring)
3. call polygonToCells at level 8 which returns the cell string and boundary
4. split return to process each cell
5. get all children for each cell
6. format objects for worldmap

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [16 January 2023 03:41 UTC](https://discourse.nodered.org/t/node-to-wrap-h3-js-from-https-github-com-uber-h3-js/70598/7 "2023-01-16T03:41:07Z")

</div>

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.
