Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Executing Geojson2h3 on the browser without Nodejs #28

Open
sharmapn opened this issue Jun 23, 2022 · 15 comments
Open

Executing Geojson2h3 on the browser without Nodejs #28

sharmapn opened this issue Jun 23, 2022 · 15 comments

Comments

@sharmapn
Copy link

I am trying to execute Geojson2h3 on the client browser without Nodejs. I have imported the 'geojson2h3.js' javascript file
However, I am getting an error with the following code.
I am hoping that this is possible to execute geojson2h3 in the browser.

<script src="assets/js/geojson2h3.js"></script>
<script src="assets/js/h3binning.js"></script>		
<script src="https://unpkg.com/h3-js"></script>
<script src="https://unpkg.com/h3-js@3.7.2/dist/h3-js.umd.js"></script>

function h3binning(){     
    var filename = "sample.geojson"
    var geoJSON = { "type": "FeatureCollection",
    "features": [
      { "type": "Feature",
        "geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
        "properties": {"prop0": "value0"}
        }
      ]
    }

    console.log('geoJSON: geoJSON' )
    console.log('JSON.stringify(geoJSON): ' + JSON.stringify(geoJSON) )
        
    const hexagons2 = geojson2h3.featureToH3Set(geoJSON, 10); //binning level.  
}

I receive this error.

Uncaught ReferenceError: geojson2h3 is not defined
    at h3binning (h3binning.js:61:23)
    at HTMLButtonElement.<anonymous> ((index):467:22)
    at HTMLButtonElement.dispatch (jquery-3.6.0.min.js:2:43064)
    at v.handle (jquery-3.6.0.min.js:2:41048) 
@dfellis
Copy link
Collaborator

dfellis commented Jun 23, 2022

So this is a Node.js library using the CommonJS module system. If you want to get it in the browser, you need to bundle it with a shim of some sort to work in the browser.

Browserify would be the simplest way to make such a bundle, though it is less popular these days.

@sharmapn
Copy link
Author

sharmapn commented Jun 23, 2022

Hi. Thank you for your reply.
I was just wondering if the examples on the following page are run in conjunction with node.js or somehow from just within the browser. The functionality executes quite fast, so I am thing it may be running fully client-sided?
https://observablehq.com/@nrabinowitz/h3-tutorial-using-point-layers?collection=@nrabinowitz/h3-tutorial

@dfellis
Copy link
Collaborator

dfellis commented Jun 23, 2022

Fully client side like you suspect. It's not hard to add that shim, but h3-js and geojson2h3 do not do this by themselves because there are 3 different "run Node.js library in the browser" tools that I am aware of and they are not compatible with each other, so it's up to the user to pick the one they want.

@sharmapn
Copy link
Author

Thank you so much,
I now want to get started with implementing this browser-sided functionality.
I am a total beginner in regards to shim, and browserify - would you have some sample code with geojson2h3 to get started with?
The code on that page is quite complex to start with. I tried to see the source code using the browser source-view but its quite complicated.

@dfellis
Copy link
Collaborator

dfellis commented Jun 23, 2022

So all of the browser-shim tools require you to use Node.js to generate a browser-only JS file. But I mentioned Browserify because I personally find it the easiest to work with.

I just made h3-js available to the browser with Browserify locally. Here's the steps I took:

damocles@zelbinion:~/oss$ mkdir tmp
damocles@zelbinion:~/oss$ cd tmp
damocles@zelbinion:~/oss/tmp$ yarn add browserify h3-js
...
damocles@zelbinion:~/oss/tmp$ ./node_modules/.bin/browserify -o h3js.js -r h3-js
damocles@zelbinion:~/oss/tmp$ vim index.html

The last command, vim index.html was where I created a test index.html file to use the Browserified h3-js. The contents are:

<!doctype html>
<html>
  <head>
    <title>Example Browserified h3js</title>
    <script src="./h3js.js"></script>
  </head>
  <body>
    <script>
      const h3js = require('h3-js');
      window.alert(h3js.geoToH3(0, 0, 9));
    </script>
  </body>
</html>

Browserify creates a require function that you can then use to get access to any of the modules you listed in the -r list. In this case only h3-js.

Opening that file in Firefox, I then got:

Screenshot from 2022-06-23 18-30-45

Which confirms that it works.

EDIT: Btw, this requires that you have node in your path, and I used yarn out of habit, but you could also npm i browserify h3-js instead if you don't have yarn. (NPM comes by default with Node.)

EDIT2: I just realized that I used h3-js instead of geojson2h3 but it should basically be identical, except a different module to install, pass to browserify, and a different method to call on the thing you require. geojson2h3 depends on h3-js internally, so an npm i geojson2h3 will pull it in automatically for you.

@sharmapn
Copy link
Author

Thank you so much David Ellis.
I cannot thank you enough.
This is great help.

@sharmapn
Copy link
Author

sharmapn commented Jul 4, 2022

I used an ubuntu server to first install geojson2h3 and also browserify
and then put in the command for conversion. But when I call the Javascript function,
I receive this following error in the browser console:
Uncaught Error: Unhandled geometry type: Point

I started by installing the browserify and h3-js on the ubuntu server

ubuntu@test:~$ npm install -g browserify
..
added 183 packages from 123 contributors in 18.99s
ubuntu@test:~$ npm install h3-js
..
added 9 packages from 3 contributors and audited 1 package in 5.095s

ubuntu@test:~$ npm install geojson2h3
....
added 1 package from 1 contributor and audited 16 packages in 2.614s

ubuntu@test:~$ browserify -o geojson2h3.js -r geojson2h3
ubuntu@test:~$ ls
default        node_modules  package-lock.json
backups     cds.sh     geojson2h3.js  nohup.out     server

Then I imported the generated geojson2h3.js file in the html
<script src="assets/js/geojson2h3.js"></script> <!-- geojson2h3 created using browserify -->

Then when the html page loads, I call this javascript function via browser console

function h3binning(){  
    //var h3 = require('h3-js');  
    var geojson2h3 = require('geojson2h3'); 
    var level = 2;
    
    var bbox = [
        [-123.308821530582, 38.28055644998254],
        [-121.30037257250085, 38.28055644998254],
        [-121.30037257250085, 37.242722073589164],
        [-123.308821530582, 37.242722073589164]
    ];

    //test h3-js..it works file
    console.log('h3js ' + h3.geoToH3(0, 0, 9))

    var filename = "sample.geojson"
    var geoJSON2 = { "type": "FeatureCollection",
    "features": [
      { "type": "Feature",
        "geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
        "properties": {"prop0": "value0"}
        }
      ]
    }
                                              
    const hexagons2 = geojson2h3.featureToH3Set(geoJSON2, 10); //THE ERROR IS FROM HERE
    console.log('hexagons ' + hexagons2)
     
    const featuresCollection = geojson2h3.h3SetToFeatureCollection(hexagons2);
    console.log('hexagons ' + featuresCollection)    
}

But I receive this output and error

h3binning() // I type this function call in the browser console
h3js 89754e64993ffff
geoJSON2: geoJSON2
JSON.stringify(geoJSON2): {"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[102,0.5]},"properties":{"prop0":"value0"}}]}
hexagons 
geojson2h3.js:128 Uncaught Error: Unhandled geometry type: Point
    at featureToH3Set (geojson2h3.js:128:15)
    at geojson2h3.js:85:61
    at Array.map (<anonymous>)
    at featureCollectionToH3Set (geojson2h3.js:85:29)
    at Object.featureToH3Set (geojson2h3.js:121:16)
    at h3binning (h3binning.js:95:34)
    at <anonymous>:1:1

@dfellis
Copy link
Collaborator

dfellis commented Jul 4, 2022

So that error is expected. Currently geojson2h3 only supports [Polygon and MultiPolygon](https://github.com/uber/geojson2h3#geojson2h3featuretoh3setfeature-resolution-options--arrays trying) features. If you change that to Polygon instead of Point, that might "fix" it, but the proper solution would be to add point support to the library and call geoToH3 internally.

@sharmapn
Copy link
Author

sharmapn commented Jul 4, 2022

Thank you again David, That worked!
Now the question is how to generate polygons from points within a geojson format?
Would be glad if you can guide, maybe with some online resource/code.

My geojson is created from shapefile and just contains points, no polygons there.

@dfellis
Copy link
Collaborator

dfellis commented Jul 4, 2022

Well, that really depends on what those points represent and how you want to turn them into a polygon. To be a polygon there needs to be an order for the points. If your shapefile already has the points listed in the right order for the polygon you want, then it would just be wrapping all of the point arrays into a big array in the GeoJSON format and you're done.

However, if the points represent a "cloud" of activity in an area and you want the smallest polygon to contain them, then you need to implement a convex hull algorithm to get the polygon to then put into the GeoJSON to then feed into this to produce a set of H3 cells.

You can simply short circuit that by calling geoToH3 on all of the points and use the JS Set type to deduplicate the hexagons and approximate the convex hull, but there is a risk that the output set of hexagons are not all contiguous, so you can't traverse between them, so that would depend on what you need from the output as well.

Basically, there is no one right answer and it all depends on both the kind of input data you have and how you intend to use the output.

@sharmapn
Copy link
Author

sharmapn commented Jul 4, 2022

Thank you David,

I am attaching a screen of what my geospatial data normally looks like. It would be a point cloud.
I am then also attaching what the Ideal output should look like - together with the slider options to increase/decrease the H3 Resolution and Buffer radius.

What you said makes sense - but at the moment I am just hoping to get started somewhere with some piece of code, and build from there.
As you can see, all data would be in geojson, so first to convert them in a format for h3-binning.
OriginalDataset
IdealOutput

@sharmapn
Copy link
Author

sharmapn commented Jul 4, 2022

Attaching sample geojson data (of the first dataset shown above). Note file is zipped.
mygeodata (1).zip.

@dfellis
Copy link
Collaborator

dfellis commented Jul 4, 2022

Thank you David,

I am attaching a screen of what my geospatial data normally looks like. It would be a point cloud. I am then also attaching what the Ideal output should look like - together with the slider options to increase/decrease the H3 Resolution and Buffer radius.

What you said makes sense - but at the moment I am just hoping to get started somewhere with some piece of code, and build from there. As you can see, all data would be in geojson, so first to convert them in a format for h3-binning. OriginalDataset IdealOutput

So I wouldn't use geojson2h3 at all for this.

You'd have to ask @nrabinowitz what the buffer radius is about / how it works, but the basics of what you need are just:

const h3 = require('h3-js');
const resolution = 9;
const aggregatedH3Density = {};
for (const point in pointCloud) {
  const h3Index = h3.geoToH3(point[0], point[1], resolution); // Assuming point is [lat, lng]
  aggregatedH3Density[h3Index] = (aggregatedH3Density[h3Index] ?? 0) + 1;
}

Then all of the hexagons that have any data at all will be the keys of the aggregatedH3Density object, and their values are the number of points that fell into that H3 cell. If you want to smooth the data out, you need to come up with a smoothing function as well as the size you want to smooth into.

The easiest way is to smooth in grid-space using the kRingDistances function, and one potential smoothing function is to take the point value as a float 1.0 and split it evenly between the rings and then within each ring split that fraction of it evenly amongst the cells. For instance, with a "k-ring" of 1, you'd put 0.5 in for the actual cell the data was in, and then 0.5/6 = 0.0833... for each of the cells in the outer ring. This would modify the example above to be:

const h3 = require('h3-js');
const resolution = 9;
const k = 1;
const aggregatedH3Density = {};
for (const point in pointCloud) {
  const krd = h3.kRingDistnaces(h3.geoToH3(point[0], point[1], resolution), k); // Assuming point is [lat, lng]
  const numRings = krd.length;
  for (const [index, ring] in krd.entries()) {
    const distributedVal = 1.0 / (numRings * ring.length);
    for (const cell in ring) {
      aggregatedH3Density[h3Index] = (aggregatedH3Density[h3Index] ?? 0) + distributedVal;
    }
  }
}

This doesn't smooth by exact kilometers, but by grid-space sizes, however it is super simple to understand and is very similar to how Uber's hex surge algorithm dealt with "eyeball" point data when I worked there (it was in Java and was written in "reverse" so it could be parallelizable across CPU cores, with batches of output hexagons assigned to different cores all reading from the same read-only input array, which was possible because the service area to cover was known ahead of time).

@sharmapn
Copy link
Author

sharmapn commented Jul 5, 2022

Dear David,
Thank you for the code. That solves a lot.
Sorry I am new to GIS, and thus I may have been confused on what point cloud means.
My data is in GeoJSON format, thus everytime you perform this line, is not possible as my data is not in coordinate format.
for (const point in pointCloud) {

I would first have to bring the geojson data (point features) to coordinate format to perform that line.
Please refer to the zip file, which has the geojson data of our project.

I would be grateful if you can guide me on that aspect as well, if you know.

@sharmapn
Copy link
Author

sharmapn commented Sep 12, 2022

Hi David,
I think I know how to get the coordinates from a geojson:

geojson.features.forEach(feature => {
    const [lng, lat] = feature.geometry.coordinates;
    //const h3Index = h3.geoToH3(lat, lng, h3Resolution);
    //layer[h3Index] = (layer[h3Index] || 0) + 1;
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants