From 11b1a209f7b688e5b9f2d54c2dace460b268d548 Mon Sep 17 00:00:00 2001 From: Aymeric Dominique Date: Wed, 24 Apr 2024 20:18:12 +0200 Subject: [PATCH] hop --- src/db/bordeaux-address.model.ts | 5 +- src/db/est_ensemble-address.model.ts | 5 +- src/db/lille-address.model.ts | 5 +- src/db/lyon-address.model.ts | 5 +- src/db/montpellier-address.model.ts | 5 +- src/db/plaine_commune-address.model.ts | 5 +- src/db/rent.service.ts | 17 ++- src/interfaces/json-item-bordeaux.ts | 3 + src/services/address/address-factory.ts | 16 +++ ...{address.ts => default-address-service.ts} | 113 +++++------------- src/services/address/paris-address-service.ts | 44 +++++++ src/services/diggers/dig.ts | 6 +- src/services/districts/addresses.ts | 93 ++++++-------- src/services/districts/districts-list.ts | 101 ++++++++-------- src/services/districts/districts.ts | 9 +- .../district-filter/bordeaux-district.ts | 24 ++-- .../district-filter-factory.ts | 2 +- .../district-filter/district-filter-parent.ts | 32 ++++- .../filters/district-filter/lille-district.ts | 5 + .../filters/district-filter/lyon-district.ts | 6 +- .../district-filter/montpellier-district.ts | 2 +- .../plaine-commune-district.ts | 11 ++ .../encadrement-filter-factory.ts | 2 +- .../encadrement-filter/filter-bordeaux.ts | 9 +- .../encadrement-filter/filter-est-ensemble.ts | 9 +- .../encadrement-filter/filter-lille.ts | 9 +- .../filters/encadrement-filter/filter-lyon.ts | 9 +- .../encadrement-filter/filter-montpellier.ts | 9 +- .../encadrement-filter/filter-paris.ts | 9 +- .../filter-plaine-commune.ts | 9 +- src/services/helpers/cleanup.ts | 2 +- src/services/simulator/manual-result.ts | 4 +- src/services/stats/chloropleth-map.ts | 4 +- src/services/stats/map.ts | 4 +- src/services/websites/website.ts | 2 +- 35 files changed, 317 insertions(+), 278 deletions(-) create mode 100644 src/services/address/address-factory.ts rename src/services/address/{address.ts => default-address-service.ts} (62%) create mode 100644 src/services/address/paris-address-service.ts diff --git a/src/db/bordeaux-address.model.ts b/src/db/bordeaux-address.model.ts index e9122c44..27abc92e 100644 --- a/src/db/bordeaux-address.model.ts +++ b/src/db/bordeaux-address.model.ts @@ -22,9 +22,10 @@ const schema = new Schema({ }) schema.index( - { nom_voie: 'text', numero: 'text' }, + { nom_commune: 'text', nom_voie: 'text', numero: 'text' }, { weights: { + nom_commune: 10, nom_voie: 10, numero: 1, }, @@ -33,7 +34,7 @@ schema.index( ) schema.index( - { nom_voie: 1, numero: 1 }, + { nom_commune: 1, nom_voie: 1, numero: 1 }, { collation: { locale: 'fr', diff --git a/src/db/est_ensemble-address.model.ts b/src/db/est_ensemble-address.model.ts index cdf6ee61..062bcd3e 100644 --- a/src/db/est_ensemble-address.model.ts +++ b/src/db/est_ensemble-address.model.ts @@ -29,9 +29,10 @@ const schema = new Schema({ }) schema.index( - { nom_voie: 'text', numero: 'text' }, + { nom_commune: 'text', nom_voie: 'text', numero: 'text' }, { weights: { + nom_commune: 10, nom_voie: 10, numero: 1, }, @@ -40,7 +41,7 @@ schema.index( ) schema.index( - { nom_voie: 1, numero: 1 }, + { nom_commune: 1, nom_voie: 1, numero: 1 }, { collation: { locale: 'fr', diff --git a/src/db/lille-address.model.ts b/src/db/lille-address.model.ts index 034b90c8..6495a938 100644 --- a/src/db/lille-address.model.ts +++ b/src/db/lille-address.model.ts @@ -29,9 +29,10 @@ const schema = new Schema({ }) schema.index( - { nom_voie: 'text', numero: 'text' }, + { nom_commune: 'text', nom_voie: 'text', numero: 'text' }, { weights: { + nom_commune: 10, nom_voie: 10, numero: 1, }, @@ -39,7 +40,7 @@ schema.index( } ) schema.index( - { nom_voie: 1, numero: 1 }, + { nom_commune: 1, nom_voie: 1, numero: 1 }, { collation: { locale: 'fr', diff --git a/src/db/lyon-address.model.ts b/src/db/lyon-address.model.ts index d00b8e7f..9e9b9d33 100644 --- a/src/db/lyon-address.model.ts +++ b/src/db/lyon-address.model.ts @@ -30,9 +30,10 @@ const schema = new Schema({ }) schema.index( - { nom_voie: 'text', numero: 'text' }, + { nom_commune: 'text', nom_voie: 'text', numero: 'text' }, { weights: { + nom_commune: 10, nom_voie: 10, numero: 1, }, @@ -40,7 +41,7 @@ schema.index( } ) schema.index( - { nom_voie: 1, numero: 1 }, + { nom_commune: 1, nom_voie: 1, numero: 1 }, { collation: { locale: 'fr', diff --git a/src/db/montpellier-address.model.ts b/src/db/montpellier-address.model.ts index 0909fd30..68ab66ec 100644 --- a/src/db/montpellier-address.model.ts +++ b/src/db/montpellier-address.model.ts @@ -29,9 +29,10 @@ const schema = new Schema({ }) schema.index( - { nom_voie: 'text', numero: 'text' }, + { nom_commune: 'text', nom_voie: 'text', numero: 'text' }, { weights: { + nom_commune: 10, nom_voie: 10, numero: 1, }, @@ -40,7 +41,7 @@ schema.index( ) schema.index( - { nom_voie: 1, numero: 1 }, + { nom_commune: 1, nom_voie: 1, numero: 1 }, { collation: { locale: 'fr', diff --git a/src/db/plaine_commune-address.model.ts b/src/db/plaine_commune-address.model.ts index 3713d993..d0e8fc6d 100644 --- a/src/db/plaine_commune-address.model.ts +++ b/src/db/plaine_commune-address.model.ts @@ -29,9 +29,10 @@ const schema = new Schema({ }) schema.index( - { nom_voie: 'text', numero: 'text' }, + { nom_commune: 'text', nom_voie: 'text', numero: 'text' }, { weights: { + nom_commune: 10, nom_voie: 10, numero: 1, }, @@ -40,7 +41,7 @@ schema.index( ) schema.index( - { nom_voie: 1, numero: 1 }, + { nom_commune: 1, nom_voie: 1, numero: 1 }, { collation: { locale: 'fr', diff --git a/src/db/rent.service.ts b/src/db/rent.service.ts index d136adc2..6566a485 100644 --- a/src/db/rent.service.ts +++ b/src/db/rent.service.ts @@ -64,7 +64,7 @@ export async function getChloroplethMapData( } } - return await Rent.find(filter, { isLegal: 1, district: 1 }).lean() + return await Rent.find(filter, { isLegal: 1, district: 1 }) } export async function getPriceDiffData( @@ -227,7 +227,7 @@ export async function getLegalPerClassicRenterData( return (await Rent.find(filter, { isLegal: 1, renter: 1, - }).lean()) as unknown as { isLegal: boolean; renter: string }[] + })) as unknown as { isLegal: boolean; renter: string }[] } export async function getLegalPerRenterData( @@ -307,7 +307,7 @@ export async function getLegalPerWebsiteData( return (await Rent.find(filter, { isLegal: 1, website: 1, - }).lean()) as unknown as { + })) as unknown as { isLegal: boolean website: string }[] @@ -427,18 +427,15 @@ export async function getRelevantAdsData( } ).lean()) as unknown as RelevantAdsData[] - return ads.map((ad) => { + return await Promise.all(ads.map(async (ad) => { let blurry = false if (!ad.longitude || !ad.latitude) { const mainCity = cityList[ad.city].mainCity - const polygon = new DistrictsList(mainCity as AvailableMainCities).currentPolygon(ad.district) + const feature = await new DistrictsList(mainCity as AvailableMainCities).currentFeature(ad.district) - const point = randomPositionInPolygon({ - type: 'Feature', - geometry: polygon, - }) + const point = randomPositionInPolygon(feature) ad.longitude = point[0] ad.latitude = point[1] @@ -455,7 +452,7 @@ export async function getRelevantAdsData( blurry, exceeding, } - }) + })) } export async function getRelevantAdsDataTotalCount(filterParam: { diff --git a/src/interfaces/json-item-bordeaux.ts b/src/interfaces/json-item-bordeaux.ts index 473200b8..5fd96451 100644 --- a/src/interfaces/json-item-bordeaux.ts +++ b/src/interfaces/json-item-bordeaux.ts @@ -1,3 +1,5 @@ +import { DISPLAY_ZONE_FIELD } from '@services/districts/districts-list' + export interface BordeauxEncadrementItem { zone: number; maison: boolean; @@ -15,6 +17,7 @@ export interface BordeauxDistrictItem { com_code: number zonage: string commune: string + [DISPLAY_ZONE_FIELD]: string } geometry: { type: 'Polygon' diff --git a/src/services/address/address-factory.ts b/src/services/address/address-factory.ts new file mode 100644 index 00000000..e39c539c --- /dev/null +++ b/src/services/address/address-factory.ts @@ -0,0 +1,16 @@ +import { Ad } from '@interfaces/ad' +import { AddressService, DefaultAddressService } from '@services/address/default-address-service' +import { ParisAddressService } from '@services/address/paris-address-service' + +export class AddressServiceFactory { + getDiggerStrategy(city: string, postalCode: string, ad: Ad): AddressService { + switch (city) { + case 'paris': { + return new ParisAddressService(city, postalCode, ad) + } + default: { + return new DefaultAddressService(city, postalCode, ad) + } + } + } +} \ No newline at end of file diff --git a/src/services/address/address.ts b/src/services/address/default-address-service.ts similarity index 62% rename from src/services/address/address.ts rename to src/services/address/default-address-service.ts index eb00122d..b5a55c4d 100644 --- a/src/services/address/address.ts +++ b/src/services/address/default-address-service.ts @@ -8,11 +8,10 @@ import { PlaineCommuneAddress, } from '@db/db' import { Ad } from '@interfaces/ad' -import { ParisAddressItemDB } from '@interfaces/json-item-paris' -import { AddressItem, Coordinate, DefaultAddressItemDB } from '@interfaces/shared' +import { Coordinate, AddressItem, DefaultAddressItemDB, AddressItemDB } from '@interfaces/shared' import { AvailableCities, cityList } from '@services/address/city' -import * as cleanup from '@services/helpers/cleanup' import { regexString } from '@services/helpers/regex' +import * as cleanup from '@services/helpers/cleanup' export const dbMapping = { paris: ParisAddress, @@ -24,24 +23,25 @@ export const dbMapping = { bordeaux: BordeauxAddress, } -export interface AddressStrategy { - getAddress(): Promise<[string, Coordinate, Coordinate]> -} +export abstract class AddressService { + abstract getAddress(): Promise<[string, Coordinate, Coordinate]> -export class AddressStrategyFactory { - getDiggerStrategy(city: string, postalCode: string, ad: Ad): AddressStrategy { - switch (city) { - case 'paris': { - return new ParisAddressStrategy(city, postalCode, ad) - } - default: { - return new DefaultAddressStrategy(city, postalCode, ad) - } - } + static async getAddresses(city: AvailableCities, query: string): Promise { + const addressDb = dbMapping[cityList[city].mainCity] + return (await addressDb + .find( + { + $text: { $search: query }, + }, + { score: { $meta: 'textScore' } } + ) + .sort({ score: { $meta: 'textScore' } }) + .limit(5) + .lean()) as DefaultAddressItemDB[] } } -export class DefaultAddressStrategy implements AddressStrategy { +export class DefaultAddressService implements AddressService { private city: AvailableCities private postalCode: string private ad: Ad @@ -54,7 +54,7 @@ export class DefaultAddressStrategy implements AddressStrategy { this.ad = ad } - public async getAddress(): Promise<[string, Coordinate, Coordinate]> { + async getAddress(): Promise<[string, Coordinate, Coordinate]> { const tab = [this.ad.address, this.ad.title, this.ad.description].filter(Boolean) for (const text of tab) { @@ -68,11 +68,12 @@ export class DefaultAddressStrategy implements AddressStrategy { return [null, null, null] } - protected async digForAddressInText(city: string, postalCode: string, text: string): Promise { + private async digForAddressInText(city: string, postalCode: string, text: string): Promise { const addressRe = new RegExp(regexString('address')) const addressesFromRegex = text.match(addressRe) as string[] if (addressesFromRegex?.length) { const addressesQueries = this.querifyAddresses(city, addressesFromRegex) + const result: { item: AddressItem score: number @@ -104,14 +105,14 @@ export class DefaultAddressStrategy implements AddressStrategy { } } - protected querifyAddresses(city: string, addressesFromRegex: string[]): string[] { + private querifyAddresses(city: AvailableCities, addressesFromRegex: string[]): string[] { return addressesFromRegex - .map((a) => (cleanup.address(a, city) ? `${cleanup.address(a, city)}` : null)) + .map((a) => (cleanup.address(a, city) ?? null)) .filter(Boolean) } protected async getAddressCompleted( - city: string, + city: AvailableCities, query: string ): Promise< { @@ -124,17 +125,7 @@ export class DefaultAddressStrategy implements AddressStrategy { return null } - const addressDb = dbMapping[cityList[city].mainCity] - const result: DefaultAddressItemDB[] = (await addressDb - .find( - { - $text: { $search: query }, - }, - { score: { $meta: 'textScore' } } - ) - .sort({ score: { $meta: 'textScore' } }) - .limit(10) - .lean()) as DefaultAddressItemDB[] + const result: DefaultAddressItemDB[] = await AddressService.getAddresses(city, query) as DefaultAddressItemDB[] return result ? result.map((r: DefaultAddressItemDB) => ({ @@ -142,8 +133,8 @@ export class DefaultAddressStrategy implements AddressStrategy { address: `${r.numero} ${r.nom_voie}`, postalCode: r.code_postal.toString(), coordinate: { - lng: +r.geometry.coordinates[0], - lat: +r.geometry.coordinates[1], + lng: +r.geometry?.coordinates[0], + lat: +r.geometry?.coordinates[1], }, }, score: r.score, @@ -152,7 +143,7 @@ export class DefaultAddressStrategy implements AddressStrategy { : [] } - protected setCoordinates(coord: Coordinate, streetNumber: string): void { + private setCoordinates(coord: Coordinate, streetNumber: string): void { if (streetNumber) { this.coordinates = { ...coord } } else { @@ -179,52 +170,4 @@ export class DefaultAddressStrategy implements AddressStrategy { } } } -} - -export class ParisAddressStrategy extends DefaultAddressStrategy { - protected async getAddressCompleted( - city: string, - query: string - ): Promise< - { - item: AddressItem - score: number - streetNumber: string - }[] - > { - if (!query) { - return null - } - const addressDb = dbMapping[cityList[city].mainCity] - const result = (await addressDb - .find( - { - $text: { $search: query }, - }, - { score: { $meta: 'textScore' } } - ) - .sort({ score: { $meta: 'textScore' } }) - .limit(10) - .lean()) as ParisAddressItemDB[] - - return result - ? result.map((r: ParisAddressItemDB) => ({ - item: { - address: r.fields.l_adr, - postalCode: ParisAddressStrategy.postalCodeFormat(r.fields.c_ar.toString()), - coordinate: { - lng: r.fields.geom.coordinates[0], - lat: r.fields.geom.coordinates[1], - }, - }, - score: r.score, - streetNumber: cleanup.streetNumber(query)?.toString(), - })) - : [] - } - - static postalCodeFormat(postalCode: string): string { - // 10 -> 75010 9 -> 75009 - return postalCode.length === 1 ? `7500${postalCode}` : `750${postalCode}` - } -} +} \ No newline at end of file diff --git a/src/services/address/paris-address-service.ts b/src/services/address/paris-address-service.ts new file mode 100644 index 00000000..7a77c661 --- /dev/null +++ b/src/services/address/paris-address-service.ts @@ -0,0 +1,44 @@ +import { ParisAddressItemDB } from '@interfaces/json-item-paris' +import { AddressItem } from '@interfaces/shared' +import { cityList } from '@services/address/city' +import { AddressService, DefaultAddressService, dbMapping } from '@services/address/default-address-service' +import * as cleanup from '@services/helpers/cleanup' + +export class ParisAddressService extends DefaultAddressService { + protected async getAddressCompleted( + city: string, + query: string + ): Promise< + { + item: AddressItem + score: number + streetNumber: string + }[] + > { + if (!query) { + return null + } + + const result: ParisAddressItemDB[] = await AddressService.getAddresses(city, query) as ParisAddressItemDB[] + + return result + ? result.map((r: ParisAddressItemDB) => ({ + item: { + address: r.fields.l_adr, + postalCode: ParisAddressService.postalCodeFormat(r.fields.c_ar.toString()), + coordinate: { + lng: r.fields.geom.coordinates[0], + lat: r.fields.geom.coordinates[1], + }, + }, + score: r.score, + streetNumber: cleanup.streetNumber(query)?.toString(), + })) + : [] + } + + static postalCodeFormat(postalCode: string): string { + // 10 -> 75010 9 -> 75009 + return postalCode.length === 1 ? `7500${postalCode}` : `750${postalCode}` + } +} diff --git a/src/services/diggers/dig.ts b/src/services/diggers/dig.ts index 952d7559..b42833da 100644 --- a/src/services/diggers/dig.ts +++ b/src/services/diggers/dig.ts @@ -1,6 +1,6 @@ import { Ad, CleanAd } from '@interfaces/ad' import { Coordinate } from '@interfaces/shared' -import { AddressStrategyFactory } from '@services/address/address' +import { AddressServiceFactory } from '@services/address/address-factory' import { AvailableCities, CityService, cityList } from '@services/address/city' import { PostalCodeStrategyFactory } from '@services/address/postalcode' import { ERROR_CODE } from '@services/api/errors' @@ -58,8 +58,8 @@ export class DigService { // Order is important here const postalCode = postalCodeStrategy.getPostalCode() - const addressStrategy = new AddressStrategyFactory().getDiggerStrategy(city, postalCode, this.ad) - const [address, coordinates, blurryCoordinates] = await addressStrategy.getAddress() + const addressService = new AddressServiceFactory().getDiggerStrategy(city, postalCode, this.ad) + const [address, coordinates, blurryCoordinates] = await addressService.getAddress() const stations = this.ad.stations diff --git a/src/services/districts/addresses.ts b/src/services/districts/addresses.ts index cf1c5d7a..2a553e8c 100644 --- a/src/services/districts/addresses.ts +++ b/src/services/districts/addresses.ts @@ -1,15 +1,15 @@ -import { ParisAddressItemDB, ParisDistrictItem } from '@interfaces/json-item-paris' -import { AddressItemDB, DefaultAddressItemDB, DefaultDistrictItem } from '@interfaces/shared' -import { ParisAddressStrategy, dbMapping } from '@services/address/address' +import { AddressItemDB, DefaultAddressItemDB } from '@interfaces/shared' import { AvailableMainCities } from '@services/address/city' import { DistrictFilterFactory } from '@services/filters/district-filter/district-filter-factory' import { PrettyLog } from '@services/helpers/pretty-log' import { Request, Response } from 'express' import { DistrictsList } from './districts-list' +import { AddressService } from '@services/address/default-address-service' export async function getAddresses(req: Request, res: Response) { PrettyLog.call(`-> ${req.baseUrl} getAddresses`, 'blue') - const city = req.params.city as AvailableMainCities + const mainCity = req.params.city as AvailableMainCities + const city = req.query.city.toString() const addressQuery = req.query.q.toString() if (addressQuery.length < 2) { @@ -17,61 +17,36 @@ export async function getAddresses(req: Request, res: Response) { return } - let data: AddressItemDB[] = await dbMapping[city] - .find( - { - $text: { $search: addressQuery }, - }, - { score: { $meta: 'textScore' } } - ) - .sort({ score: { $meta: 'textScore' } }) - .limit(10) - .lean() - - const CurrentDistrictFilter = new DistrictFilterFactory(city).currentFilter() - - switch (city) { - case 'paris': - data = await Promise.all((data as ParisAddressItemDB[]).map(async (elem) => { - const parisDistrictFilter = new CurrentDistrictFilter( - city, - ParisAddressStrategy.postalCodeFormat(elem.fields.c_ar.toString()), - { - lng: elem.geometry.coordinates[0], - lat: elem.geometry.coordinates[1], - } - ) - - const district = await parisDistrictFilter.getFirstDistrict() as ParisDistrictItem - - return { - ...elem, - districtName: district ? DistrictsList.digZoneInProperties(city, district['properties']) : null, - } - })) - break - default: - data = await Promise.all((data as DefaultAddressItemDB[]).map(async (elem) => { - const currentDistrictFilter = new CurrentDistrictFilter( - elem.nom_commune, - elem.code_postal, { - lng: elem.geometry.coordinates[0], - lat: elem.geometry.coordinates[1], - } - ) - - const district = await currentDistrictFilter.getFirstDistrict() as DefaultDistrictItem - - return { - ...elem, - fields: { - l_adr: `${elem.numero}${elem.rep || ''} ${elem.nom_voie} (${elem.code_postal})`, - }, - districtName: district ? DistrictsList.digZoneInProperties(city, district['properties']) : null, - } - })) - break - } + let data: AddressItemDB[] = await AddressService.getAddresses(city, addressQuery) as AddressItemDB[] + + const CurrentDistrictFilter = new DistrictFilterFactory(mainCity).currentDistrictFilter() + + // Add [districtName] to all elements + data = await Promise.all(data.map(async (elem: AddressItemDB) => { + const currentDistrictFilter = new CurrentDistrictFilter({ + lng: elem.geometry.coordinates[0], + lat: elem.geometry.coordinates[1], + }) + + const district = await currentDistrictFilter.getFirstDistrict() + + const itemAugmented = { + ...elem, + districtName: district ? DistrictsList.digZoneInProperties(mainCity, district['properties']) : null, + } + + if (mainCity === 'paris') { + return itemAugmented + } else { + return { + ...itemAugmented, + fields: { + l_adr: `${(elem as DefaultAddressItemDB).numero}${(elem as DefaultAddressItemDB).rep || ''} `+ + `${(elem as DefaultAddressItemDB).nom_voie} (${(elem as DefaultAddressItemDB).code_postal})`, + }, + } + } + })) as AddressItemDB[] res.json(data) } diff --git a/src/services/districts/districts-list.ts b/src/services/districts/districts-list.ts index 89faf44b..8bfa8b91 100644 --- a/src/services/districts/districts-list.ts +++ b/src/services/districts/districts-list.ts @@ -1,13 +1,13 @@ -import { GeojsonFile } from '@interfaces/shared' -import { AvailableMainCities } from '@services/address/city' +import { DistrictItem, GeojsonFile } from '@interfaces/shared' +import { AvailableCities, AvailableMainCities } from '@services/address/city' +import { DistrictFilterFactory } from '@services/filters/district-filter/district-filter-factory' import * as fs from 'fs' import path from 'path' -import { Memoize } from 'typescript-memoize' interface DistrictElem { value: string groupBy: string | null - displaySequence: number + displaySequence: number | string } export const DISPLAY_ZONE_FIELD = 'displayZone' @@ -24,70 +24,77 @@ export const CITY_FILE_PATHS = { } export class DistrictsList { - city: AvailableMainCities + CurrentDistrictFilter = null + mainCity: AvailableMainCities + city: AvailableCities - constructor(city: AvailableMainCities) { - this.city = city + constructor(city: AvailableMainCities, options?: { specificCity?: AvailableCities }) { + this.mainCity = city + this.city = options?.specificCity + this.CurrentDistrictFilter = new DistrictFilterFactory(this.mainCity).currentDistrictFilter() } - @Memoize() - currentGeodata(): GeojsonFile { - const file = this.geodataFile() + async currentGeodata(): Promise { + const features: DistrictItem[] = await new this.CurrentDistrictFilter( + { lat: null, lng: null }, + { city: this.city }, + ).getDistricts() + return { - ...file, - features: file.features.map((data) => ({ + type: 'FeatureCollection', + features: features.map((data) => ({ ...data, properties: { ...data.properties, - [DISPLAY_ZONE_FIELD]: DistrictsList.digZoneInProperties(this.city, data['properties']), + [DISPLAY_ZONE_FIELD]: DistrictsList.digZoneInProperties(this.mainCity, data['properties']) as string, }, - })), + })) as DistrictItem[], } } - currentPolygon(displayZoneField: string) { - return this.currentGeodata().features.find((feature) => { - return feature.properties[DISPLAY_ZONE_FIELD] === displayZoneField - })?.geometry + async currentFeature(displayZoneField: string): Promise { + return await new this.CurrentDistrictFilter( + { lat: null, lng: null }, + { city: this.city, districtName: displayZoneField }, + ).getFirstDistrict() } - currentGeodataWithGroupBy() { - return this.currentGeodata() - .features.reduce((prev: DistrictElem[], data) => { - switch (this.city) { - case 'paris': { - if (!prev.some((elem: DistrictElem) => elem.value === data['properties']['l_qu'])) { - prev.push({ - value: data['properties']['l_qu'], - displaySequence: data['properties']['c_ar'], - groupBy: `${data['properties']['c_ar']}${(data['properties']['c_ar'] > 1 - ? 'ème' - : 'er' - ).toString()} arrondissement`, - }) - } - break + async currentGeodataWithGroupBy(): Promise { + return ((await this.currentGeodata()).features as DistrictItem[]).reduce((prev: DistrictElem[], data) => { + switch (this.city) { + case 'paris': { + if (!prev.some((elem: DistrictElem) => elem.value === data['properties']['l_qu'])) { + prev.push({ + value: data['properties'][DISPLAY_ZONE_FIELD], + displaySequence: data['properties']['c_ar'], + groupBy: `${data['properties']['c_ar']}${(data['properties']['c_ar'] > 1 + ? 'ème' + : 'er' + ).toString()} arrondissement`, + }) } - default: { - if (!prev.some((elem: DistrictElem) => elem.value === DistrictsList.digZoneInProperties(this.city, data['properties']))) { - prev.push({ - value: DistrictsList.digZoneInProperties(this.city, data['properties']), - displaySequence: data['properties'][DISPLAY_ZONE_FIELD], - groupBy: null, - }) - } - break + break + } + default: { + if (!prev.some((elem: DistrictElem) => elem.value === data['properties'][DISPLAY_ZONE_FIELD])) { + prev.push({ + value: data['properties'][DISPLAY_ZONE_FIELD], + displaySequence: data['properties'][DISPLAY_ZONE_FIELD], + groupBy: null, + }) } + break } + } - return prev - }, []) + return prev + }, []) .sort((a: DistrictElem, b: DistrictElem) => { return a.displaySequence > b.displaySequence ? 1 : -1 }) } - static digZoneInProperties(city: AvailableMainCities, data: unknown) { + static digZoneInProperties(city: AvailableMainCities, data: unknown): string { switch (city) { case 'paris': return data['l_qu'] @@ -102,6 +109,6 @@ export class DistrictsList { } private geodataFile() { - return JSON.parse(fs.readFileSync(path.join(CITY_FILE_PATHS[this.city]), 'utf8')) + return JSON.parse(fs.readFileSync(path.join(CITY_FILE_PATHS[this.mainCity]), 'utf8')) } } diff --git a/src/services/districts/districts.ts b/src/services/districts/districts.ts index 32442d34..409e458f 100644 --- a/src/services/districts/districts.ts +++ b/src/services/districts/districts.ts @@ -1,13 +1,14 @@ -import { AvailableMainCities } from '@services/address/city' +import { AvailableCities, AvailableMainCities } from '@services/address/city' import { DistrictsList } from '@services/districts/districts-list' import { PrettyLog } from '@services/helpers/pretty-log' import { Request, Response } from 'express' -export function getDistricts(req: Request, res: Response) { +export async function getDistricts(req: Request, res: Response) { PrettyLog.call(`-> ${req.baseUrl} getDistricts`, 'blue') - const city = req.params.city + const mainCity: AvailableMainCities = req.params.city as AvailableMainCities + const city: AvailableCities = req.query.city as AvailableCities - const geodata = new DistrictsList(city as AvailableMainCities).currentGeodataWithGroupBy() + const geodata = await new DistrictsList(mainCity as AvailableMainCities, { specificCity: city }).currentGeodataWithGroupBy() res.json(geodata) } diff --git a/src/services/filters/district-filter/bordeaux-district.ts b/src/services/filters/district-filter/bordeaux-district.ts index 8d7501c7..07e309d4 100644 --- a/src/services/filters/district-filter/bordeaux-district.ts +++ b/src/services/filters/district-filter/bordeaux-district.ts @@ -11,16 +11,18 @@ export class BordeauxDistrictFilter extends DistrictFilterParent { } protected async getDistrictsFromPostalCode(): Promise { - if (this.postalCode) { - const districts = await this.GeojsonCollection.find( - { - 'properties.com_code': +this.postalCode - }, - ) - return districts?.length ? districts : [] - } else { - // There is not other city in the Bordeaux Agglomeration - return await this.GeojsonCollection.find({}) - } + if (!this.postalCode) { return [] } + + const districts = await this.GeojsonCollection.find( + { + 'properties.com_code': +this.postalCode + }, + ) + return districts?.length ? districts : [] + } + + protected async getDistrictsFromCity(): Promise { + // There is not other city in the Bordeaux Agglomeration + return await this.GeojsonCollection.find({}) } } diff --git a/src/services/filters/district-filter/district-filter-factory.ts b/src/services/filters/district-filter/district-filter-factory.ts index f04168d8..fdf47cd2 100644 --- a/src/services/filters/district-filter/district-filter-factory.ts +++ b/src/services/filters/district-filter/district-filter-factory.ts @@ -14,7 +14,7 @@ export class DistrictFilterFactory { this.mainCity = mainCity } - currentFilter() { + currentDistrictFilter() { switch (this.mainCity) { case 'paris': return ParisDistrictFilter diff --git a/src/services/filters/district-filter/district-filter-parent.ts b/src/services/filters/district-filter/district-filter-parent.ts index f6faf9c9..95b177eb 100644 --- a/src/services/filters/district-filter/district-filter-parent.ts +++ b/src/services/filters/district-filter/district-filter-parent.ts @@ -10,11 +10,11 @@ export class DistrictFilterParent { postalCode: string = null districtName: string = null - constructor(city: AvailableCities, postalCode: string, coordinates?: Coordinate, districtName?: string) { - this.city = city + constructor(coordinates: Coordinate, options?: { city?: AvailableCities, postalCode?: string, districtName?: string }) { this.coordinates = coordinates - this.postalCode = postalCode - this.districtName = districtName + this.city = options?.city + this.postalCode = options?.postalCode + this.districtName = options?.districtName } async getFirstDistrict(): Promise { @@ -33,7 +33,21 @@ export class DistrictFilterParent { return districtFromCoordinate } - return this.getDistrictsFromPostalCode() + const districtFromPostalCode = + await this.getDistrictsFromPostalCode() + + if (districtFromPostalCode.length) { + return districtFromPostalCode + } + + const districtFromSpecificCity = + await this.getDistrictsFromCity() + + if (districtFromSpecificCity.length) { + return districtFromSpecificCity + } + + return await this.getDistrictsFromMainCity() } protected async getDistrictFromName(): Promise { @@ -51,6 +65,14 @@ export class DistrictFilterParent { return [] } + protected async getDistrictsFromCity(): Promise { + return [] + } + + protected async getDistrictsFromMainCity(): Promise { + return await this.GeojsonCollection.find({}) + } + private async getDistrictFromCoordinate(lat: number, lng: number): Promise { if (!this.coordinates?.lat || !this.coordinates?.lng) return [] diff --git a/src/services/filters/district-filter/lille-district.ts b/src/services/filters/district-filter/lille-district.ts index d170a09b..abf925c4 100644 --- a/src/services/filters/district-filter/lille-district.ts +++ b/src/services/filters/district-filter/lille-district.ts @@ -10,4 +10,9 @@ export class LilleDistrictFilter extends DistrictFilterParent { async getDistricts(): Promise { return super.getDistricts() as Promise } + + protected async getDistrictsFromCity(): Promise { + // There is not other city in the Lille Agglomeration + return await this.GeojsonCollection.find({}) + } } diff --git a/src/services/filters/district-filter/lyon-district.ts b/src/services/filters/district-filter/lyon-district.ts index 015a07e2..ce23977e 100644 --- a/src/services/filters/district-filter/lyon-district.ts +++ b/src/services/filters/district-filter/lyon-district.ts @@ -12,12 +12,12 @@ export class LyonDistrictFilter extends DistrictFilterParent { return super.getDistricts() as Promise } - protected async getDistrictsFromPostalCode(): Promise { - if (!this.postalCode || !this.city) return [] + protected async getDistrictsFromCity(): Promise { + if (!this.city) return [] const districts = await this.GeojsonCollection.find( { - 'properties.city': new RegExp(`/^${this.city}$/`, 'i') + 'properties.city': { $regex: this.city, $options: 'i' } }, ) return districts?.length ? districts : [] diff --git a/src/services/filters/district-filter/montpellier-district.ts b/src/services/filters/district-filter/montpellier-district.ts index 8c8d89a1..a7a1628e 100644 --- a/src/services/filters/district-filter/montpellier-district.ts +++ b/src/services/filters/district-filter/montpellier-district.ts @@ -11,7 +11,7 @@ export class MontpellierDistrictFilter extends DistrictFilterParent { return super.getDistricts() as Promise } - protected async getDistrictsFromPostalCode(): Promise { + protected async getDistrictsFromCity(): Promise { // There is not other city in the Montpellier Agglomeration return await this.GeojsonCollection.find({}) } diff --git a/src/services/filters/district-filter/plaine-commune-district.ts b/src/services/filters/district-filter/plaine-commune-district.ts index 65ec2e1a..183d90ee 100644 --- a/src/services/filters/district-filter/plaine-commune-district.ts +++ b/src/services/filters/district-filter/plaine-commune-district.ts @@ -22,4 +22,15 @@ export class PlaineCommuneDistrictFilter extends DistrictFilterParent { ) return districts?.length ? districts : [] } + + protected async getDistrictsFromCity(): Promise { + if (!this.city) return [] + + const districts = await this.GeojsonCollection.find( + { + 'properties.NOM_COM': { $regex: this.city, $options: 'i' } + }, + ) + return districts?.length ? districts : [] + } } diff --git a/src/services/filters/encadrement-filter/encadrement-filter-factory.ts b/src/services/filters/encadrement-filter/encadrement-filter-factory.ts index 5acb29af..b55542d9 100644 --- a/src/services/filters/encadrement-filter/encadrement-filter-factory.ts +++ b/src/services/filters/encadrement-filter/encadrement-filter-factory.ts @@ -14,7 +14,7 @@ export class EncadrementFilterFactory { this.city = city } - currentFilter() { + currentEncadrementFilter() { switch (this.city) { case 'paris': return FilterParis diff --git a/src/services/filters/encadrement-filter/filter-bordeaux.ts b/src/services/filters/encadrement-filter/filter-bordeaux.ts index 9bbc5b28..7016209f 100644 --- a/src/services/filters/encadrement-filter/filter-bordeaux.ts +++ b/src/services/filters/encadrement-filter/filter-bordeaux.ts @@ -11,10 +11,11 @@ export class FilterBordeaux extends EncadrementFilterParent { async filter(): Promise { const districtsMatched = await new BordeauxDistrictFilter( - this.infoToFilter.city, - this.infoToFilter.postalCode, - this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, - this.infoToFilter.districtName + this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, { + city: this.infoToFilter.city, + postalCode: this.infoToFilter.postalCode, + districtName: this.infoToFilter.districtName, + } ).getDistricts() const timeDates: string[] = new YearBuiltService(this.rangeTime, this.universalRangeTime).getRangeTimeFromYearBuilt(this.infoToFilter.yearBuilt) diff --git a/src/services/filters/encadrement-filter/filter-est-ensemble.ts b/src/services/filters/encadrement-filter/filter-est-ensemble.ts index 8bb40f34..3c62599b 100644 --- a/src/services/filters/encadrement-filter/filter-est-ensemble.ts +++ b/src/services/filters/encadrement-filter/filter-est-ensemble.ts @@ -12,10 +12,11 @@ export class FilterEstEnsemble extends EncadrementFilterParent { async filter(): Promise { const districtsMatched = await new EstEnsembleDistrictFilter( - this.infoToFilter.city, - this.infoToFilter.postalCode, - this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, - this.infoToFilter.districtName + this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, { + city: this.infoToFilter.city, + postalCode: this.infoToFilter.postalCode, + districtName: this.infoToFilter.districtName, + } ).getDistricts() const timeDates: string[] = new YearBuiltService(this.rangeTime, this.universalRangeTime).getRangeTimeFromYearBuilt(this.infoToFilter.yearBuilt) diff --git a/src/services/filters/encadrement-filter/filter-lille.ts b/src/services/filters/encadrement-filter/filter-lille.ts index ec8a2503..0de37492 100644 --- a/src/services/filters/encadrement-filter/filter-lille.ts +++ b/src/services/filters/encadrement-filter/filter-lille.ts @@ -12,10 +12,11 @@ export class FilterLille extends EncadrementFilterParent { async filter(): Promise { const districtsMatched = await new LilleDistrictFilter( - this.infoToFilter.city, - this.infoToFilter.postalCode, - this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, - this.infoToFilter.districtName + this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, { + city: this.infoToFilter.city, + postalCode: this.infoToFilter.postalCode, + districtName: this.infoToFilter.districtName, + } ).getDistricts() const timeDates: string[] = new YearBuiltService(this.rangeTime, this.universalRangeTime).getRangeTimeFromYearBuilt(this.infoToFilter.yearBuilt) diff --git a/src/services/filters/encadrement-filter/filter-lyon.ts b/src/services/filters/encadrement-filter/filter-lyon.ts index 02b0e3f8..5a0d8441 100644 --- a/src/services/filters/encadrement-filter/filter-lyon.ts +++ b/src/services/filters/encadrement-filter/filter-lyon.ts @@ -13,10 +13,11 @@ export class FilterLyon extends EncadrementFilterParent { async filter(): Promise { const districtsMatched = await new LyonDistrictFilter( - this.infoToFilter.city, - this.infoToFilter.postalCode, - this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, - this.infoToFilter.districtName + this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, { + city: this.infoToFilter.city, + postalCode: this.infoToFilter.postalCode, + districtName: this.infoToFilter.districtName, + } ).getDistricts() const timeDates: string[] = new YearBuiltService(this.rangeTime, this.universalRangeTime).getRangeTimeFromYearBuilt( diff --git a/src/services/filters/encadrement-filter/filter-montpellier.ts b/src/services/filters/encadrement-filter/filter-montpellier.ts index 3d13ec2d..ed447d6b 100644 --- a/src/services/filters/encadrement-filter/filter-montpellier.ts +++ b/src/services/filters/encadrement-filter/filter-montpellier.ts @@ -13,10 +13,11 @@ export class FilterMontpellier extends EncadrementFilterParent { async filter(): Promise { const districtsMatched = await new MontpellierDistrictFilter( - this.infoToFilter.city, - this.infoToFilter.postalCode, - this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, - this.infoToFilter.districtName + this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, { + city: this.infoToFilter.city, + postalCode: this.infoToFilter.postalCode, + districtName: this.infoToFilter.districtName, + } ).getDistricts() const timeDates: string[] = new YearBuiltService(this.rangeTime, this.universalRangeTime).getRangeTimeFromYearBuilt(this.infoToFilter.yearBuilt) diff --git a/src/services/filters/encadrement-filter/filter-paris.ts b/src/services/filters/encadrement-filter/filter-paris.ts index 38e708b8..de1237de 100644 --- a/src/services/filters/encadrement-filter/filter-paris.ts +++ b/src/services/filters/encadrement-filter/filter-paris.ts @@ -16,10 +16,11 @@ export class FilterParis extends EncadrementFilterParent { async filter(): Promise { const districtsMatched = await new ParisDistrictFilter( - this.infoToFilter.city, - this.infoToFilter.postalCode, - this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, - this.infoToFilter.districtName + this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, { + city: this.infoToFilter.city, + postalCode: this.infoToFilter.postalCode, + districtName: this.infoToFilter.districtName, + } ).getDistricts() const timeDates: string[] = new YearBuiltService(this.rangeTime, this.universalRangeTime).getRangeTimeFromYearBuilt(this.infoToFilter.yearBuilt) diff --git a/src/services/filters/encadrement-filter/filter-plaine-commune.ts b/src/services/filters/encadrement-filter/filter-plaine-commune.ts index e608c25f..b2b750a0 100644 --- a/src/services/filters/encadrement-filter/filter-plaine-commune.ts +++ b/src/services/filters/encadrement-filter/filter-plaine-commune.ts @@ -12,10 +12,11 @@ export class FilterPlaineCommune extends EncadrementFilterParent { async filter(): Promise { const districtsMatched = await new PlaineCommuneDistrictFilter( - this.infoToFilter.city, - this.infoToFilter.postalCode, - this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, - this.infoToFilter.districtName + this.infoToFilter.coordinates || this.infoToFilter.blurryCoordinates, { + city: this.infoToFilter.city, + postalCode: this.infoToFilter.postalCode, + districtName: this.infoToFilter.districtName, + } ).getDistricts() const timeDates: string[] = new YearBuiltService(this.rangeTime, this.universalRangeTime).getRangeTimeFromYearBuilt(this.infoToFilter.yearBuilt) diff --git a/src/services/helpers/cleanup.ts b/src/services/helpers/cleanup.ts index 48406b5d..773a33a2 100644 --- a/src/services/helpers/cleanup.ts +++ b/src/services/helpers/cleanup.ts @@ -28,6 +28,6 @@ export function address(string: string, city: AvailableCities): string { if (blackList.some((w) => string.includes(w))) return null return string .replace('bd ', 'boulevard ') - .replace(city as string, '') + .replace(city as string, '') // remove the city from the postal address .trim() } diff --git a/src/services/simulator/manual-result.ts b/src/services/simulator/manual-result.ts index a17a6764..28c928ce 100644 --- a/src/services/simulator/manual-result.ts +++ b/src/services/simulator/manual-result.ts @@ -33,15 +33,15 @@ export async function getManualResult(req: Request, res: Response) { const isHouse: boolean = +isHouseValue === 1 - const CurrentEncadrementFilter = new EncadrementFilterFactory(city).currentFilter() + const CurrentEncadrementFilter = new EncadrementFilterFactory(city).currentEncadrementFilter() const params: InfoToFilter = { + city: null, postalCode: null, coordinates: null, blurryCoordinates: null, yearBuilt: dateBuiltStr[0] === -1 ? null : dateBuiltStr, districtName: district, roomCount: room, - city: null, hasFurniture, } diff --git a/src/services/stats/chloropleth-map.ts b/src/services/stats/chloropleth-map.ts index 7ad97bb1..ba21af76 100644 --- a/src/services/stats/chloropleth-map.ts +++ b/src/services/stats/chloropleth-map.ts @@ -7,12 +7,12 @@ import { Vega } from '@services/helpers/vega' import { Request, Response } from 'express' import rewind from '@mapbox/geojson-rewind' -export function getChloroplethMap(req: Request, res: Response) { +export async function getChloroplethMap(req: Request, res: Response) { PrettyLog.call(`-> ${req.baseUrl} getChloroplethMap`, 'blue') const city: AvailableMainCities = req.params.city as AvailableMainCities const dateValue: string = req.query.dateValue as string const dateRange: string[] = dateValue?.split(',') - const geodata = new DistrictsList(city as AvailableMainCities).currentGeodata() + const geodata = await new DistrictsList(city as AvailableMainCities).currentGeodata() rentService .getChloroplethMapData(city, dateRange) diff --git a/src/services/stats/map.ts b/src/services/stats/map.ts index 0279beef..e43dbbe7 100644 --- a/src/services/stats/map.ts +++ b/src/services/stats/map.ts @@ -7,12 +7,12 @@ import { Vega } from '@services/helpers/vega' import { Request, Response } from 'express' import rewind from '@mapbox/geojson-rewind' -export function getMap(req: Request, res: Response) { +export async function getMap(req: Request, res: Response) { PrettyLog.call(`-> ${req.baseUrl} getMap`, 'blue') const city: AvailableMainCities = req.params.city as AvailableMainCities const dateValue: string = req.query.dateValue as string const dateRange: string[] = dateValue?.split(',') - const geodata = new DistrictsList(city as AvailableMainCities).currentGeodata() + const geodata = await new DistrictsList(city as AvailableMainCities).currentGeodata() rentService .getMapData(city, dateRange) diff --git a/src/services/websites/website.ts b/src/services/websites/website.ts index 2af5e930..00971385 100644 --- a/src/services/websites/website.ts +++ b/src/services/websites/website.ts @@ -67,7 +67,7 @@ export abstract class Website { try { const cleanAd: CleanAd = await new DigService(ad).digInAd(city) - const CurrentEncadrementFilter = new EncadrementFilterFactory(cityList[city].mainCity).currentFilter() + const CurrentEncadrementFilter = new EncadrementFilterFactory(cityList[city].mainCity).currentEncadrementFilter() const filteredResult: FilteredResult = await new CurrentEncadrementFilter(cleanAd).find() if (filteredResult) {