UNPKG

geocoder-dadata

Version:

Dadata geocoder API client that supports caching and is written purely on typescript

279 lines (211 loc) 9.31 kB
import fetch, { Response } from 'node-fetch' import { Pacekeeper } from 'pace-keeper' const ENV_API_URL : string = 'DADATA_API_URL' const ENV_API_TOKEN : string = 'DADATA_API_TOKEN' const ENV_API_SECRET : string = 'DADATA_API_SECRET' const PACE_INTERVAL : number = 1000 const PACE_LIMIT : number = 5 const DEFAULT_API_URL : string = "https://cleaner.dadata.ru/api/v1/clean/address" const CACHE : {[key: string]: GeoResult} = {} export interface IGeocoderOptions { api_token? : string api_secret? : string api_url? : string pace_limit? : number cached? : boolean } export interface IGeoPoint { lat : number | void lng : number | void } export interface IGeoResponseForwardCoding { "source" : string, "result" : string, "postal_code" : string | null, "country" : "Россия", "country_iso_code" : "RU", "federal_district" : "Центральный", "region_fias_id" : "0c5b2444-70a0-4932-980c-b4dc0d3f02b5", "region_kladr_id" : "7700000000000", "region_iso_code" : string | null, "region_with_type" : string | null, "region_type" : string | null, "region_type_full" : string | null, "region" : string | null, "area_fias_id" : string | null, "area_kladr_id" : string | null, "area_with_type" : string | null, "area_type" : string | null, "area_type_full" : string | null, "area" : string | null, "city_fias_id" : string | null, "city_kladr_id" : string | null, "city_with_type" : string | null, "city_type" : string | null, "city_type_full" : string | null, "city" : string | null, "city_area" : string | null, "city_district_fias_id" : string | null, "city_district_kladr_id" : string | null, "city_district_with_type" : string | null, "city_district_type" : string | null, "city_district_type_full" : string | null, "city_district" : string | null, "settlement_fias_id" : string | null, "settlement_kladr_id" : string | null, "settlement_with_type" : string | null, "settlement_type" : string | null, "settlement_type_full" : string | null, "settlement" : string | null, "street_fias_id" : string | null, "street_kladr_id" : string | null, "street_with_type" : string | null, "street_type" : string | null, "street_type_full" : string | null, "street" : string | null, "house_fias_id" : string | null, "house_kladr_id" : string | null, "house_type" : string | null, "house_type_full" : string | null, "house" : string | null, "block_type" : string | null, "block_type_full" : string | null, "block" : string | null, "entrance" : string | null, "floor" : string | null, "flat_fias_id" : string | null, "flat_type" : string | null, "flat_type_full" : string | null, "flat" : number | null, "flat_area" : string | number | null, "square_meter_price" : string | number | null, "flat_price" : string | number | null, "postal_box" : string | null, "fias_id" : string | null, "fias_code" : string | null, "fias_level" : string | number | null, "fias_actuality_state" : string | number | null, "kladr_id" : string | number | null, "capital_marker" : string | number | null, "okato" : string | number | null, "oktmo" : string | number | null, "tax_office" : string | number | null, "tax_office_legal" : string | number | null, "timezone" : string | null, "geo_lat" : number | null, "geo_lon" : number | null, "beltway_hit" : string | null, "beltway_distance" : string | null, "qc_geo" : number, "qc_complete" : number, "qc_house" : number, "qc" : number, "unparsed_parts" : any, "metro" : [ { "name" : string, "line" : string, "distance" : number, }, { "name" : string, "line" : string, "distance" : number, }, { "name" : string, "line" : string, "distance" : number, } ] } export interface IGeoResult { ok : boolean geo : IGeoPoint } export class Geocoder { private API_TOKEN : string | undefined private API_SECRET : string | undefined private API_URL : string private pace : Pacekeeper private cached : boolean constructor({api_token, api_secret, api_url, pace_limit, cached}: IGeocoderOptions = { cached: true }) { Object.defineProperty(this, 'API_TOKEN', { enumerable: false, writable: false, value: api_token || process.env[ENV_API_TOKEN] }) Object.defineProperty(this, 'API_SECRET', { enumerable: false, writable: false, value: api_secret || process.env[ENV_API_SECRET] }) this.API_URL = api_url || process.env[ENV_API_URL] || DEFAULT_API_URL this.pace = new Pacekeeper({ interval: PACE_INTERVAL, pace: pace_limit || PACE_LIMIT, parse_429: true }) this.cached = cached === undefined ? true : !!cached } geocode(query : string | string[]): Promise<GeoResult> { if (!query) return Promise.resolve(build_error({ status: 0, statusText: 'empty query' })) if (typeof query === 'string') query = [ query ] if (!Array.isArray(query)) return Promise.resolve(build_error({ status: 0, statusText: 'bad query' })) query = query.filter(i => i && typeof i === 'string') if (query.length < 1) return Promise.resolve(build_error({ status: 0, statusText: 'empty query' })) let key = Array.isArray(query) ? query.sort().join(';') : query let body = JSON.stringify(query) let headers = { "Content-Type" : 'application/json', "Authorization" : `Token ${this.API_TOKEN}`, "X-Secret" : `${this.API_SECRET}` } if (this.cached) { let cached : GeoResult | undefined = CACHE[key.toLowerCase()] if (cached) return Promise.resolve(cached) } return this.pace .submit(() => fetch(this.API_URL, { headers, body, method:'POST' })).promise .then(res => typeof res?.json === 'function' ? res.json().then(build_result(res, this.cached, key)) : build_error(res)) } } export default Geocoder function build_result(res: Response, cache: boolean = false, key: string = ''): (body: any) => GeoResult { return function(body: any): GeoResult { let result = new GeoResult({ status: { code : res.status, message : res.statusText }, results: body }) if (cache && key && typeof key === 'string') { CACHE[key] = result } return result } } function build_error(res: any): GeoResult { return new GeoResult({ status : { code : res?.status, message : res?.statusText } }) } class GeoResult implements IGeoResult { public status : undefined | { code: number, message: string } public results: undefined | IGeoResponseForwardCoding[] constructor(data: any) { Object.assign(this, data) } get total_results() : number { return Array.isArray(this.results) ? this.results.length : 0 } get ok() : boolean { return this.status?.code === 200 && (this.total_results ? this.total_results > 0 : false) } get geo() : IGeoPoint { return this.ok && Array.isArray(this.results) && this.results.length > 0 ? { lat: this.results[0].geo_lat || void 0, lng: this.results[0].geo_lon || void 0 } : { lat: void 0, lng: void 0 } } get address() : string { return this.ok && Array.isArray(this.results) && this.results.length > 0 ? this.results[0].result : '' } }