From 6fe5374228ee83ec95b4ba290b40021baefdd88c Mon Sep 17 00:00:00 2001 From: Morgan Ludtke <42942267+ludtkemorgan@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:49:15 -0600 Subject: [PATCH] fix: add map layer geocoding checks (#3825) * fix: add map layer geocoding checks * fix: review comment fix --- backend/core/package.json | 1 + .../src/applications/applications.module.ts | 3 +- .../services/geocoding.service.spec.ts | 163 ++++++++- .../services/geocoding.service.ts | 110 +++++- .../map-layers/entities/map-layer.entity.ts | 5 + .../1705637577495-add-feature-collection.ts | 13 + backend/core/src/seeder/seeds/map-layers.ts | 332 +++++++++++++++++- yarn.lock | 11 +- 8 files changed, 605 insertions(+), 33 deletions(-) create mode 100644 backend/core/src/migration/1705637577495-add-feature-collection.ts diff --git a/backend/core/package.json b/backend/core/package.json index ea73a9cb3d..184c70b1e0 100644 --- a/backend/core/package.json +++ b/backend/core/package.json @@ -55,6 +55,7 @@ "@turf/buffer": "6.5.0", "@turf/helpers": "6.5.0", "@turf/boolean-point-in-polygon": "6.5.0", + "@turf/points-within-polygon": "6.5.0", "@types/cache-manager": "^3.4.0", "async-retry": "^1.3.1", "axios": "0.21.3", diff --git a/backend/core/src/applications/applications.module.ts b/backend/core/src/applications/applications.module.ts index 16bbc1dce7..570b0b5036 100644 --- a/backend/core/src/applications/applications.module.ts +++ b/backend/core/src/applications/applications.module.ts @@ -7,6 +7,7 @@ import { SharedModule } from "../shared/shared.module" import { ListingsModule } from "../listings/listings.module" import { Address } from "../shared/entities/address.entity" import { Applicant } from "./entities/applicant.entity" +import { MapLayer } from "../map-layers/entities/map-layer.entity" import { ApplicationsSubmissionController } from "./applications-submission.controller" import { TranslationsModule } from "../translations/translations.module" import { Listing } from "../listings/entities/listing.entity" @@ -20,7 +21,7 @@ import { GeocodingService } from "./services/geocoding.service" @Module({ imports: [ - TypeOrmModule.forFeature([Application, Applicant, Address, Listing]), + TypeOrmModule.forFeature([Application, Applicant, Address, Listing, MapLayer]), AuthModule, ActivityLogModule, SharedModule, diff --git a/backend/core/src/applications/services/geocoding.service.spec.ts b/backend/core/src/applications/services/geocoding.service.spec.ts index 682749d7fc..5a21a65d8d 100644 --- a/backend/core/src/applications/services/geocoding.service.spec.ts +++ b/backend/core/src/applications/services/geocoding.service.spec.ts @@ -6,6 +6,8 @@ import { Application } from "../entities/application.entity" import { ValidationMethod } from "../../multiselect-question/types/validation-method-enum" import { Listing } from "../../listings/entities/listing.entity" import { InputType } from "../../shared/types/input-type" +import { MapLayer } from "../../map-layers/entities/map-layer.entity" +import { FeatureCollection } from "@turf/helpers" describe("GeocodingService", () => { let service: GeocodingService @@ -15,7 +17,7 @@ describe("GeocodingService", () => { update: applicationRepoUpdate, } const date = new Date() - const listingAddress: Address = { + const address: Address = { id: "id", createdAt: date, updatedAt: date, @@ -28,6 +30,47 @@ describe("GeocodingService", () => { latitude: 38.8977, longitude: -77.0365, } + const featureCollection: FeatureCollection = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + properties: {}, + geometry: { + coordinates: [ + [ + [ + [-77.0392589333301, 38.79186072967565], + [-76.90981025809415, 38.89293952026222], + [-77.04122027689426, 38.996161202682146], + [-77.12000091005532, 38.93465307055658], + [-77.10561772391833, 38.91990351952725], + [-77.09123453778136, 38.90565966392609], + [-77.06802530560486, 38.9015894658674], + [-77.06181438431805, 38.889377471720564], + [-77.03697069917165, 38.870801038935525], + [-77.03043288729134, 38.850437727576235], + [-77.03435557441966, 38.80816525459605], + [-77.0392589333301, 38.79186072967565], + ], + ], + ], + type: "Polygon", + }, + }, + ], + } + const mockMapLayerRepo = { + createQueryBuilder: jest.fn(), + findBy: jest.fn().mockResolvedValue([ + { + id: "mapLayerId", + name: "map layer", + jurisdictionId: "1", + featureCollection: featureCollection, + }, + ]), + } beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -37,6 +80,10 @@ describe("GeocodingService", () => { provide: getRepositoryToken(Application), useValue: mockApplicationRepo, }, + { + provide: getRepositoryToken(MapLayer), + useValue: mockMapLayerRepo, + }, ], }).compile() @@ -48,12 +95,12 @@ describe("GeocodingService", () => { expect( service.verifyRadius( { - ...listingAddress, + ...address, latitude: null, longitude: null, }, 5, - listingAddress + address ) ).toBe("unknown") }) @@ -61,12 +108,12 @@ describe("GeocodingService", () => { expect( service.verifyRadius( { - ...listingAddress, + ...address, latitude: 38.89485, longitude: -77.04251, }, 5, - listingAddress + address ) ).toBe("true") }) @@ -74,12 +121,12 @@ describe("GeocodingService", () => { expect( service.verifyRadius( { - ...listingAddress, + ...address, latitude: 39.284205, longitude: -76.621698, }, 5, - listingAddress + address ) ).toBe("false") }) @@ -87,17 +134,42 @@ describe("GeocodingService", () => { expect( service.verifyRadius( { - ...listingAddress, + ...address, }, 5, - listingAddress + address ) ).toBe("true") }) }) + describe("verifyLayers", () => { + it("should return 'unknown' if no lat/long", () => { + expect( + service.verifyLayers( + { + ...address, + latitude: null, + longitude: null, + }, + featureCollection + ) + ).toBe("unknown") + }) + it("should return 'true' if address is within layer", () => { + expect(service.verifyLayers(address, featureCollection)).toBe("true") + }) + it("should return 'false' if address is within layer", () => { + expect( + service.verifyLayers( + { ...address, latitude: 39.284205, longitude: -76.621698 }, + featureCollection + ) + ).toBe("false") + }) + }) describe("validateRadiusPreferences", () => { const listing = { - buildingAddress: listingAddress, + buildingAddress: address, listingMultiselectQuestions: [ { multiselectQuestion: { @@ -113,7 +185,7 @@ describe("GeocodingService", () => { }, ], } - const preferenceAddress = { ...listingAddress, latitude: 38.89485, longitude: -77.04251 } + const preferenceAddress = { ...address, latitude: 38.89485, longitude: -77.04251 } const application = { id: "applicationId", preferences: [ @@ -164,4 +236,73 @@ describe("GeocodingService", () => { ) }) }) + describe("validateGeoLayerPreferences", () => { + const listing = { + buildingAddress: address, + listingMultiselectQuestions: [ + { + multiselectQuestion: { + options: [ + { + text: "Geocoding option by map", + collectAddress: true, + mapLayerId: "mapLayerId", + validationMethod: ValidationMethod.map, + }, + ], + }, + }, + ], + } + const preferenceAddress = { ...address, latitude: 38.89485, longitude: -77.04251 } + const application = { + id: "applicationId", + preferences: [ + { + key: "Geocoding preference", + options: [ + { + key: "Geocoding option by map", + checked: true, + extraData: [ + { + type: InputType.address, + value: preferenceAddress, + }, + ], + }, + ], + }, + ], + } + it("should save the validated value as extraData for map layer", async () => { + await service.validateGeoLayerPreferences( + (application as unknown) as Application, + listing as Listing + ) + expect(applicationRepoUpdate).toBeCalledWith( + { id: "applicationId" }, + { + preferences: expect.arrayContaining([ + expect.objectContaining({ + key: "Geocoding preference", + options: [ + { + checked: true, + extraData: [ + { + type: "address", + value: preferenceAddress, + }, + { key: "geocodingVerified", type: "text", value: "true" }, + ], + key: "Geocoding option by map", + }, + ], + }), + ]), + } + ) + }) + }) }) diff --git a/backend/core/src/applications/services/geocoding.service.ts b/backend/core/src/applications/services/geocoding.service.ts index bc20daedc6..651827fa35 100644 --- a/backend/core/src/applications/services/geocoding.service.ts +++ b/backend/core/src/applications/services/geocoding.service.ts @@ -1,10 +1,12 @@ -import { point } from "@turf/helpers" +import { FeatureCollection, point, polygons } from "@turf/helpers" import buffer from "@turf/buffer" import booleanPointInPolygon from "@turf/boolean-point-in-polygon" +import pointsWithinPolygon from "@turf/points-within-polygon" import { InjectRepository } from "@nestjs/typeorm" -import { Repository } from "typeorm" +import { In, Repository } from "typeorm" import { Address } from "../../shared/entities/address.entity" import { Application } from "../entities/application.entity" +import { MapLayer } from "../../map-layers/entities/map-layer.entity" import { Listing } from "../../listings/entities/listing.entity" import { ValidationMethod } from "../../multiselect-question/types/validation-method-enum" import { MultiselectOption } from "../../multiselect-question/types/multiselect-option" @@ -15,11 +17,13 @@ import { GeocodingValues } from "../../shared/types/geocoding-values" export class GeocodingService { constructor( - @InjectRepository(Application) private readonly repository: Repository + @InjectRepository(Application) private readonly applicationRepository: Repository, + @InjectRepository(MapLayer) private readonly mapLayerRepository: Repository ) {} public async validateGeocodingPreferences(application: Application, listing: Listing) { await this.validateRadiusPreferences(application, listing) + await this.validateGeoLayerPreferences(application, listing) } verifyRadius( @@ -48,6 +52,43 @@ export class GeocodingService { return GeocodingValues.unknown } + verifyLayers( + preferenceAddress: Address, + featureCollectionLayers: FeatureCollection + ): GeocodingValues { + try { + if (preferenceAddress.latitude && preferenceAddress.longitude) { + const preferencePoint = point([ + Number.parseFloat(preferenceAddress.longitude.toString()), + Number.parseFloat(preferenceAddress.latitude.toString()), + ]) + + // Convert the features to the format that turfjs wants + const polygonsFromFeature = [] + featureCollectionLayers.features.forEach((feature) => { + if (feature.geometry.type === "MultiPolygon" || feature.geometry.type === "Polygon") { + feature.geometry.coordinates.forEach((coordinate) => { + polygonsFromFeature.push(coordinate) + }) + } + }) + const layer = polygons(polygonsFromFeature) + + const points = pointsWithinPolygon(preferencePoint, layer) + if (points && points.features?.length) { + return GeocodingValues.true + } + + return GeocodingValues.false + } + } catch (e) { + console.log("e", e) + } + // If the geocoding value was not able to be verified we need to set it as "unknown" + // in order to signify we are unable to automatically verify and manually checking will need to be done + return GeocodingValues.unknown + } + public async validateRadiusPreferences(application: Application, listing: Listing) { // Get all radius preferences from the listing const radiusPreferenceOptions: MultiselectOption[] = listing.listingMultiselectQuestions.reduce( @@ -95,7 +136,68 @@ export class GeocodingService { return { ...preference, options: newPreferenceOptions } } ) - await this.repository.update({ id: application.id }, { preferences: preferences }) + await this.applicationRepository.update({ id: application.id }, { preferences: preferences }) + } + } + + public async validateGeoLayerPreferences(application: Application, listing: Listing) { + // Get all map layer preferences from the listing + const mapPreferenceOptions: MultiselectOption[] = listing.listingMultiselectQuestions?.reduce( + (options, multiselectQuestion) => { + const newOptions = multiselectQuestion.multiselectQuestion?.options?.filter( + (option) => option.validationMethod === ValidationMethod.map + ) + return [...options, ...newOptions] + }, + [] + ) + + const preferencesOptions = ( + preference: ApplicationMultiselectQuestion, + mapLayers: MapLayer[] + ): ApplicationMultiselectQuestionOption[] => { + const preferenceOptions = [] + preference.options.forEach((option) => { + const addressData = option.extraData.find((data) => data.type === InputType.address) + if (option.checked && addressData) { + const foundOption = mapPreferenceOptions.find( + (preferenceOption) => preferenceOption.text === option.key + ) + if (foundOption && foundOption.mapLayerId) { + const layer = mapLayers.find((layer) => layer.id === foundOption.mapLayerId) + const geocodingVerified = this.verifyLayers( + addressData.value as Address, + layer?.featureCollection + ) + preferenceOptions.push({ + ...option, + extraData: [ + ...option.extraData, + { + key: "geocodingVerified", + type: InputType.text, + value: geocodingVerified, + }, + ], + }) + return + } + } + preferenceOptions.push(option) + }) + return preferenceOptions + } + if (mapPreferenceOptions?.length) { + const preferences = [] + const mapLayers = await this.mapLayerRepository.findBy({ + id: In(mapPreferenceOptions.map((option) => option.mapLayerId)), + }) + application.preferences.forEach((preference) => { + const newPreferenceOptions = preferencesOptions(preference, mapLayers) + preferences.push({ ...preference, options: newPreferenceOptions }) + }) + + await this.applicationRepository.update({ id: application.id }, { preferences: preferences }) } } } diff --git a/backend/core/src/map-layers/entities/map-layer.entity.ts b/backend/core/src/map-layers/entities/map-layer.entity.ts index 2417c9abf3..e7edb5eac0 100644 --- a/backend/core/src/map-layers/entities/map-layer.entity.ts +++ b/backend/core/src/map-layers/entities/map-layer.entity.ts @@ -20,4 +20,9 @@ export class MapLayer { @Column() @IsString({ groups: [ValidationsGroupsEnum.default] }) jurisdictionId: string + + @Column("jsonb") + @Expose() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + featureCollection: any } diff --git a/backend/core/src/migration/1705637577495-add-feature-collection.ts b/backend/core/src/migration/1705637577495-add-feature-collection.ts new file mode 100644 index 0000000000..04b1e08d67 --- /dev/null +++ b/backend/core/src/migration/1705637577495-add-feature-collection.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class addFeatureCollection1705637577495 implements MigrationInterface { + name = "addFeatureCollection1705637577495" + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "map_layers" ADD "feature_collection" jsonb NOT NULL`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "map_layers" DROP COLUMN "feature_collection"`) + } +} diff --git a/backend/core/src/seeder/seeds/map-layers.ts b/backend/core/src/seeder/seeds/map-layers.ts index 87fd08043c..d9c65a3d96 100644 --- a/backend/core/src/seeder/seeds/map-layers.ts +++ b/backend/core/src/seeder/seeds/map-layers.ts @@ -14,22 +14,322 @@ export class MapLayerSeeder { { name: "Map Layer 1", jurisdictionId: jurisdictions?.[0]?.id ?? "1", - }, - { - name: "Map Layer 2", - jurisdictionId: jurisdictions?.[0]?.id ?? "1", - }, - { - name: "Map Layer 3", - jurisdictionId: jurisdictions?.[0]?.id ?? "1", - }, - { - name: "Map Layer 4", - jurisdictionId: jurisdictions?.[1]?.id ?? "2", - }, - { - name: "Map Layer 5", - jurisdictionId: jurisdictions?.[2]?.id ?? "3", + featureCollection: { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [-122.26591, 37.89001], + [-122.26565, 37.88796], + [-122.26533, 37.88531], + [-122.26311, 37.88555], + [-122.26276, 37.88617], + [-122.2626, 37.88673], + [-122.2626, 37.88705], + [-122.26255, 37.88735], + [-122.26236, 37.8875], + [-122.26211, 37.88761], + [-122.26177, 37.88773], + [-122.26153, 37.88782], + [-122.26144, 37.88802], + [-122.26145, 37.88821], + [-122.2616, 37.88848], + [-122.26208, 37.88886], + [-122.2623, 37.8891], + [-122.26241, 37.88967], + [-122.26188, 37.88994], + [-122.2609, 37.89018], + [-122.26052, 37.89016], + [-122.2602, 37.89014], + [-122.25989, 37.89016], + [-122.25931, 37.89032], + [-122.25876, 37.89063], + [-122.25887, 37.89067], + [-122.25919, 37.89067], + [-122.25943, 37.8907], + [-122.25976, 37.89081], + [-122.25983, 37.89091], + [-122.25991, 37.89104], + [-122.25969, 37.8914], + [-122.25976, 37.89166], + [-122.26018, 37.89202], + [-122.26051, 37.89218], + [-122.26087, 37.89218], + [-122.26223, 37.89188], + [-122.26268, 37.8917], + [-122.26314, 37.89137], + [-122.26353, 37.89106], + [-122.26407, 37.89062], + [-122.2649, 37.89022], + [-122.26535, 37.89002], + [-122.26591, 37.89001], + ], + ], + ], + }, + properties: { + area_id: 6664, + city_id: 17, + grade: "D", + fill: "#d9838d", + label: "D1", + name: " ", + category_id: 4, + sheets: 1, + area: 0.0000230966496717784, + bounds: [ + [37.88531, -122.26591], + [37.89218, -122.25876], + ], + label_coords: [37.888, -122.264], + residential: true, + commercial: false, + industrial: false, + }, + }, + { + type: "Feature", + geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [-122.27239, 37.86322], + [-122.27236, 37.86524], + [-122.27271, 37.8653], + [-122.27275, 37.86581], + [-122.27328, 37.86584], + [-122.27332, 37.86662], + [-122.27303, 37.86696], + [-122.27253, 37.86696], + [-122.27268, 37.86879], + [-122.27371, 37.86876], + [-122.27392, 37.87025], + [-122.27296, 37.87062], + [-122.27303, 37.87119], + [-122.29152, 37.86896], + [-122.29132, 37.86812], + [-122.28897, 37.86111], + [-122.27239, 37.86322], + ], + [ + [-122.28572, 37.86679], + [-122.28455, 37.86695], + [-122.28449, 37.86636], + [-122.2857, 37.86622], + [-122.28572, 37.86679], + ], + ], + [ + [ + [-122.2732, 37.87251], + [-122.27479, 37.87235], + [-122.27526, 37.8765], + [-122.28269, 37.87569], + [-122.28235, 37.87301], + [-122.29281, 37.87163], + [-122.29201, 37.86973], + [-122.27314, 37.87202], + [-122.2732, 37.87251], + ], + ], + ], + }, + properties: { + area_id: 6774, + city_id: 17, + grade: "D", + fill: "#d9838d", + label: "D2", + name: " ", + category_id: 4, + sheets: 1, + area: 0.000197049068477924, + bounds: [ + [37.86111, -122.29281], + [37.8765, -122.27236], + ], + label_coords: [37.866, -122.279], + residential: true, + commercial: false, + industrial: false, + }, + id: 10, + }, + { + type: "Feature", + geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [-122.28911, 37.84974], + [-122.28665, 37.85025], + [-122.28947, 37.85919], + [-122.28987, 37.86059], + [-122.29112, 37.86446], + [-122.29249, 37.86893], + [-122.29947, 37.86731], + [-122.30288, 37.86649], + [-122.30249, 37.86552], + [-122.3012, 37.86364], + [-122.30055, 37.86264], + [-122.30056, 37.86192], + [-122.30041, 37.86166], + [-122.29993, 37.86082], + [-122.29948, 37.85998], + [-122.29893, 37.85862], + [-122.29834, 37.85694], + [-122.2977, 37.85497], + [-122.29654, 37.85182], + [-122.29601, 37.85002], + [-122.29601, 37.8496], + [-122.29559, 37.84842], + [-122.28911, 37.84974], + ], + ], + [ + [ + [-122.29704, 37.88312], + [-122.29804, 37.88301], + [-122.29894, 37.88281], + [-122.29949, 37.88271], + [-122.30042, 37.88277], + [-122.30115, 37.88271], + [-122.30215, 37.88249], + [-122.30306, 37.88239], + [-122.30348, 37.8822], + [-122.3055, 37.88209], + [-122.30612, 37.882], + [-122.30691, 37.88181], + [-122.30741, 37.88175], + [-122.30783, 37.88187], + [-122.30812, 37.88211], + [-122.30831, 37.8822], + [-122.30959, 37.88196], + [-122.30698, 37.87299], + [-122.30479, 37.86635], + [-122.30298, 37.86678], + [-122.30306, 37.86731], + [-122.30244, 37.86759], + [-122.29284, 37.86967], + [-122.29704, 37.88312], + ], + [ + [-122.29787, 37.8724], + [-122.29692, 37.87264], + [-122.29651, 37.87144], + [-122.29752, 37.87126], + [-122.29787, 37.8724], + ], + ], + ], + }, + properties: { + area_id: 6672, + city_id: 17, + grade: "D", + fill: "#d9838d", + label: "D3", + name: " ", + category_id: 4, + sheets: 1, + area: 0.000366050178227747, + bounds: [ + [37.84842, -122.30959], + [37.88312, -122.28665], + ], + label_coords: [37.863, -122.296], + residential: true, + commercial: false, + industrial: false, + }, + id: 12, + }, + { + type: "Feature", + geometry: { + type: "MultiPolygon", + coordinates: [ + [ + [ + [-122.27165, 37.85605], + [-122.2719, 37.85796], + [-122.27262, 37.85796], + [-122.27276, 37.85959], + [-122.27201, 37.85973], + [-122.27215, 37.86142], + [-122.26838, 37.86198], + [-122.26869, 37.8637], + [-122.27239, 37.86322], + [-122.28897, 37.86111], + [-122.28559, 37.85014], + [-122.28381, 37.8505], + [-122.28313, 37.84867], + [-122.28271, 37.84876], + [-122.28185, 37.84681], + [-122.27675, 37.84746], + [-122.27625, 37.84566], + [-122.27314, 37.846], + [-122.27265, 37.84605], + [-122.27232, 37.84679], + [-122.27511, 37.84639], + [-122.27545, 37.8482], + [-122.27146, 37.84869], + [-122.27037, 37.85112], + [-122.2708, 37.85382], + [-122.27169, 37.85388], + [-122.27212, 37.85582], + [-122.27165, 37.85605], + ], + [ + [-122.28512, 37.85717], + [-122.28352, 37.85733], + [-122.283, 37.85442], + [-122.28464, 37.85417], + [-122.28512, 37.85717], + ], + ], + [ + [ + [-122.26623, 37.8623], + [-122.26474, 37.86252], + [-122.26498, 37.86432], + [-122.26545, 37.86711], + [-122.26688, 37.86696], + [-122.26623, 37.8623], + ], + ], + ], + }, + properties: { + area_id: 6675, + city_id: 17, + grade: "D", + fill: "#d9838d", + label: "D4", + name: " ", + category_id: 4, + sheets: 1, + area: 0.000231566437344764, + bounds: [ + [37.84566, -122.28897], + [37.86711, -122.26474], + ], + label_coords: [37.853, -122.277], + residential: true, + commercial: false, + industrial: false, + }, + id: 13, + }, + ], + }, }, ] diff --git a/yarn.lock b/yarn.lock index 6e6863a695..270b6ce5fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5272,7 +5272,7 @@ "@turf/helpers" "^6.5.0" "@turf/meta" "^6.5.0" -"@turf/boolean-point-in-polygon@6.5.0": +"@turf/boolean-point-in-polygon@6.5.0", "@turf/boolean-point-in-polygon@^6.5.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz#6d2e9c89de4cd2e4365004c1e51490b7795a63cf" integrity sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A== @@ -5327,6 +5327,15 @@ dependencies: "@turf/helpers" "^6.5.0" +"@turf/points-within-polygon@6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@turf/points-within-polygon/-/points-within-polygon-6.5.0.tgz#d49f4d7cf19b7a440bf1e06f771ff4e1df13107f" + integrity sha512-YyuheKqjliDsBDt3Ho73QVZk1VXX1+zIA2gwWvuz8bR1HXOkcuwk/1J76HuFMOQI3WK78wyAi+xbkx268PkQzQ== + dependencies: + "@turf/boolean-point-in-polygon" "^6.5.0" + "@turf/helpers" "^6.5.0" + "@turf/meta" "^6.5.0" + "@turf/projection@^6.5.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@turf/projection/-/projection-6.5.0.tgz#d2aad862370bf03f2270701115464a8406c144b2"