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

Somthing is funky at the borders of the cells #189

Open
syonfox opened this issue Mar 13, 2024 · 1 comment
Open

Somthing is funky at the borders of the cells #189

syonfox opened this issue Mar 13, 2024 · 1 comment

Comments

@syonfox
Copy link

syonfox commented Mar 13, 2024

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
// 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: <b>${h3id}</b>
                <br />
                Average edge length (m): <b>${averageEdgeLength.toLocaleString()}</b>
                <br />
                Cell area (m^2): <b>${cellArea.toLocaleString()}</b>
                `;

                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 = `<text x="20" y="70" class="h3Text">${h3id}</text>`;
                    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
Copy link
Collaborator

See this answer: #158 (comment)

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.

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