uber / h3-js

h3-js provides a JavaScript version of H3, a hexagon-based geospatial indexing system.
https://uber.github.io/h3
Apache License 2.0
867 stars 78 forks source link

Somthing is funky at the borders of the cells #189

Open syonfox opened 7 months ago

syonfox commented 7 months ago

image

Uncaught Error: Resolution argument was outside of acceptable range (code: 4, value: -1)
    c h3.js:1
    s h3.js:1
    y h3.js:1
    polygonToCells h3.js:1
    updateMapDisplay h3viewer.js:115
    fire Events.js:195
    _moveEnd Map.js:1248
    _onZoomTransitionEnd Map.js:1735
    setTimeout handler*_animateZoom Map.js:1714
    _tryAnimatedZoom Map.js:1679
    x Util.js:231
    _tryAnimatedZoom Map.js:1676
    setView Map.js:195
    setZoomAround Map.js:249
    _performZoom Map.ScrollWheelZoom.js:83
    setTimeout handler*_onWheelScroll Map.ScrollWheelZoom.js:57
    o DomEvent.js:108
    ke DomEvent.js:123
    S DomEvent.js:34
    addHooks Map.ScrollWheelZoom.js:33
    enable Handler.js:23
    addHandler Map.js:733
    i Class.js:114
    callInitHooks Class.js:81
    initialize Map.js:156
    e Class.js:24
    map Map.js:1750
    <anonymous> logic.js:204
[h3.js:1:157510](/h3.umd.js)

this also happens when you zoom out so multiple earths are in frame. . it would be nice if the polygons supported Mercator clipping or something. not sure what the most sain implementation is 

Anyway though id point it out if curious this is how im adding this to leaflet map

h3viewer.js ```js // let map, hexLayer, p; const GeoUtils = { EARTH_RADIUS_METERS: 6371000, radiansToDegrees: (r) => r * 180 / Math.PI, degreesToRadians: (d) => d * Math.PI / 180, getDistanceOnEarthInMeters: (lat1, lon1, lat2, lon2) => { const lat1Rad = GeoUtils.degreesToRadians(lat1); const lat2Rad = GeoUtils.degreesToRadians(lat2); const lonDelta = GeoUtils.degreesToRadians(lon2 - lon1); const x = Math.sin(lat1Rad) * Math.sin(lat2Rad) + Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lonDelta); return GeoUtils.EARTH_RADIUS_METERS * Math.acos(Math.max(Math.min(x, 1), -1)); } }; const ZOOM_TO_H3_RES_CORRESPONDENCE = { 5: 1, 6: 2, 7: 3, 8: 3, 9: 4, 10: 5, 11: 6, 12: 6, 13: 7, 14: 8, 15: 9, 16: 9, 17: 10, 18: 10, 19: 11, 20: 11, 21: 12, 22: 13, 23: 14, 24: 15, }; const H3_RES_TO_ZOOM_CORRESPONDENCE = {}; for (const [zoom, res] of Object.entries(ZOOM_TO_H3_RES_CORRESPONDENCE)) { H3_RES_TO_ZOOM_CORRESPONDENCE[res] = zoom; } const getH3ResForMapZoom = (mapZoom) => { return ZOOM_TO_H3_RES_CORRESPONDENCE[mapZoom] ?? Math.floor((mapZoom - 1) * 0.7); }; const h3BoundsToPolygon = (lngLatH3Bounds) => { lngLatH3Bounds.push(lngLatH3Bounds[0]); // "close" the polygon return lngLatH3Bounds; }; /** * Parse the current Query String and return its components as an object. */ const parseQueryString = () => { const queryString = window.location.search; const query = {}; const pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&'); for (let i = 0; i < pairs.length; i++) { const pair = pairs[i].split('='); query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || ''); } return query; }; const queryParams = parseQueryString(); const copyToClipboard = (text) => { const dummy = document.createElement("textarea"); document.body.appendChild(dummy); dummy.value = text; dummy.select(); document.execCommand("copy"); document.body.removeChild(dummy); }; function initH3LayerOnMap(h3code) { let self = { computeAverageEdgeLengthInMeters: function (vertexLocations) { let totalLength = 0; let edgeCount = 0; for (let i = 1; i < vertexLocations.length; i++) { const [fromLat, fromLng] = vertexLocations[i - 1]; const [toLat, toLng] = vertexLocations[i]; const edgeDistance = GeoUtils.getDistanceOnEarthInMeters(fromLat, fromLng, toLat, toLng); totalLength += edgeDistance; edgeCount++; } return totalLength / edgeCount; }, updateMapDisplay: function () { if (hexLayer) { hexLayer.remove(); } hexLayer = L.layerGroup().addTo(map); const zoom = map.getZoom(); self.currentH3Res = getH3ResForMapZoom(zoom); const {_southWest: sw, _northEast: ne} = map.getBounds(); const boundsPolygon = [ [sw.lat, sw.lng], [ne.lat, sw.lng], [ne.lat, ne.lng], [sw.lat, ne.lng], [sw.lat, sw.lng], ]; const h3s = h3.polygonToCells(boundsPolygon, self.currentH3Res); for (const h3id of h3s) { const polygonLayer = L.layerGroup() .addTo(hexLayer); const isSelected = h3id === self.searchH3Id; const style = isSelected ? {fillColor: "orange"} : {}; const h3Bounds = h3.cellToBoundary(h3id); const averageEdgeLength = self.computeAverageEdgeLengthInMeters(h3Bounds); const cellArea = h3.cellArea(h3id, "m2"); const tooltipText = ` Cell ID: ${h3id}
Average edge length (m): ${averageEdgeLength.toLocaleString()}
Cell area (m^2): ${cellArea.toLocaleString()} `; const h3Polygon = L.polygon(h3BoundsToPolygon(h3Bounds), style) .on('click', () => copyToClipboard(h3id)) .bindTooltip(tooltipText) .addTo(polygonLayer); // less SVG, otherwise perf is bad if (Math.random() > 0.8 || isSelected) { var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); svgElement.setAttribute('viewBox', "0 0 200 200"); svgElement.innerHTML = `${h3id}`; var svgElementBounds = h3Polygon.getBounds(); L.svgOverlay(svgElement, svgElementBounds).addTo(polygonLayer); } } }, gotoLocation: function () { const [lat, lon] = (self.gotoLatLon || "").split(",").map(Number); if (Number.isFinite(lat) && Number.isFinite(lon) && lat <= 90 && lat >= -90 && lon <= 180 && lon >= -180) { map.setView([lat, lon], 16); } }, findH3: function () { if (!h3.isValidCell(self.searchH3Id)) { return; } const h3Boundary = h3.cellToBoundary(self.searchH3Id); let bounds = undefined; for ([lat, lng] of h3Boundary) { if (bounds === undefined) { bounds = new L.LatLngBounds([lat, lng], [lat, lng]); } else { bounds.extend([lat, lng]); } } map.fitBounds(bounds); const newZoom = H3_RES_TO_ZOOM_CORRESPONDENCE[h3.getResolution(self.searchH3Id)]; map.setZoom(newZoom); } } function mounted() { map.on("zoomend", self.updateMapDisplay); map.on("moveend", self.updateMapDisplay); const h3 = h3code || p.h3viewer; console.log(h3) // if (h3) { // self.searchH3Id = h3; // window.setTimeout(() => self.findH3(), 50); // } self.updateMapDisplay(); return self; } return mounted() } var hexLayer function getParamH3Viewer() { let z = map.getZoom() let c = map.getCenter() let a = [z, c.lat, c.lng] return "mapinfo=" + encodeURIComponent(JSON.stringify(a)) } if (p && p.h3viewer) { try { let a = p.h3viewer // hmm coulf be hexcode console.log("yo dog") window.h3viewer = initH3LayerOnMap(false) } catch (e) { console.error("Invaled mapinfo doing nothing [zoom, lat, lon]") } } ```
nrabinowitz commented 7 months ago

See this answer: https://github.com/uber/h3-js/issues/158#issuecomment-1244087555

The problem in the image is a rendering issue, not an H3-js issue. H3 always provides coordinates with normalized longitudes (in the range -180 to 180) but this doesn't always render well on web maps.

The error you pasted to is a different issue - at some point you're sending resolution -1 to the polygonToCells function, and this throws an error for bad input.