-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathindex.js
173 lines (141 loc) · 4.82 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
const Query = require('./model/Query')
const axios = require('axios')
const Cache = require('lru')
const Queue = require('promise-queue')
const defaultConcurrency = 1
const defaultMaxQueueLength = Infinity
const cacheSize = 100000
// Context is shared across different modules
const SingletonContext = {
queue: new Queue(defaultConcurrency, defaultMaxQueueLength),
cache: new Cache(cacheSize),
}
class Nominatim {
constructor(options, queryOptions) {
const defaultOptions = {
secure: false, // enables ssl
host: 'nominatim.openstreetmap.org',
customUrl: undefined, // if you want to host your own nominatim
cache: true,
delay: 1000, // Delay between requests
}
const queryDefaults = {
format: 'json',
limit: 3,
}
this.options = Object.assign({}, defaultOptions, options)
this.queryDefaults = Object.assign({}, queryDefaults, queryOptions)
}
static setupCache(size) {
// Just to be sure the memory will be freed
if (SingletonContext.cache) {
SingletonContext.cache.clear()
}
SingletonContext.cache = new Cache(size)
}
static setupQueue(concurrency, maxQueueLength) {
SingletonContext.queue = new Queue(
concurrency || defaultConcurrency,
maxQueueLength || defaultMaxQueueLength)
}
protocol(options) {
return options.secure ? 'https' : 'http'
}
buildUrl(options, slug) {
if (options.customUrl) {
return options.customUrl + slug
}
return this.protocol(options) + '://' + options.host + slug
}
search(query, options) {
// Merge options
const opt = Object.assign({}, this.options, options)
const url = this.buildUrl(opt, '/search')
return this.query(url, query)
}
reverse(query, options) {
// Merge options
const opt = Object.assign({}, this.options, options)
const url = this.buildUrl(opt, '/reverse')
return this.query(url, query)
}
query(url, query) {
const queryObject = Object.assign(new Query(), this.queryDefaults, query)
const cacheHitInformation = { hit: false }
return SingletonContext.queue.add(() => {
const promise = new Promise((resolve, reject) => {
let cachedResponse
if (cachedResponse = SingletonContext.cache.get(queryObject.hash())) {
cacheHitInformation.hit = true
resolve(cachedResponse)
return
}
axios.get(url, { params: queryObject.plainObject() })
.then((response) => {
resolve(response.data)
})
.catch((error) => {
SingletonContext.cache.remove(queryObject.hash())
reject(error)
})
})
const delayedPromise = this.delayPromise(promise, cacheHitInformation)
// Store promise in cache, resolved promises are self-unwrapping
SingletonContext.cache.set(queryObject.hash(), delayedPromise)
return delayedPromise
})
}
// This method wraps a promise, it waits after a promise is resolved/rejected
// and resolves the outer promise when the remaing time after delayBetweenRequests
// has passed. Quite a hack...
// ToDo: Replace
// Note: Rate limiting is subject to the jasmine tests
delayPromise(promise, cacheHitInformation) {
const start = new Date()
const handleEndedPromise = (action, responseOrError) => {
const end = new Date()
const timeToWait = this.options.delay - (end - start)
const isFromCache = cacheHitInformation.hit
if (timeToWait > 0 && !isFromCache) {
setTimeout(() => action(responseOrError), timeToWait)
} else {
action(responseOrError) // immediate resolve
}
}
return new Promise((resolve, reject) => {
promise.then((response, query, test) => {
handleEndedPromise(resolve, response)
}).catch((error, query) => {
handleEndedPromise(reject, error)
})
})
}
}
// Wrapper for those who prefer callbacks
class NominatimCallbackWrapper extends Nominatim {
search(query, options, callback) {
const promise = super.search(query, options)
return this.handlePromise(promise, callback)
}
reverse(query, options, callback) {
const promise = super.reverse(query, options)
return this.handlePromise(promise, callback)
}
handlePromise(promise, callback) {
if (typeof callback === 'function') {
return this.wrapPromiseWithCallback(promise, callback)
}
return promise
}
wrapPromiseWithCallback(promise, callback) {
return promise
.then((data, query) => callback(undefined, data))
.catch((error, query) => callback(error, undefined))
}
}
Nominatim.Nominatim = Nominatim // hack to have a default and named exports
Nominatim.NominatimCallback = NominatimCallbackWrapper
Nominatim.NominatimSingletonContext = SingletonContext
Nominatim.NominatimCache = Cache
Nominatim.NominatimQueue = Queue
module.exports = Nominatim