fetch/fetch-zoning-by-post.js

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchZoningByPost = void 0;
const axios_1 = __importDefault(require("axios"));
const turf = __importStar(require("@turf/turf"));
const calculate_centroid_1 = require("../utils/polyline/calculate-centroid");
const proj4_1 = __importDefault(require("proj4"));
const constants_1 = require("../utils/constants/constants");
/**
 * @namespace fetch
 * @description Functions for fetching and processing zoning data.
 */
/**
 * Fetches zoning data by performing a POST request with user-provided geometry.
 * Processes geometry intersections, offsets, and centroids.
 *
 * @memberof fetch
 * @async
 * @function fetchZoningByPost
 * @param {string} county - Name of the county.
 * @param {string} urlString - URL endpoint for querying zoning data.
 * @param {string} spatialReference - The spatial reference system (e.g., EPSG code).
 * @param {object} geometry - Geometry object representing the area of interest.
 * @param {SchemaKeys} schema - Schema defining attributes to extract.
 * @param {string} layer - The layer name for categorizing the data.
 * @returns {Promise<object>} A promise resolving to an object containing processed zoning features.
 * @throws {Error} Throws an error if the request or processing fails.
 * @example
 * const result = await fetchZoningByPost(
 *   "Horry",
 *   "https://example.com/api",
 *   "4326",
 *   { rings: [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]] },
 *   schemaKeys,
 *   "zoning"
 * );
 * console.log(result.features);
 */
const fetchZoningByPost = (county, urlString, spatialReference, geometry, schema, layer) => __awaiter(void 0, void 0, void 0, function* () {
    const dataParam = new URLSearchParams({
        outFields: "*",
        geometryType: "esriGeometryPolygon",
        geometry: JSON.stringify(geometry),
        inSR: spatialReference,
        spatialRel: "esriSpatialRelIndexIntersects",
        outSR: spatialReference,
        f: "pjson",
    });
    try {
        const response = yield axios_1.default.post(urlString, dataParam.toString(), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            httpsAgent: constants_1.agent,
        });
        const data = response.data;
        if (!data.features) {
            throw new Error(`No features returned in response: ${JSON.stringify(data, null, 2)}`);
        }
        const poly1 = turf.polygon(geometry.rings);
        const polyline1 = geometry.rings;
        const offsetter = [offsetPolygon(poly1.geometry.coordinates, 100, spatialReference).geometry.coordinates[0]];
        const poly4 = turf.polygon(offsetter);
        const clippedFeatures = data.features.map((feature) => {
            const geometry = feature.geometry;
            if (!geometry || (!geometry.rings && !geometry.paths)) {
                console.warn('Invalid or missing geometry:', JSON.stringify(feature, null, 2));
                return null;
            }
            if (geometry.rings) {
                const polyline2 = geometry.rings;
                //let splitPathINTX = splitPathAtIntersection(polyline2, polyline1);
                const poly2 = turf.polygon(geometry.rings);
                const fc = turf.featureCollection([poly1, poly2]);
                if (poly1 && poly2) {
                    try {
                        const intersection = turf.intersect(fc);
                        if (!intersection)
                            return null;
                        const flattenedIntersection = flattenGeometryRings(intersection.geometry);
                        const intersectionCoordinates = flattenedIntersection.coordinates;
                        let centroid = { x: 0, y: 0 };
                        if (intersectionCoordinates.length > 0) {
                            const polylineRings = (0, calculate_centroid_1.convertRingsToPolyline)(intersectionCoordinates);
                            const centroidXY = (0, calculate_centroid_1.calculateCentroid)(polylineRings);
                            if (centroidXY)
                                centroid = centroidXY;
                        }
                        if (flattenedIntersection) {
                            if (flattenedIntersection.coordinates.length > 1) {
                                return flattenedIntersection.coordinates.map((coordinate) => ({
                                    type: 'zoning',
                                    layer,
                                    attributes: {
                                        zoning: feature.attributes[schema.zoningKey]
                                    },
                                    centroid: (function () {
                                        const polyline = (0, calculate_centroid_1.convertRingsToPolyline)([coordinate]);
                                        const centroidXY = (0, calculate_centroid_1.calculateCentroid)(polyline);
                                        return centroidXY || centroid;
                                    })(),
                                    geometry: { rings: [coordinate] },
                                }));
                            }
                            else {
                                return {
                                    type: 'zoning',
                                    layer,
                                    attributes: {
                                        zoning: feature.attributes[schema.zoningKey]
                                    },
                                    centroid,
                                    geometry: { rings: flattenedIntersection.coordinates },
                                };
                            }
                        }
                    }
                    catch (error) {
                        console.error('Error during intersection:', error instanceof Error ? error.message : error);
                    }
                }
            }
            return null;
        }).filter((feature) => feature);
        return { features: clippedFeatures.flat() };
    }
    catch (error) {
        console.error('Error fetching zoning data:', error);
        if (error.response) {
            console.error('Response data:', JSON.stringify(error.response.data, null, 2));
            console.error('Response status:', error.response.status);
        }
        throw new Error('An error occurred while fetching zoning data');
    }
});
exports.fetchZoningByPost = fetchZoningByPost;
/**
 * Flattens geometry rings by removing unnecessary nesting and ensuring valid structure.
 *
 * @memberof fetch
 * @function flattenGeometryRings
 * @param {object} geometry - Geometry object containing rings or paths.
 * @returns {object} A geometry object with flattened coordinates.
 * @example
 * const flattened = flattenGeometryRings({ coordinates: [[[0, 0], [10, 0]]] });
 * console.log(flattened.coordinates); // [[0, 0], [10, 0]]
 */
function flattenGeometryRings(geometry) {
    const { coordinates } = geometry;
    if (coordinates.length > 1) {
        return { coordinates: coordinates.flat() };
    }
    return { coordinates };
}
/**
 * Offsets a polygon by a specified distance and reprojects coordinates.
 *
 * @memberof fetch
 * @function offsetPolygon
 * @param {number[][][]} polygonCoords - Coordinates of the polygon to offset.
 * @param {number} offsetDistance - The distance to offset the polygon.
 * @param {string} spatialReference - The spatial reference system (e.g., EPSG code).
 * @returns {object} A GeoJSON Polygon object representing the offset polygon.
 * @throws {Error} Throws an error if the offset polygon is undefined.
 * @example
 * const offset = offsetPolygon([[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]], 100, "EPSG:2273");
 * console.log(offset.geometry.coordinates);
 */
function offsetPolygon(polygonCoords, offsetDistance, spatialReference) {
    const WGS84 = proj4_1.default.WGS84;
    const sourceProjection = 'EPSG:2273';
    const reprojectedCoords = polygonCoords[0].map((coord) => (0, proj4_1.default)(sourceProjection, WGS84, coord));
    const polygon = turf.polygon([reprojectedCoords]);
    const offsetPolygon = turf.buffer(polygon, offsetDistance, { units: 'feet' });
    if (!offsetPolygon) {
        throw new Error('Offset polygon is undefined');
    }
    const offsetCoords = offsetPolygon.geometry.coordinates[0].map((coord) => (0, proj4_1.default)(WGS84, sourceProjection, coord));
    return turf.polygon([offsetCoords]);
}