Skip to content

Commit

Permalink
Merge pull request #4 from nrabinowitz/update-h3
Browse files Browse the repository at this point in the history
Update h3-js to 3.1.1, add benchmarks
  • Loading branch information
nrabinowitz committed Sep 1, 2018
2 parents 6fd2ea9 + 34780d6 commit e41fe3a
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 111 deletions.
16 changes: 8 additions & 8 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "geojson2h3",
"description": "Conversion utilities between H3 indexes and GeoJSON",
"version": "1.0.0",
"version": "1.0.1",
"author": "Nick Rabinowitz <nickr@uber.com>",
"keywords": [
"h3",
Expand All @@ -16,11 +16,10 @@
"main": "index.js",
"es2015": "lib/geojson2h3.js",
"dependencies": {
"@turf/boolean-clockwise": "^6.0.1",
"@turf/boolean-point-in-polygon": "^6.0.1",
"h3-js": "^3.0.1"
"h3-js": "^3.1.1"
},
"devDependencies": {
"benchmark": "^2.1.4",
"buble": "^0.19.3",
"eslint": "^4.19.1",
"eslint-config-prettier": "^2.9.0",
Expand All @@ -35,17 +34,18 @@
"scripts": {
"lint": "eslint src test",
"cover": "istanbul cover --report lcov --report html -- test/index.js",
"test": "yarn run dist-test && yarn lint && yarn run test-es6 && yarn run test-dist",
"test": "yarn dist-test && yarn lint && yarn test-es6 && yarn test-dist",
"test-raw": "node test/index.js",
"test-es6": "yarn test-raw | faucet",
"test-dist": "node dist/test/index.js | faucet",
"check-prettier": "yarn prettier && git diff --exit-code",
"check-docs": "yarn build-docs && git diff --exit-code",
"build-docs": "jsdoc2md --global-index-format grouped --separators --template doc-files/README.md.tmpl src/geojson2h3.js > README.md",
"dist": "yarn run dist-clean && buble -i src -o dist/src",
"dist": "yarn dist-clean && buble -i src -o dist/src",
"dist-clean": "rm -rf dist && mkdir dist",
"dist-test": "yarn run dist && buble -i test -o dist/test",
"prepublish": "yarn run dist",
"dist-test": "yarn dist && buble -i test -o dist/test",
"benchmarks": "yarn dist-test && node dist/test/benchmarks.js",
"prepublish": "yarn dist",
"prettier": "prettier --write --single-quote --no-bracket-spacing --print-width=100 'src/**/*.js' 'test/**/*.js'"
},
"engines": {
Expand Down
67 changes: 1 addition & 66 deletions src/geojson2h3.js
Expand Up @@ -19,8 +19,6 @@
*/

const h3 = require('h3-js');
const inside = require('@turf/boolean-point-in-polygon').default;
const isClockwise = require('@turf/boolean-clockwise').default;

const FEATURE = 'Feature';
const FEATURE_COLLECTION = 'FeatureCollection';
Expand Down Expand Up @@ -51,69 +49,6 @@ function flatten(arrays) {
return out;
}

/**
* The current H3 algorithm returns a single array of loops (as for a GeoJSON Polygon),
* some external and some internal (holes). This format may be considered invalid
* for some consumers, and doesn't render correctly in mapbox-gl.
*
* For now, identify outer and inner loops here and format into correct MultiPolygons. Note
* that the method used here to allocate holes to their containing loops will not work for
* certain complex shapes (e.g. nested donuts), but should be sufficient for most real-world
* use cases.
*
* TODO: This is a workaround for https://github.com/uber/h3/issues/53 - remove when closed
* @private
* @param {Array[]} polygons Arrays of loops
* @return {Array[]} Spec-conforming MultiPolygon
*/
function normalizeMultiPolygon(polygons) {
const normalized = [];
const innerLoops = [];
const testPolygons = [];
// Split into outer/inner loops
polygons.forEach(polygon => {
polygon.forEach(loop => {
// Inner loops are always clockwise
if (isClockwise(loop)) {
// Push to temp inner loop array
innerLoops.push(loop);
} else {
// Push to output as the first loop in a new polygon
normalized.push([loop]);
// also include in the test set for the inner loop allocation,
// normalizing to GeoJSON for turf
testPolygons.push({
type: 'Feature',
geometry: {type: 'Polygon', coordinates: [loop]}
});
}
});
});
// Allocate inner loops to outer loops
innerLoops.forEach(loop => {
// No overlaps, so a single test point is sufficient
const testPoint = loop[0];
let allocated = false;
for (let i = 0; i < testPolygons.length; i++) {
if (inside(testPoint, testPolygons[i])) {
if (allocated) {
// This case is possible with valid input if you have nested donuts
throw new Error('Unsupported MultiPolygon topology');
}
normalized[i].push(loop);
allocated = true;
}
}
/* istanbul ignore if */
if (!allocated) {
// This case should not be possible with valid input
throw new Error('Failed to allocate inner loop');
}
});
// The normalized version now has one outer loop per polygon, then any inner loops
return normalized;
}

/**
* Convert a GeoJSON feature collection to a set of hexagons. Only hexagons whose centers
* fall within the features will be included.
Expand Down Expand Up @@ -200,7 +135,7 @@ function h3ToFeature(hexAddress, properties = {}) {
* @return {Feature} GeoJSON Feature object
*/
function h3SetToFeature(hexagons, properties = {}) {
const polygons = normalizeMultiPolygon(h3.h3SetToMultiPolygon(hexagons, true));
const polygons = h3.h3SetToMultiPolygon(hexagons, true);
// See if we can unwrap to a simple Polygon.
const isMultiPolygon = polygons.length > 1;
const type = isMultiPolygon ? MULTI_POLYGON : POLYGON;
Expand Down
62 changes: 62 additions & 0 deletions test/benchmarks.js
@@ -0,0 +1,62 @@
/*
* Copyright 2018 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* eslint-env node */
/* eslint-disable no-console */
const Benchmark = require('benchmark');
const h3 = require('h3-js');
const geojson2h3 = require('../src/geojson2h3');

const suite = new Benchmark.Suite();

// fixtures

const h3Index = '89283080ddbffff';
const ring50 = h3.kRing(h3Index, 50);
const ring50Feature = geojson2h3.h3SetToFeature(ring50, 9);
const ring50Donuts = h3
.kRing(h3Index, 10)
.concat(h3.kRing(h3, 20))
.concat(h3.kRing(h3, 30))
.concat(h3.kRing(h3, 40))
.concat(h3.kRing(h3, 50));
const ring50DonutsFeature = geojson2h3.h3SetToFeature(ring50Donuts, 9);

// add tests

suite.add('h3SetToFeature - ring50', () => {
geojson2h3.h3SetToFeature(ring50);
});

suite.add('h3SetToFeature - ring50Donuts', () => {
geojson2h3.h3SetToFeature(ring50Donuts);
});

suite.add('featureToH3Set - ring50', () => {
geojson2h3.featureToH3Set(ring50Feature, 9);
});

suite.add('featureToH3Set - ring50Donuts', () => {
geojson2h3.featureToH3Set(ring50DonutsFeature, 9);
});

// add listeners
suite
.on('cycle', event => {
console.log(String(event.target));
})
// run async
.run({async: true});
25 changes: 15 additions & 10 deletions test/geojson2h3.spec.js
Expand Up @@ -167,7 +167,7 @@ test('Symmetrical - One hexagon', assert => {
coordinates: [
// Note that this is a little brittle; iterating from any
// starting vertex would be correct
[...vertices.slice(2, 6), ...vertices.slice(0, 3)]
[...vertices]
]
}
};
Expand All @@ -185,6 +185,8 @@ test('Symmetrical - Two hexagons', assert => {
type: 'Polygon',
coordinates: [
[
[-122.42778275313196, 37.775989518837726],
[-122.42652309807923, 37.77448508566524],
[-122.42419231791126, 37.7746633251758],
[-122.42312112449315, 37.776346021077586],
[-122.42438078060647, 37.77785047757876],
Expand All @@ -193,9 +195,7 @@ test('Symmetrical - Two hexagons', assert => {
[-122.4303021418057, 37.778998255103545],
[-122.4313731964829, 37.77731555898803],
[-122.43011350268344, 37.77581120251896],
[-122.42778275313196, 37.775989518837726],
[-122.42652309807923, 37.77448508566524],
[-122.42419231791126, 37.7746633251758]
[-122.42778275313196, 37.775989518837726]
]
]
}
Expand Down Expand Up @@ -539,14 +539,19 @@ test('h3SetToFeature - multi donut', assert => {
assert.end();
});

test('h3SetToFeature - nested donut throws', assert => {
test('h3SetToFeature - nested donut', assert => {
const middle = '89283082877ffff';
const hexagons = h3.hexRing(middle, 1).concat(h3.hexRing(middle, 3));
assert.throws(
() => h3SetToFeature(hexagons),
/Unsupported MultiPolygon topology/,
'throws expected error'
);
const feature = h3SetToFeature(hexagons);
const coords = feature.geometry.coordinates;

assert.strictEqual(coords.length, 2, 'expected polygon count');
assert.strictEqual(coords[0].length, 2, 'expected loop count for p1');
assert.strictEqual(coords[1].length, 2, 'expected loop count for p2');
assert.strictEqual(coords[0][0].length, 6 * 3 + 1, 'expected outer coord count p1');
assert.strictEqual(coords[0][1].length, 7, 'expected inner coord count p1');
assert.strictEqual(coords[1][0].length, 6 * 7 + 1, 'expected outer coord count p2');
assert.strictEqual(coords[1][1].length, 6 * 5 + 1, 'expected inner coord count p2');

assert.end();
});
Expand Down
41 changes: 14 additions & 27 deletions yarn.lock
Expand Up @@ -2,30 +2,6 @@
# yarn lockfile v1


"@turf/boolean-clockwise@^6.0.1":
version "6.0.1"
resolved "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.0.1.tgz#87261f8ed1e03671e39b6ebd138388de0e6aca4b"
dependencies:
"@turf/helpers" "6.x"
"@turf/invariant" "6.x"

"@turf/boolean-point-in-polygon@^6.0.1":
version "6.0.1"
resolved "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.0.1.tgz#5836677afd77d2ee391af0056a0c29b660edfa32"
dependencies:
"@turf/helpers" "6.x"
"@turf/invariant" "6.x"

"@turf/helpers@6.x":
version "6.1.4"
resolved "https://registry.npmjs.org/@turf/helpers/-/helpers-6.1.4.tgz#d6fd7ebe6782dd9c87dca5559bda5c48ae4c3836"

"@turf/invariant@6.x":
version "6.1.2"
resolved "https://registry.npmjs.org/@turf/invariant/-/invariant-6.1.2.tgz#6013ed6219f9ac2edada9b31e1dfa5918eb0a2f7"
dependencies:
"@turf/helpers" "6.x"

abbrev@1:
version "1.1.1"
resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
Expand Down Expand Up @@ -172,6 +148,13 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"

benchmark@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629"
dependencies:
lodash "^4.17.4"
platform "^1.3.3"

bluebird@~3.5.0:
version "3.5.1"
resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
Expand Down Expand Up @@ -735,9 +718,9 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.9:
version "4.1.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"

h3-js@^3.0.1:
version "3.0.2"
resolved "https://registry.npmjs.org/h3-js/-/h3-js-3.0.2.tgz#99a7d3251a6ffb21d8d885f1e6ecf2fd96248f69"
h3-js@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/h3-js/-/h3-js-3.1.1.tgz#5e62994c46ee4053327cd437f4fde4fe3430bf3d"

handlebars@^4.0.1, handlebars@^4.0.11:
version "4.0.11"
Expand Down Expand Up @@ -1187,6 +1170,10 @@ pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"

platform@^1.3.3:
version "1.3.5"
resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444"

pluralize@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
Expand Down

0 comments on commit e41fe3a

Please sign in to comment.