routes/county-router/county-router.js

"use strict";
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 });
const express_1 = __importDefault(require("express"));
const fetch_parcel_by_pin_1 = require("../../fetch/fetch-parcel-by-pin");
const fetch_from_mongo_1 = require("../../fetch/fetch-from-mongo");
const fetch_parcel_data_by_box_1 = require("../../fetch/fetch-parcel-data-by-box");
const fetch_parcel_by_polygon_1 = require("../../fetch/fetch-parcel-by-polygon");
const axios_1 = __importDefault(require("axios"));
const calculate_centroid_1 = require("../../utils/polyline/calculate-centroid");
const fetch_ngs_data_by_box_1 = require("../../fetch/fetch-ngs-data-by-box");
const database_1 = require("../../config/database");
const fetch_flood_by_post_1 = require("../../fetch/fetch-flood-by-post");
const fetch_zoning_by_post_1 = require("../../fetch/fetch-zoning-by-post");
const constants_1 = require("../../utils/constants/constants");
/**
 * Express router for handling county-based parcel data requests.
 *
 * @type {express.Router}
 */
const router = express_1.default.Router();
/**
 * Fetches parcel data for a given state, county, and PIN (Parcel Identification Number).
 *
 * If no PIN is provided, an example PIN from the county schema is used. Fetches the parcel
 * data using the county schema's base URL and spatial reference.
 *
 * @name GET /:state/:county/pin
 * @function
 * @memberof router
 * @async
 * @param {Request} req - The Express request object.
 * @param {string} req.params.state - The state code (e.g., "SC").
 * @param {string} req.params.county - The county name (e.g., "Horry").
 * @param {string} [req.query.pin] - The optional Parcel Identification Number (PIN) for fetching data.
 * @param {Response} res - The Express response object.
 * @returns {Promise<void>} JSON response containing parcel data or an error message.
 * @example
 * // Example request:
 * // GET /SC/Horry/pin?pin=123456
 *
 * // Example response:
 * // {
 * //   features: [
 * //     {
 * //       type: 'parcel',
 * //       layer: 'VB-PROP-BNDY-LINE-E',
 * //       attributes: { ... },
 * //       centroid: { x: 12345, y: 67890 },
 * //       geometry: { rings: [ ... ] }
 * //     }
 * //   ]
 * // }
 */
router.get('/:state/:county/pin', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    const { state, county } = req.params;
    let pin = req.query.pin;
    const layer = 'VB-PROP-BNDY-LINE-E';
    console.log(state, county);
    try {
        // Fetch the county schema from the database
        const countySchema = yield (0, fetch_from_mongo_1.fetchCountySchema)(state, county);
        if (!pin) {
            // Use example PIN if none is provided
            pin = countySchema.examplePin;
        }
        let pinString = `${pin}`;
        // Check and format PIN type based on schema
        const pinType = countySchema.esriPinType;
        if (pinType === 'esriFieldTypeString') {
            pinString = `'${pin}'`;
        }
        // Fetch parcel data using the schema's base URL and spatial reference
        const parcelUrl = countySchema.baseUrls.parcel;
        const spatialReference = countySchema.spatialReference;
        const schema = countySchema.keys;
        const results = yield (0, fetch_parcel_by_pin_1.fetchDataForPin)(county, pinString, parcelUrl, spatialReference, schema, layer);
        res.json(results);
    }
    catch (error) {
        console.error('Error sending response:', error);
        res.status(500).send('An error occurred while fetching parcel data');
    }
}));
router.get('/:state/:county/box/:type', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    let baseUrl = '';
    const { state, county, type } = req.params;
    const geometry = req.query.geometry;
    if (!state || !county || !type) {
        return res.status(400).json({ message: 'State, County, or Type parameter is missing.' });
    }
    if (!geometry) {
        return res.status(400).json({ message: 'Geometry box parameter is missing.' });
    }
    const { xmin, ymin, xmax, ymax } = (0, fetch_parcel_data_by_box_1.parseGeometryBox)(geometry);
    if (!xmin || !ymin || !xmax || !ymax) {
        return res.status(400).send('Missing required query parameters: xmin, ymin, xmax, ymax');
    }
    try {
        const countySchema = yield (0, fetch_from_mongo_1.fetchCountySchema)(state, county);
        const spatialReference = countySchema.spatialReference;
        const layersDb = yield (0, database_1.connectToDatabase)('available_layers');
        const layerCollection = yield layersDb.collection('layers').findOne({}, { projection: { _id: 0 } });
        const layer = layerCollection && layerCollection[type] ? layerCollection[type] : "0";
        console.log(layer);
        // put a schema file on db that will update if a new layer type is added. Ie. parcel, building, flood, etc.
        if (type === 'parcel') {
            baseUrl = countySchema.baseUrls.parcel;
        }
        else if (type === 'building') {
            baseUrl = countySchema.baseUrls.building;
        }
        else if (type === 'flood') {
            baseUrl = 'https://hazards.fema.gov/arcgis/rest/services/public/NFHL/MapServer/28/query';
        }
        else if (type === 'roads') {
            baseUrl = countySchema.baseUrls.roads;
        }
        else if (type === 'zoning') {
            baseUrl = countySchema.baseUrls.zoning;
        }
        else if (type === 'boundary') {
            baseUrl = countySchema.baseUrls.boundary;
        }
        else if (type === 'noaa') {
            baseUrl = 'https://services2.arcgis.com/C8EMgrsFcRFL6LrL/arcgis/rest/services/NGS_Datasheets_Feature_Service/FeatureServer/1/query';
        }
        else if (type === 'contour') {
            baseUrl = countySchema.baseUrls.contour;
        }
        else if (type === 'zip') {
            baseUrl = countySchema.baseUrls.zip;
        }
        const results = yield (0, fetch_parcel_data_by_box_1.fetchParcelDataByBbox)(Number(xmin), Number(ymin), Number(xmax), Number(ymax), baseUrl, spatialReference, county, countySchema.keys, type, layer);
        res.json(results);
    }
    catch (error) {
        console.error('Error fetching parcel data:', error);
        res.status(500).send('An error occurred while fetching parcel data');
    }
    finally {
        //await closeClient();
    }
}));
router.get('/:state/:county/adjoiners', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    const { state, county } = req.params;
    const pin = req.query.pin;
    let pinString = `${pin}`;
    console.log(pin);
    if (!pin) {
        return res.status(400).send('Missing required query parameter: pin');
    }
    try {
        const countySchema = yield (0, fetch_from_mongo_1.fetchCountySchema)(state, county);
        const layersDb = yield (0, database_1.connectToDatabase)('available_layers');
        const layerCollection = yield layersDb.collection('layers').findOne({}, { projection: { _id: 0 } });
        const layer = layerCollection && layerCollection['parcel'] ? layerCollection['parcel'] : "0";
        console.log(layer);
        const pinType = countySchema.esriPinType;
        if (pinType === 'esriFieldTypeString') {
            pinString = `'${pin}'`;
            console.log('pinType is string');
        }
        const parcelUrl = countySchema.baseUrls.parcel;
        const spatialReference = countySchema.spatialReference;
        const schema = countySchema.keys;
        const pinKey = schema["pinKey"];
        const response = yield axios_1.default.get(`${parcelUrl}?where=${pinKey}=${pinString}&inSR=${spatialReference}&outSR=${spatialReference}&outFields=*&f=pjson`, { httpsAgent: constants_1.agent });
        const rings = response.data.features[0].geometry;
        //console.log(rings)
        // Fetch attributes and geometry
        const { features } = yield (0, fetch_parcel_by_polygon_1.fetchParcelByPolygon)(county, parcelUrl, spatialReference, rings, schema, layer);
        res.send({
            features
        });
    }
    catch (e) {
        console.error("Error fetching polyline bounds:", e);
        return res.status(500).send("An error occurred while fetching polyline bounds");
    }
    finally {
        //await closeClient();
    }
}));
// fetch the county boundary and calculate centroid. The goal for this is to also be able to create an overall scale factor for the county in the future.
router.get('/:state/:county/boundary', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    const { state, county } = req.params;
    try {
        const countySchema = yield (0, fetch_from_mongo_1.fetchCountySchema)(state, county);
        const boundaryUrl = countySchema.baseUrls.boundary;
        if (!boundaryUrl) {
            console.error("The county schema provided does not contain the overall boundary. Please add it to the schema file if you wish to continue");
            return res.status(500).send("The county schema provided does not contain the overall boundary. Please add it to the schema file if you wish to continue");
        }
        const spatialReference = countySchema.spatialReference;
        const url = `${boundaryUrl}?where=1%3D1&inSR=${spatialReference}&outSR=${spatialReference}&outFields=*&f=pjson`;
        console.log(url);
        const response = yield axios_1.default.get(url, { 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 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;
            }
            // Handle the case where geometry.rings is present
            if (geometry.rings && geometry.rings.length > 0) {
                const firstRing = geometry.rings[0]; // Get only the first ring
                const polyline = (0, calculate_centroid_1.convertRingsToPolyline)([firstRing]); // Convert only the first ring to a polyline
                const centroid = (0, calculate_centroid_1.calculateCentroid)(polyline); // Calculate centroid of the first ring
                return {
                    attributes: {
                        LegalDescr: `${county} county centroid X: ${(centroid === null || centroid === void 0 ? void 0 : centroid.x) ? centroid.x : ""} Y: ${(centroid === null || centroid === void 0 ? void 0 : centroid.y) ? centroid.y : ""}`
                    },
                    centroid,
                    geometry: { rings: [firstRing] } // Include only the first ring in the output
                };
            }
            // Handle the case where geometry.paths is present
            if (geometry.paths && geometry.paths.length > 0) {
                const firstPath = geometry.paths[0]; // Get only the first path
                const polyline = (0, calculate_centroid_1.convertRingsToPolyline)([firstPath]); // Convert only the first path to a polyline
                const centroid = (0, calculate_centroid_1.calculateCentroid)(polyline); // Calculate centroid of the first path
                return {
                    attributes: {
                        LegalDescr: `${county} county centroid X: ${(centroid === null || centroid === void 0 ? void 0 : centroid.x) ? centroid.x : ""} Y: ${(centroid === null || centroid === void 0 ? void 0 : centroid.y) ? centroid.y : ""}`
                    },
                    centroid,
                    geometry: { paths: [firstPath] } // Include only the first path in the output
                };
            }
            return null;
        }).filter((feature) => { var _a, _b; return feature && feature.geometry && (((_a = feature.geometry.rings) === null || _a === void 0 ? void 0 : _a.length) > 0 || ((_b = feature.geometry.paths) === null || _b === void 0 ? void 0 : _b.length) > 0); });
        res.send({ features: clippedFeatures });
    }
    catch (e) {
        console.error("Error fetching polyline bounds:", e);
        return res.status(500).send("An error occurred while fetching polyline bounds");
    }
}));
router.get('/:state/:county/ngs', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    const baseUrl = 'https://services2.arcgis.com/C8EMgrsFcRFL6LrL/arcgis/rest/services/NGS_Datasheets_Feature_Service/FeatureServer/1/query';
    const { state, county } = req.params;
    const geometry = req.query.geometry;
    if (!geometry) {
        return res.status(400).json({ message: 'Geometry box parameter is missing.' });
    }
    const { xmin, ymin, xmax, ymax } = (0, fetch_parcel_data_by_box_1.parseGeometryBox)(geometry);
    if (!xmin || !ymin || !xmax || !ymax) {
        return res.status(400).send('Missing required query parameters: xmin, ymin, xmax, ymax');
    }
    try {
        const countySchema = yield (0, fetch_from_mongo_1.fetchCountySchema)(state, county);
        const spatialReference = countySchema.spatialReference;
        const results = yield (0, fetch_ngs_data_by_box_1.fetchNGSdataByBox)(Number(xmin), Number(ymin), Number(xmax), Number(ymax), baseUrl, spatialReference);
        res.json(results);
    }
    catch (error) {
        console.error('Error fetching parcel data:', error);
        res.status(500).send('An error occurred while fetching parcel data');
    }
}));
router.get('/:state/:county/flood', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    const { state, county } = req.params;
    const geometry = req.query.geometry;
    let parsedGeometry;
    if (!geometry) {
        return res.status(400).send('Missing required query parameter: geometry');
    }
    try {
        parsedGeometry = JSON.parse(decodeURIComponent(geometry));
    }
    catch (e) {
        console.error('Error parsing geometry:', e);
        return res.status(400).send('Invalid geometry parameter');
    }
    try {
        const countySchema = yield (0, fetch_from_mongo_1.fetchCountySchema)(state, county);
        const layersDb = yield (0, database_1.connectToDatabase)('available_layers');
        const layerCollection = yield layersDb.collection('layers').findOne({}, { projection: { _id: 0 } });
        const layer = layerCollection && layerCollection['flood'] ? layerCollection['flood'] : "0";
        console.log(layer);
        const floodUrl = 'https://hazards.fema.gov/arcgis/rest/services/public/NFHLWMS/MapServer/28/query';
        const spatialReference = countySchema.spatialReference;
        const schema = countySchema.keys;
        const features = yield (0, fetch_flood_by_post_1.fetchFloodByPost)(county, floodUrl, spatialReference, parsedGeometry, schema, layer);
        res.send(features);
    }
    catch (e) {
        console.error("Error fetching polyline bounds:", e);
        return res.status(500).send("An error occurred while fetching polyline bounds");
    }
    finally {
        yield (0, database_1.closeClient)();
    }
}));
router.get('/:state/:county/zoning', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
    const { state, county } = req.params;
    const geometry = req.query.geometry;
    let parsedGeometry;
    if (!geometry) {
        return res.status(400).send('Missing required query parameter: geometry');
    }
    try {
        parsedGeometry = JSON.parse(decodeURIComponent(geometry));
    }
    catch (e) {
        console.error('Error parsing geometry:', e);
        return res.status(400).send('Invalid geometry parameter');
    }
    try {
        const countySchema = yield (0, fetch_from_mongo_1.fetchCountySchema)(state, county);
        const layersDb = yield (0, database_1.connectToDatabase)('available_layers');
        const layerCollection = yield layersDb.collection('layers').findOne({}, { projection: { _id: 0 } });
        const layer = layerCollection && layerCollection['zoning'] ? layerCollection['zoning'] : "0";
        console.log(layer);
        const zoningUrl = 'https://www.horrycountysc.gov/parcelapp/rest/services/HorryCountyGISApp/MapServer/30/query';
        const spatialReference = countySchema.spatialReference;
        const schema = countySchema.keys;
        const features = yield (0, fetch_zoning_by_post_1.fetchZoningByPost)(county, zoningUrl, spatialReference, parsedGeometry, schema, layer);
        res.send(features);
    }
    catch (e) {
        console.error("Error fetching polyline bounds:", e);
        return res.status(500).send("An error occurred while fetching polyline bounds");
    }
    finally {
        yield (0, database_1.closeClient)();
    }
}));
exports.default = router;