H3Error polygonStringToGeoPolygon()

in src/apps/filters/h3.c [1448:1608]


H3Error polygonStringToGeoPolygon(FILE *fp, char *polygonString,
                                  GeoPolygon *out) {
    // There are two kinds of valid input, an array of arrays of lat, lng values
    // defining a singular polygon loop, or an array of array of arrays of lat,
    // lng values defining a polygon and zero or more holes. Which kind of input
    // this is can be determined by the `[` depth reached before the first
    // floating point number is found. This means either 2 or 3 `[`s at the
    // beginning are valid, and nothing more than that.
    int8_t maxDepth = 0;
    int8_t curDepth = 0;
    int64_t numVerts = 6;
    int64_t curVert = 0;
    int64_t curLoop = 0;
    LatLng *verts = calloc(numVerts, sizeof(LatLng));
    int strPos = 0;
    while (polygonString[strPos] != 0) {
        // Load more of the file if we've hit our buffer limit
        if (strPos >= BUFFER_SIZE && fp != 0) {
            int result = fread(polygonString, 1, BUFFER_SIZE, fp);
            strPos = 0;
            // If we didn't get any data from the file, we're done.
            if (result == 0) {
                break;
            }
        }
        // Try to match `[` first
        if (polygonString[strPos] == '[') {
            curDepth++;
            if (curDepth > maxDepth) {
                maxDepth = curDepth;
            }
            if (curDepth > 4) {
                // This is beyond the depth for a valid input, so we abort early
                free(verts);
                return E_FAILED;
            }
            strPos++;
            continue;
        }
        // Similar matching for `]`
        if (polygonString[strPos] == ']') {
            curDepth--;
            if (curDepth < 0) {
                break;
            }
            strPos++;
            // We may need to set up a new geoloop at this point. If the
            // curDepth <= maxDepth - 2 then we increment the curLoop and get a
            // new one set up.
            if (curDepth <= maxDepth - 2) {
                if (curLoop == 0) {
                    out->geoloop.verts = verts;
                    out->geoloop.numVerts = curVert;
                } else {
                    GeoLoop *holes = calloc(out->numHoles + 1, sizeof(GeoLoop));
                    if (holes == 0) {
                        return E_MEMORY_ALLOC;
                    }
                    for (int i = 0; i < out->numHoles; i++) {
                        holes[i].numVerts = out->holes[i].numVerts;
                        holes[i].verts = out->holes[i].verts;
                    }
                    free(out->holes);
                    holes[out->numHoles].verts = verts;
                    holes[out->numHoles].numVerts = curVert;
                    out->holes = holes;
                    out->numHoles++;
                }
                curLoop++;
                curVert = 0;
                numVerts = 6;
                verts = calloc(numVerts, sizeof(LatLng));
            }
            continue;
        }
        // Try to grab a floating point value followed by optional whitespace, a
        // comma, and more optional whitespace, and a second floating point
        // value, then store the lat, lng pair
        double lat, lng;
        int count = 0;
        // Must grab the closing ] or we might accidentally parse a partial
        // float across buffer boundaries
        char closeBracket[2] = "]";
        int result = sscanf(polygonString + strPos, "%lf%*[, \n]%lf%1[]]%n",
                            &lat, &lng, &closeBracket[0], &count);
        if (count > 0 && result == 3) {
            verts[curVert].lat = H3_EXPORT(degsToRads)(lat);
            verts[curVert].lng = H3_EXPORT(degsToRads)(lng);
            curVert++;
            // Create a new vert buffer, copy the old buffer, and free it, if
            // necessary
            if (curVert == numVerts) {
                LatLng *newVerts = calloc(numVerts * 2, sizeof(LatLng));
                for (int i = 0; i < numVerts; i++) {
                    newVerts[i].lat = verts[i].lat;
                    newVerts[i].lng = verts[i].lng;
                }
                free(verts);
                verts = newVerts;
                numVerts *= 2;
            }
            strPos += count;
            curDepth--;
            continue;
        }
        // Check for whitespace and skip it if we reach this point.
        if (polygonString[strPos] == ',' || polygonString[strPos] == ' ' ||
            polygonString[strPos] == '\n' || polygonString[strPos] == '\t' ||
            polygonString[strPos] == '\r') {
            strPos++;
            continue;
        }
        if (strPos != 0 && fp != 0) {
            // Scan the remaining of the current buffer for `0`. If not
            // found, then we grab a new chunk and append to the remainder
            // of the existing buffer. Otherwise, just skip unknown
            // characters.
            bool endOfFile = false;
            for (int i = strPos; i <= BUFFER_SIZE; i++) {
                if (polygonString[strPos] == 0) {
                    endOfFile = true;
                    break;
                }
            }
            if (endOfFile) {
                strPos++;
            } else {
                // Move the end of this buffer to the beginning
                for (int i = strPos; i <= BUFFER_SIZE; i++) {
                    polygonString[i - strPos] = polygonString[i];
                }
                // Set the string position to the end of the buffer
                strPos = BUFFER_SIZE - strPos;
                // Grab up to the remaining size of the buffer and fill it
                // into the file
                // Did you know that if the amount you want to read from
                // the file buffer is larger than the buffer itself,
                // fread can choose to give you squat and not the
                // remainder of the file as you'd expect from the
                // documentation? This was a surprise to me and a
                // significant amount of wasted time to figure out how
                // to tackle. C leaves *way* too much as undefined
                // behavior to be nice to work with...
                int64_t i = 0;
                int result;
                do {
                    result = fread(polygonString + strPos + i, 1, 1, fp);
                    i++;
                } while (i < BUFFER_SIZE - strPos && result != 0);
                if (result == 0) {
                    polygonString[strPos + i - 1] = 0;
                }
                strPos = 0;
            }
        } else {
            strPos++;
        }
    }
    free(verts);
    return E_SUCCESS;
}