├── .gitignore
├── README.md
├── example
└── index.html
├── init.js
├── package-lock.json
├── package.json
├── streamserver
├── pipelines
│ ├── custom.js
│ └── default.js
├── streamserver_express.js
├── streamserver_simple.js
└── utils
│ ├── esri_types.js
│ └── filter_utils.js
├── templates
└── service.json
└── utils
├── test2.json
├── test3.json
├── test_json_ws.js
└── websocket_utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArcGIS WebSocket Server
2 |
3 |
4 |
5 |
6 |
7 | - [Start the app](#start-the-app)
8 | - [Using HTTPS (NGROK)](#using-https-ngrok)
9 | - [Known issues](#known-issues)
10 | - [ArcGIS API for JavaScript version <= v4.8 & v3.x](#arcgis-api-for-javascript-version--v48--v3x)
11 | - [Additional documentation](#additional-documentation)
12 | - [Talk: Geolocating tweets in real time (in Spanish)](#talk-geolocating-tweets-in-real-time-in-spanish)
13 |
14 |
15 |
16 | This node server behaves as a [GeoEvent](https://www.esri.com/en-us/arcgis/products/arcgis-geoevent-server) [StreamServer](https://developers.arcgis.com/rest/services-reference/stream-service.htm) layer, so it will emit geographic features in the [Esri JSON](https://developers.arcgis.com/documentation/common-data-types/feature-object.htm) format though a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API). This way we will be able to display a real time layer in ArcGIS without an [ArcGIS Enterprise](https://www.esri.com/en-us/arcgis/products/arcgis-enterprise/overview) stack.
17 |
18 | 
19 |
20 | It can be used with any [ArcGIS developer technology](https://developers.arcgis.com/documentation/#sdks) or [any other product](https://esri-es.github.io/awesome-arcgis/arcgis/products/). For example add the StreamServer to a [webmap](https://esri-es.github.io/awesome-arcgis/esri/open-vision/open-specifications/web-map/) and visualize it in Operations Dashboard, ArcGIS Pro, any Storymap, etc.
21 |
22 | ## Start the app
23 |
24 | > *We are assuming you are familiar with NodeJS, if you are not please [read this first](https://nodejs.org/en/docs/guides/getting-started-guide/)*
25 |
26 | 1. Check [init.js](https://github.com/esri-es/arcgis_websocket_server/blob/feature/dynamic_service/init.js#L4) and fill in **SERVICE_CONF** object properly.
27 |
28 | ```js
29 | const SERVICE_CONF = {
30 | name : "twitter",
31 | out_sr : {
32 | wkid : 102100,
33 | latestWkid : 3857
34 | },
35 | port : 9000,
36 | host : process.env["NGROK"] || "localhost",
37 | protocol : process.env["NGROK"] ? "https" : "http"
38 | };
39 | ```
40 |
41 | > Be aware of the **name : "twitter"**. It will be propagated below, in the instructions. If you want to use othe name, change it also along the instructions.
42 |
43 | 2. Start the real time server: `node init.js "ws://localhost:8888" "4.11"`
44 |
45 | > In this example **ws://localhost:8888** is the websocket connection we want to consume data from. This websocket connection it's not the same as the one which will be exposed by **StreamServer**. But don't worry, all will be wired up (luckily)
46 |
47 | > It will check the **websocket** connection first. If it's any problem, it will notice it, and it will warn you :-)
48 |
49 | > If it's able to connect, it "automagically" set the fields for your **StreamServer** according to the payload received in the websocket connection attempted before.
50 |
51 | 3. Start a web server: `cd example & http-server -p 9090`
52 | 4. Open: [http://localhost:9090/](http://localhost:9090/)
53 | 1. Stream service url: `http://localhost:9000/arcgis/rest/services/twitter/StreamServer`
54 |
55 | ### Using HTTPS (NGROK)
56 |
57 | If you want to test this from the [sandbox sample](https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=layers-streamlayer) you can also use [ngrok](https://ngrok.com/)
58 |
59 | 1) Run: `ngrok http 9000`
60 | 2) Stop init.js and run `NGROK=yourid.ngrok.io node init.js "ws://localhost:8888" "4.11"`
61 | 3) Use: `https://yourid.ngrok.io/arcgis/rest/services/twitter/StreamServer` instead of `http://localhost:9000/arcgis/rest/services/twitter/StreamServer`
62 |
63 | ## Known issues
64 |
65 | ### ArcGIS API for JavaScript version <= v4.8 & v3.x
66 |
67 | > UPDATE : Working in a version which can handle any version! Cross your fingers!
68 |
69 | Before [this commit](https://github.com/hhkaos/arcgis_websocket_server/commit/22c48299d92e7761e6c718d2c6afa525284fc448) on May 5, 2015 this streamserver was only working with JS API <= v4.8 and v3.x. If you want to know more you can also [check this issue](https://github.com/hhkaos/arcgis_websocket_server/issues/3).
70 |
71 | ## Additional documentation
72 |
73 | * [ArcGIS Server > Stream services](http://enterprise.arcgis.com/en/server/latest/publish-services/linux/stream-services.htm)
74 | * [ArcGIS REST API > StreamServices](https://developers.arcgis.com/rest/services-reference/stream-service.htm)
75 | * [Awesome ArcGIS > GeoEvent Server](https://esri-es.github.io/awesome-arcgis/arcgis/products/arcgis-enterprise/arcgis-server/geoevent-server/)
76 | * [Awesome ArcGIS > Internet of things (IoT) & Real-time (RT)](https://esri-es.github.io/awesome-arcgis/esri/emerging-technologies/iot-rt/?)
77 | * [Public stream services in ArcGIS Online](https://esri-es.github.io/arcgis-developer-tips-and-tricks/arcgis-online/search/?q=typekeywords%3A%22stream+service%22&numResults=100&sortField=relevance&Thumbnail=generateThumbnail(elem)&Title=elem.title&Details=%27%3Ca+href%3D%22https%3A%2F%2Fwww.arcgis.com%2Fhome%2Fitem.html%3Fid%3D%27%2Belem.id%2B%27%22+target%3D%22_blank%22%3EDetails%3C%2Fa%3E%27&Owner=elem.owner&Type=elem.type&Views=elem.numViews)
78 | * [Preview several Stream Services simultaneously in a Web Map](http://www.arcgis.com/home/webmap/viewer.html?webmap=55a55a4c08934ba890f7fbd5589cffe6)
79 | * [Pubnub & Esri](https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi)
80 | * [Mapping and Tracking that Your Users Crave](https://www.youtube.com/watch?v=VWoXSJWgwrU)
81 | * [Esri DevSummit 2017 Keynote: PubNub CEO Todd Greene](https://www.youtube.com/watch?v=yrbODI7cuAk)
82 | * [Search more about "Stream services"](https://esri-es.github.io/arcgis-search/?amp%3Butm_source=opensearch&search=%22Stream+services%22)
83 |
84 | ## Talk: Geolocating tweets in real time (in Spanish)
85 |
86 | En la charla [Geolocalizando tweets en tiempo real](http://slides.com/hhkaos/geolocalizando-tweets#/) del día día 24 de Julio de 2019 se explicó:
87 |
88 | * Objetivos y resultados del experimento
89 | * Cómo lanzar el proyecto
90 | * Demo: Cargar los tweets en una StreamLayer
91 | * Demo: Cargar los tweets en un Web map
92 | * Demo: Cargar los tweets con gráficar en tº real
93 | * Diagrama del comportamiento de [twitter-rt-service](https://github.com/esri-es/twitter-rt-service)
94 | * Mejoras pendientes en [twitter-rt-service](https://github.com/esri-es/twitter-rt-service)
95 | * Diagrama del comportamiento de [arcgis_websocket_server](https://github.com/esri-es/arcgis_websocket_server)
96 | * Mejoras pendientes en [arcgis_websocket_server](https://github.com/esri-es/arcgis_websocket_server)
97 | * Estructura de ficheros del proyecto: [twitter-rt-service](https://github.com/esri-es/twitter-rt-service)
98 | * Explicación del código: [twitter-rt-service](https://github.com/esri-es/twitter-rt-service)
99 | * Estructura de ficheros del proyecto: [arcgis_websocket_server](https://github.com/esri-es/arcgis_websocket_server)
100 | * Explicación del código: [arcgis_websocket_server](https://github.com/esri-es/arcgis_websocket_server)
101 | * Despedida, preguntas y agracedimientos
102 |
103 | A continuación puede encontrar el vídeo en Youtube con un índice interactivo en la descripción del vídeo:
104 |
105 | [](https://www.youtube.com/watch?v=PeTzi-ficFo)
106 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | StreamLayer
7 |
33 |
34 |
35 |
203 |
204 |
205 |
206 |
207 |
208 | Stream service url:
211 |
212 |
213 |
214 | Filter messages by map extent (Zoom to desired filter extent):
215 |
216 |
217 | Apply definitionExpression
218 |
219 |
220 |
--------------------------------------------------------------------------------
/init.js:
--------------------------------------------------------------------------------
1 | const testConnection = require("./utils/websocket_utils.js");
2 | const streamServer = require('./streamserver/streamserver_simple.js');
3 |
4 | const SERVICE_CONF = {
5 | name : "twitter",
6 | out_sr : {
7 | wkid : 102100,
8 | latestWkid : 3857
9 | },
10 | port : process.env["NGROK"] ? 9000 : 9000,
11 | host : process.env["NGROK"] || "localhost",
12 | protocol : process.env["NGROK"] ? "https" : "http"
13 | };
14 |
15 | var wsClientConn = process.argv[2];
16 | testConnection(wsClientConn)
17 | .then(conf => {
18 | let CONFIG = {
19 | client : conf.client,
20 | payload: conf.payload,
21 | service : SERVICE_CONF
22 | };
23 | // start StreamServer
24 | streamServer.start(CONFIG);
25 | })
26 | .catch((err) => {
27 | console.log(`ws initialization failed! reason : [${err}]`);
28 | process.exit(12);
29 | });
30 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "arcgis_websockets",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "JSV": {
8 | "version": "4.0.2",
9 | "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz",
10 | "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c="
11 | },
12 | "accepts": {
13 | "version": "1.3.5",
14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
15 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
16 | "requires": {
17 | "mime-types": "~2.1.18",
18 | "negotiator": "0.6.1"
19 | }
20 | },
21 | "ansi-styles": {
22 | "version": "1.0.0",
23 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz",
24 | "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg="
25 | },
26 | "array-flatten": {
27 | "version": "1.1.1",
28 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
29 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
30 | },
31 | "async-limiter": {
32 | "version": "1.0.0",
33 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
34 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
35 | },
36 | "body-parser": {
37 | "version": "1.18.3",
38 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
39 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
40 | "requires": {
41 | "bytes": "3.0.0",
42 | "content-type": "~1.0.4",
43 | "debug": "2.6.9",
44 | "depd": "~1.1.2",
45 | "http-errors": "~1.6.3",
46 | "iconv-lite": "0.4.23",
47 | "on-finished": "~2.3.0",
48 | "qs": "6.5.2",
49 | "raw-body": "2.3.3",
50 | "type-is": "~1.6.16"
51 | }
52 | },
53 | "bytes": {
54 | "version": "3.0.0",
55 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
56 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
57 | },
58 | "chalk": {
59 | "version": "0.4.0",
60 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz",
61 | "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=",
62 | "requires": {
63 | "ansi-styles": "~1.0.0",
64 | "has-color": "~0.1.0",
65 | "strip-ansi": "~0.1.0"
66 | }
67 | },
68 | "colors": {
69 | "version": "1.3.3",
70 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
71 | "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg=="
72 | },
73 | "content-disposition": {
74 | "version": "0.5.2",
75 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
76 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
77 | },
78 | "content-type": {
79 | "version": "1.0.4",
80 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
81 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
82 | },
83 | "cookie": {
84 | "version": "0.3.1",
85 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
86 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
87 | },
88 | "cookie-signature": {
89 | "version": "1.0.6",
90 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
91 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
92 | },
93 | "core-util-is": {
94 | "version": "1.0.2",
95 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
96 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
97 | },
98 | "debug": {
99 | "version": "2.6.9",
100 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
101 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
102 | "requires": {
103 | "ms": "2.0.0"
104 | }
105 | },
106 | "depd": {
107 | "version": "1.1.2",
108 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
109 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
110 | },
111 | "destroy": {
112 | "version": "1.0.4",
113 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
114 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
115 | },
116 | "duplexer": {
117 | "version": "0.1.1",
118 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
119 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
120 | },
121 | "duplexify": {
122 | "version": "3.7.1",
123 | "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
124 | "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
125 | "requires": {
126 | "end-of-stream": "^1.0.0",
127 | "inherits": "^2.0.1",
128 | "readable-stream": "^2.0.0",
129 | "stream-shift": "^1.0.0"
130 | }
131 | },
132 | "ee-first": {
133 | "version": "1.1.1",
134 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
135 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
136 | },
137 | "encodeurl": {
138 | "version": "1.0.2",
139 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
140 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
141 | },
142 | "end-of-stream": {
143 | "version": "1.4.1",
144 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
145 | "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
146 | "requires": {
147 | "once": "^1.4.0"
148 | }
149 | },
150 | "escape-html": {
151 | "version": "1.0.3",
152 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
153 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
154 | },
155 | "etag": {
156 | "version": "1.8.1",
157 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
158 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
159 | },
160 | "event-stream": {
161 | "version": "4.0.1",
162 | "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz",
163 | "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==",
164 | "requires": {
165 | "duplexer": "^0.1.1",
166 | "from": "^0.1.7",
167 | "map-stream": "0.0.7",
168 | "pause-stream": "^0.0.11",
169 | "split": "^1.0.1",
170 | "stream-combiner": "^0.2.2",
171 | "through": "^2.3.8"
172 | }
173 | },
174 | "finalhandler": {
175 | "version": "1.1.2",
176 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
177 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
178 | "requires": {
179 | "debug": "2.6.9",
180 | "encodeurl": "~1.0.2",
181 | "escape-html": "~1.0.3",
182 | "on-finished": "~2.3.0",
183 | "parseurl": "~1.3.3",
184 | "statuses": "~1.5.0",
185 | "unpipe": "~1.0.0"
186 | },
187 | "dependencies": {
188 | "parseurl": {
189 | "version": "1.3.3",
190 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
191 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
192 | },
193 | "statuses": {
194 | "version": "1.5.0",
195 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
196 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
197 | }
198 | }
199 | },
200 | "forwarded": {
201 | "version": "0.1.2",
202 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
203 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
204 | },
205 | "fresh": {
206 | "version": "0.5.2",
207 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
208 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
209 | },
210 | "from": {
211 | "version": "0.1.7",
212 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
213 | "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4="
214 | },
215 | "fs-extra": {
216 | "version": "8.0.1",
217 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.0.1.tgz",
218 | "integrity": "sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==",
219 | "requires": {
220 | "graceful-fs": "^4.1.2",
221 | "jsonfile": "^4.0.0",
222 | "universalify": "^0.1.0"
223 | }
224 | },
225 | "graceful-fs": {
226 | "version": "4.1.15",
227 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
228 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
229 | },
230 | "has-color": {
231 | "version": "0.1.7",
232 | "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz",
233 | "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8="
234 | },
235 | "http-errors": {
236 | "version": "1.6.3",
237 | "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
238 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
239 | "requires": {
240 | "depd": "~1.1.2",
241 | "inherits": "2.0.3",
242 | "setprototypeof": "1.1.0",
243 | "statuses": ">= 1.4.0 < 2"
244 | }
245 | },
246 | "iconv-lite": {
247 | "version": "0.4.23",
248 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
249 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
250 | "requires": {
251 | "safer-buffer": ">= 2.1.2 < 3"
252 | }
253 | },
254 | "inherits": {
255 | "version": "2.0.3",
256 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
257 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
258 | },
259 | "ipaddr.js": {
260 | "version": "1.8.0",
261 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
262 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
263 | },
264 | "isarray": {
265 | "version": "1.0.0",
266 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
267 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
268 | },
269 | "jsonfile": {
270 | "version": "4.0.0",
271 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
272 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
273 | "requires": {
274 | "graceful-fs": "^4.1.6"
275 | }
276 | },
277 | "jsonlint": {
278 | "version": "1.6.3",
279 | "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz",
280 | "integrity": "sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==",
281 | "requires": {
282 | "JSV": "^4.0.x",
283 | "nomnom": "^1.5.x"
284 | }
285 | },
286 | "map-stream": {
287 | "version": "0.0.7",
288 | "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
289 | "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg="
290 | },
291 | "media-typer": {
292 | "version": "0.3.0",
293 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
294 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
295 | },
296 | "merge-descriptors": {
297 | "version": "1.0.1",
298 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
299 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
300 | },
301 | "methods": {
302 | "version": "1.1.2",
303 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
304 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
305 | },
306 | "mgrs": {
307 | "version": "1.0.0",
308 | "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz",
309 | "integrity": "sha1-+5FYjnjJACVnI5XLQLJffNatGCk="
310 | },
311 | "mime": {
312 | "version": "1.4.1",
313 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
314 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
315 | },
316 | "mime-db": {
317 | "version": "1.36.0",
318 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz",
319 | "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw=="
320 | },
321 | "mime-types": {
322 | "version": "2.1.20",
323 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz",
324 | "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==",
325 | "requires": {
326 | "mime-db": "~1.36.0"
327 | }
328 | },
329 | "moment": {
330 | "version": "2.24.0",
331 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
332 | "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
333 | },
334 | "ms": {
335 | "version": "2.0.0",
336 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
337 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
338 | },
339 | "negotiator": {
340 | "version": "0.6.1",
341 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
342 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
343 | },
344 | "nomnom": {
345 | "version": "1.8.1",
346 | "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz",
347 | "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=",
348 | "requires": {
349 | "chalk": "~0.4.0",
350 | "underscore": "~1.6.0"
351 | }
352 | },
353 | "on-finished": {
354 | "version": "2.3.0",
355 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
356 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
357 | "requires": {
358 | "ee-first": "1.1.1"
359 | }
360 | },
361 | "once": {
362 | "version": "1.4.0",
363 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
364 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
365 | "requires": {
366 | "wrappy": "1"
367 | }
368 | },
369 | "parseurl": {
370 | "version": "1.3.2",
371 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
372 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
373 | },
374 | "path-to-regexp": {
375 | "version": "0.1.7",
376 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
377 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
378 | },
379 | "pause-stream": {
380 | "version": "0.0.11",
381 | "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
382 | "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
383 | "requires": {
384 | "through": "~2.3"
385 | }
386 | },
387 | "process-nextick-args": {
388 | "version": "2.0.0",
389 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
390 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
391 | },
392 | "proj4": {
393 | "version": "2.5.0",
394 | "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.5.0.tgz",
395 | "integrity": "sha512-XZTRT7OPdLzgvtTqL8DG2cEj8lYdovztOwiwpwRSYayOty5Ipf3H68dh/fiL+HKDEyetmQSMhkkMGiJoyziz3w==",
396 | "requires": {
397 | "mgrs": "1.0.0",
398 | "wkt-parser": "^1.2.0"
399 | }
400 | },
401 | "proxy-addr": {
402 | "version": "2.0.4",
403 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
404 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
405 | "requires": {
406 | "forwarded": "~0.1.2",
407 | "ipaddr.js": "1.8.0"
408 | }
409 | },
410 | "qs": {
411 | "version": "6.5.2",
412 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
413 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
414 | },
415 | "range-parser": {
416 | "version": "1.2.0",
417 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
418 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
419 | },
420 | "raw-body": {
421 | "version": "2.3.3",
422 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
423 | "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
424 | "requires": {
425 | "bytes": "3.0.0",
426 | "http-errors": "1.6.3",
427 | "iconv-lite": "0.4.23",
428 | "unpipe": "1.0.0"
429 | }
430 | },
431 | "readable-stream": {
432 | "version": "2.3.6",
433 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
434 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
435 | "requires": {
436 | "core-util-is": "~1.0.0",
437 | "inherits": "~2.0.3",
438 | "isarray": "~1.0.0",
439 | "process-nextick-args": "~2.0.0",
440 | "safe-buffer": "~5.1.1",
441 | "string_decoder": "~1.1.1",
442 | "util-deprecate": "~1.0.1"
443 | }
444 | },
445 | "router": {
446 | "version": "1.3.3",
447 | "resolved": "https://registry.npmjs.org/router/-/router-1.3.3.tgz",
448 | "integrity": "sha1-wUL2tepNazNZAiypW2WAvSF/ic8=",
449 | "requires": {
450 | "array-flatten": "2.1.1",
451 | "debug": "2.6.9",
452 | "methods": "~1.1.2",
453 | "parseurl": "~1.3.2",
454 | "path-to-regexp": "0.1.7",
455 | "setprototypeof": "1.1.0",
456 | "utils-merge": "1.0.1"
457 | },
458 | "dependencies": {
459 | "array-flatten": {
460 | "version": "2.1.1",
461 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
462 | "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY="
463 | }
464 | }
465 | },
466 | "safe-buffer": {
467 | "version": "5.1.2",
468 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
469 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
470 | },
471 | "safer-buffer": {
472 | "version": "2.1.2",
473 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
474 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
475 | },
476 | "send": {
477 | "version": "0.16.2",
478 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
479 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
480 | "requires": {
481 | "debug": "2.6.9",
482 | "depd": "~1.1.2",
483 | "destroy": "~1.0.4",
484 | "encodeurl": "~1.0.2",
485 | "escape-html": "~1.0.3",
486 | "etag": "~1.8.1",
487 | "fresh": "0.5.2",
488 | "http-errors": "~1.6.2",
489 | "mime": "1.4.1",
490 | "ms": "2.0.0",
491 | "on-finished": "~2.3.0",
492 | "range-parser": "~1.2.0",
493 | "statuses": "~1.4.0"
494 | }
495 | },
496 | "serve-static": {
497 | "version": "1.13.2",
498 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
499 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
500 | "requires": {
501 | "encodeurl": "~1.0.2",
502 | "escape-html": "~1.0.3",
503 | "parseurl": "~1.3.2",
504 | "send": "0.16.2"
505 | }
506 | },
507 | "setprototypeof": {
508 | "version": "1.1.0",
509 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
510 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
511 | },
512 | "split": {
513 | "version": "1.0.1",
514 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
515 | "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
516 | "requires": {
517 | "through": "2"
518 | }
519 | },
520 | "statuses": {
521 | "version": "1.4.0",
522 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
523 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
524 | },
525 | "stream-chain": {
526 | "version": "2.1.0",
527 | "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.1.0.tgz",
528 | "integrity": "sha512-PAUXdRGm0G8P0+/+JEd3O9kfmB9kwmr2nKIc5zhcsHn0KdBByD5PJ2po21iDzc+TZsOSEbU8j4JbAevJsZkLyQ=="
529 | },
530 | "stream-combiner": {
531 | "version": "0.2.2",
532 | "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
533 | "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
534 | "requires": {
535 | "duplexer": "~0.1.1",
536 | "through": "~2.3.4"
537 | }
538 | },
539 | "stream-json": {
540 | "version": "1.2.1",
541 | "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.2.1.tgz",
542 | "integrity": "sha512-KcZkgyTENM4HXBDyIguAcyWUogXp+wwWF3wLL5QtWj8SlNESx+4xGWk7rYdFDtTvQ1FhSRkQEWv5xOu4eHU+jQ==",
543 | "requires": {
544 | "stream-chain": "^2.1.0"
545 | }
546 | },
547 | "stream-shift": {
548 | "version": "1.0.0",
549 | "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
550 | "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
551 | },
552 | "string_decoder": {
553 | "version": "1.1.1",
554 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
555 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
556 | "requires": {
557 | "safe-buffer": "~5.1.0"
558 | }
559 | },
560 | "strip-ansi": {
561 | "version": "0.1.1",
562 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz",
563 | "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE="
564 | },
565 | "through": {
566 | "version": "2.3.8",
567 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
568 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
569 | },
570 | "through2": {
571 | "version": "3.0.1",
572 | "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
573 | "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
574 | "requires": {
575 | "readable-stream": "2 || 3"
576 | }
577 | },
578 | "type-is": {
579 | "version": "1.6.16",
580 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
581 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
582 | "requires": {
583 | "media-typer": "0.3.0",
584 | "mime-types": "~2.1.18"
585 | }
586 | },
587 | "ultron": {
588 | "version": "1.1.1",
589 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
590 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
591 | },
592 | "underscore": {
593 | "version": "1.6.0",
594 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
595 | "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag="
596 | },
597 | "universalify": {
598 | "version": "0.1.2",
599 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
600 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
601 | },
602 | "unpipe": {
603 | "version": "1.0.0",
604 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
605 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
606 | },
607 | "util-deprecate": {
608 | "version": "1.0.2",
609 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
610 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
611 | },
612 | "utils-merge": {
613 | "version": "1.0.1",
614 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
615 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
616 | },
617 | "uuid": {
618 | "version": "3.3.2",
619 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
620 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
621 | },
622 | "vary": {
623 | "version": "1.1.2",
624 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
625 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
626 | },
627 | "websocket-stream": {
628 | "version": "5.5.0",
629 | "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.5.0.tgz",
630 | "integrity": "sha512-EXy/zXb9kNHI07TIMz1oIUIrPZxQRA8aeJ5XYg5ihV8K4kD1DuA+FY6R96HfdIHzlSzS8HiISAfrm+vVQkZBug==",
631 | "requires": {
632 | "duplexify": "^3.5.1",
633 | "inherits": "^2.0.1",
634 | "readable-stream": "^2.3.3",
635 | "safe-buffer": "^5.1.2",
636 | "ws": "^3.2.0",
637 | "xtend": "^4.0.0"
638 | },
639 | "dependencies": {
640 | "ws": {
641 | "version": "3.3.3",
642 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
643 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
644 | "requires": {
645 | "async-limiter": "~1.0.0",
646 | "safe-buffer": "~5.1.0",
647 | "ultron": "~1.1.0"
648 | }
649 | }
650 | }
651 | },
652 | "wkt-parser": {
653 | "version": "1.2.3",
654 | "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.2.3.tgz",
655 | "integrity": "sha512-s7zrOedGuHbbzMaQOuf8HacuCYp3LmmrHjkkN//7UEAzsYz7xJ6J+j/84ZWZkQcrRqi3xXyuc4odPHj7PEB0bw=="
656 | },
657 | "wrappy": {
658 | "version": "1.0.2",
659 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
660 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
661 | },
662 | "ws": {
663 | "version": "5.2.2",
664 | "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
665 | "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
666 | "requires": {
667 | "async-limiter": "~1.0.0"
668 | }
669 | },
670 | "xtend": {
671 | "version": "4.0.1",
672 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
673 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
674 | }
675 | }
676 | }
677 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "arcgis_websockets",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "subscribe_arcgis.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "colors": "^1.3.3",
14 | "event-stream": "^4.0.1",
15 | "finalhandler": "^1.1.2",
16 | "fs-extra": "^8.0.1",
17 | "jsonlint": "^1.6.3",
18 | "moment": "^2.24.0",
19 | "proj4": "^2.5.0",
20 | "router": "^1.3.3",
21 | "stream-chain": "^2.1.0",
22 | "stream-json": "^1.2.1",
23 | "through2": "^3.0.1",
24 | "uuid": "^3.3.2",
25 | "websocket-stream": "^5.5.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/streamserver/pipelines/custom.js:
--------------------------------------------------------------------------------
1 | /*
2 | This is where you can customize the transformation of your data from websockets
3 |
4 | If you want to add more steps in the final pipeline:
5 |
6 | - implement a function like this and add it to the array on the module.exports
7 | function your_step( context ) {
8 | return data => {
9 | ...do your stuff here
10 | return result // result has to be an object like data or null to skip it
11 | }
12 | }
13 |
14 | context =
15 | {
16 | geo : { lat : fieldLat, lon : fieldLon } || null,
17 | service :
18 | }
19 |
20 | data = {
21 | key :
22 | value :
23 | }
24 |
25 |
26 | */
27 |
28 | const proj4 = require('proj4');
29 |
30 | module.exports = [adaptPayload];
31 |
32 | function adaptPayload (context) {
33 | return data => {
34 | if(context.geo !== null) {
35 | let data_lat = data.value[context.geo.lat];
36 | let data_lon = data.value[context.geo.lon];
37 | if (data_lat !== 0 && data_lon !== 0 ) {
38 | // Reprojection according to conf.
39 | try {
40 | let [lon,lat] = proj4(proj4.defs(`EPSG:${context.service.out_sr.latestWkid}`),[data_lon,data_lat])
41 | let fixed = {
42 | geometry : {
43 | x : lon, y : lat,
44 | spatialReference : context.service.out_sr
45 | },
46 | attributes : data.value
47 | };
48 | fixed.attributes.FltId = data.value.id_str;
49 | data.value = fixed;
50 |
51 | return data;
52 | } catch(err) {
53 | console.error(`Failed re-projection [${err}]`);
54 | console.log(`${data_lat} || ${data_lon}`);
55 | return null;
56 | }
57 | } else {
58 | return null
59 | }
60 | } else {
61 | return null
62 | }
63 | };
64 | }
65 |
--------------------------------------------------------------------------------
/streamserver/pipelines/default.js:
--------------------------------------------------------------------------------
1 | const {parser} = require('stream-json/Parser');
2 | const {streamValues} = require('stream-json/streamers/StreamValues');
3 | const {chain} = require('stream-chain');
4 | const CUSTOM_PIPELINE = require('./custom.js');
5 |
6 | function _injectCtx (arr,ctx) {
7 | return arr.map(fn => fn(ctx));
8 | }
9 |
10 | function sanityCheck(arr) {
11 | let length = arr.length;
12 | return Array.isArray(arr) && arr.filter(fn => typeof(fn) === "function").length === length;
13 | }
14 |
15 | function compose(ctx) {
16 | let pipeline = [
17 | parser({jsonStreaming: true}),
18 | streamValues()
19 | ];
20 | if (sanityCheck(CUSTOM_PIPELINE)) {
21 | pipeline.push(..._injectCtx(CUSTOM_PIPELINE,ctx));
22 | } else {
23 | console.log(`Default Pipeline setup...[Skipping custom pipeline]`);
24 | if (CUSTOM_PIPELINE.length > 0) {
25 | console.warn(`Something is wrong : Please review your custom pipeline`);
26 | process.exit(12);
27 | }
28 | }
29 |
30 | return pipeline;
31 | }
32 |
33 | module.exports = compose;
34 |
--------------------------------------------------------------------------------
/streamserver/streamserver_express.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | const expressWebSocket = require('express-ws');
3 | const websocket = require('websocket-stream');
4 | const websocketStream = require('websocket-stream/stream');
5 | const {parser} = require('stream-json/Parser');
6 | const {streamValues} = require('stream-json/streamers/StreamValues');
7 | const {chain} = require('stream-chain');
8 | const uuidv4 = require('uuid/v4');
9 | const proj4 = require('proj4');
10 | const esriTypes = require('./utils/esri_types.js');
11 | const streamServerFilter = require('./src/filter_utils.js');
12 |
13 | function setup(serviceName) {
14 | var CONF;
15 | try {
16 | let {
17 | hostname,
18 | port,
19 | protocol
20 | } = new URL(process.argv[2]);
21 | CONF = {
22 | ws: {
23 | server: {
24 | port: process.env["NGROK"] ? null : 9000,
25 | protocol: "ws",
26 | host: process.env["NGROK"] || "localhost"
27 | },
28 | client: {
29 | protocol: protocol.replace(/:/g, ""),
30 | host: hostname,
31 | port: port,
32 | geo_fields: null
33 | }
34 | },
35 | service: {
36 | name: serviceName,
37 | fieldObj: null,
38 | out_sr: {
39 | wkid: 102100,
40 | latestWkid: 3857
41 | }
42 | }
43 | };
44 | } catch (err) {
45 | console.log(`ws initialization failed! reason : [${err}]`);
46 | process.exit(12);
47 | }
48 | return CONF;
49 | }
50 |
51 | function updateServiceInfo(obj) {
52 | let service = require('./templates/service.json');
53 | Object.keys(obj).forEach(k => {
54 | service[k] = obj[k];
55 | });
56 | return service;
57 | }
58 |
59 | const JSAPI_VERSION = process.argv[3] || "4.11";
60 |
61 | function doChallenge() {
62 | return !/^(3\.[1-9][0-9]|4\.[1-8]?)$/.test(JSAPI_VERSION);
63 | }
64 |
65 | function guessGeoFields(arr) {
66 | let candidates = arr
67 | .filter(fieldObj => fieldObj.type === "esriFieldTypeDouble")
68 | .filter(fieldObj => /\b(latitude|longitude|lat|lon|x|y)\b/.test(fieldObj.name));
69 |
70 | if (candidates.length >= 2) {
71 | console.log(`found geofields candidates:`);
72 | candidates.map(e => console.log(e.name))
73 | }
74 | return {
75 | latField: "lat",
76 | lonField: "lon"
77 | }
78 | }
79 |
80 | function start(conf) {
81 | // Maybe we can move this to setup()
82 | const SERVICE_NAME = conf.service.name;
83 | const BASE_URL = `/arcgis/rest/services/${SERVICE_NAME}/StreamServer`;
84 | let wsClientPort = conf.ws.client.port ?
85 | `:${conf.ws.client.port}` :
86 | "";
87 | let wsServerPort = conf.ws.server.port ?
88 | `:${conf.ws.server.port}` :
89 | "";
90 | let wsClientUrl = `${conf.ws.client.protocol}://${conf.ws.client.host}${wsClientPort}`;
91 | let wsServerUrl = `${conf.ws.server.protocol}://${conf.ws.server.host}${wsServerPort}${BASE_URL}`;
92 | let fields = esriTypes.convertToEsriFields(conf.service.fieldObj);
93 | let {
94 | latField,
95 | lonField
96 | } = guessGeoFields(fields);
97 |
98 | let serviceRes = updateServiceInfo({
99 | fields: fields,
100 | streamUrls: [{
101 | "transport": "ws",
102 | "urls": [
103 | //`wss://${wsUrl}`,
104 | `${wsServerUrl}`
105 | ]
106 | }]
107 | });
108 |
109 | var app = express();
110 | // CORS middleware
111 | app.use(function(req, res, next) {
112 | res.header("Access-Control-Allow-Origin", "*");
113 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
114 | next();
115 | });
116 |
117 | app.get(`${BASE_URL}`, function(req, res, next) {
118 | res.status(200).json(serviceRes);
119 | res.end();
120 | });
121 |
122 | // extend express app with app.ws()
123 | expressWebSocket(app, null, {
124 | perMessageDeflate: false,
125 | });
126 |
127 | let pullStream = websocket(wsClientUrl, {
128 | perMessageDeflate: false
129 | });
130 |
131 | app.ws('/subscribe', function(ws, req) {
132 |
133 | // convert ws instance to stream
134 | const stream = websocketStream(ws, {
135 | // websocket-stream options here
136 | binary: true,
137 | });
138 |
139 | stream.socket.uuid = uuidv4();
140 | stream.socket.challenge = false;
141 | var filter = false;
142 | stream.on('data', function(buf) {
143 | let data = buf.toString();
144 | console.log(`${data} from [${stream.socket.uuid}]`);
145 | if (!connections[stream.socket.uuid].challenge && doChallenge()) {
146 | // Challenge
147 | stream.write(JSON.stringify({
148 | error: null,
149 | ...JSON.parse(data)
150 | }));
151 | console.log("Challenge done!");
152 | connections[stream.socket.uuid].challenge = true;
153 | } else {
154 | // Filters
155 | try {
156 | connections[stream.socket.uuid].filter = JSON.parse(data).filter.where;
157 | filter = true;
158 | } catch (err) {
159 | console.log(`Invalid filter received from ${stream.socket.uuid}: ${data}`);
160 | };
161 | }
162 | });
163 | stream.on('close', function() {
164 | console.log(`client [${stream.socket.uuid}] disconnected`);
165 | delete connections[stream.socket.uuid];
166 | })
167 |
168 | let pipeline = chain([
169 | parser({
170 | jsonStreaming: true
171 | }),
172 | streamValues(),
173 | data => {
174 | return filter ?
175 | streamServerFilter(data.value, connections[stream.socket.uuid].filter) ?
176 | data :
177 | null :
178 | data;
179 | },
180 | data => {
181 | // Reprojection according to conf.
182 | let [lon, lat] = proj4(proj4.defs(`EPSG:${serviceRes.service.out_sr.latestWkid}`), [data.value[lonField], data.value[latField]])
183 | data.value[latField] = lat;
184 | data.value[lonField] = lon;
185 | return data;
186 |
187 | },
188 | data => Buffer.from(JSON.stringify(data.value))
189 | ]);
190 |
191 | connections[stream.socket.uuid] = stream.socket;
192 | pullStream
193 | .pipe(pipeline)
194 | .pipe(stream)
195 |
196 | });
197 |
198 | app.listen(conf.ws.server.port)
199 | }
200 |
201 |
202 |
203 | module.exports = {
204 | start: start,
205 | setup: setup
206 | };
207 |
--------------------------------------------------------------------------------
/streamserver/streamserver_simple.js:
--------------------------------------------------------------------------------
1 | const websocket = require('websocket-stream');
2 | const http = require('http');
3 | const Router = require('router');
4 | const finalhandler = require('finalhandler');
5 | const {chain} = require('stream-chain');
6 | const uuidv4 = require('uuid/v4');
7 | const esriTypes = require('./utils/esri_types.js');
8 | const defaultPipeline = require('./pipelines/default.js');
9 |
10 | const JSAPI_VERSION = process.argv[3] || "4.11";
11 |
12 | function _doChallenge() {
13 | return !/^(3\.[1-9][0-9]|4\.[1-8]?)$/.test(JSAPI_VERSION);
14 | }
15 |
16 | function _updateServiceInfo(obj) {
17 | let service = require('../templates/service.json');
18 | Object.keys(obj).forEach(k => {
19 | service[k] = obj[k];
20 | });
21 | return service;
22 | }
23 |
24 | function _guessGeoFields (arr) {
25 | let latFieldsNames = ["latitude","coordenadas_y","lat","y"];
26 | let lonFieldsNames = ["longitude","coordenadas_x","long","lon","lng","x"];
27 | let regexLat = new RegExp(`\\b(${latFieldsNames.join("|")})\\b`, "i");
28 | let regexLon = new RegExp(`\\b(${lonFieldsNames.join("|")})\\b`, "i");
29 | // Lat & and Lon has to be float numbers
30 | let candidates = arr
31 | .filter(fieldObj => fieldObj.type === "esriFieldTypeDouble");
32 |
33 | let names = candidates.map(e => e.name);
34 | let latField = names.find(name => regexLat.test(name));
35 | let lonField = names.find(name => regexLon.test(name));
36 | // TO BE Reviewed
37 | return {
38 | latField : latField || null,
39 | lonField : lonField || null
40 | }
41 | }
42 |
43 | function _setup(config) {
44 | let fields = esriTypes.convertToEsriFields(config.payload);
45 | let newConfig = {
46 | ws : {
47 | server : {
48 | port : config.service.port,
49 | protocol : "ws",
50 | host : config.service.host,
51 | },
52 | client : {...config.client}
53 | },
54 | service : {...config.service,
55 | info : _updateServiceInfo({ fields : fields}),
56 | base_url : `/arcgis/rest/services/${config.service.name}/StreamServer`
57 | }
58 | };
59 |
60 | return newConfig;
61 | }
62 |
63 | function _setupHTTPServer(serviceConf){
64 | var router = Router();
65 | let isNgrok = /ngrok.io/.test(serviceConf.host);
66 | let wsServerPort = isNgrok
67 | ? ""
68 | : serviceConf.port
69 | ? `:${serviceConf.port}`
70 | : "";
71 | let wsServerUrl = `${isNgrok ? "wss" : "ws"}://${serviceConf.host}${wsServerPort}${serviceConf.base_url}`;
72 | // Update serviceConf.info prior to serve it from HTTPServer
73 | serviceConf.info.streamUrls = [{
74 | transport : "ws",
75 | urls: [
76 | //`wss://${wsUrl}`,
77 | `${wsServerUrl}`
78 | ]
79 | }];
80 |
81 | router.get(`${serviceConf.base_url}`, function (req, res) {
82 | res.setHeader('Content-Type', 'application/json; charset=utf-8');
83 | res.setHeader("Access-Control-Allow-Origin", "*");
84 | res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
85 | res.statusCode = 200;
86 | res.write(JSON.stringify(serviceConf.info));
87 | res.end();
88 |
89 | });
90 |
91 | // Retro-compatibility end-point (versions before 4.8)
92 | router.get(`/arcgis/rest/info`, function (req, res) {
93 | res.setHeader('Content-Type', 'application/json; charset=utf-8');
94 | res.setHeader("Access-Control-Allow-Origin", "*");
95 | res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
96 | res.statusCode = 200;
97 | res.write(JSON.stringify({
98 | currentVersion: 10.5,
99 | fullVersion: "10.5.0",
100 | authInfo: {
101 | isTokenBasedSecurity: false
102 | }
103 | }));
104 | res.end();
105 | });
106 |
107 | let server = http.createServer(function(req, res) {
108 | router(req, res, finalhandler(req, res));
109 | });
110 |
111 | server.listen(serviceConf.port, function() {
112 | console.log(`Your StreamServer is ready on [${serviceConf.protocol}://${serviceConf.host}${wsServerPort}${serviceConf.base_url}]`);
113 | });
114 |
115 | return server;
116 | }
117 |
118 | function _setupSource(obj) {
119 | //console.log( `WS Server ready at [${conf.ws.client.wsUrl}/${BASE_URL}/subscribe]`);
120 | return function handle(stream, request) {
121 | var serverRef = this;
122 | stream.binary = false;
123 | stream.socket.uuid = uuidv4();
124 | console.log(`client [${stream.socket.uuid}] connected`);
125 | stream.socket.challenge = false;
126 | stream.on('data', function(buf){
127 | let data = buf.toString();
128 | console.log(`${data} from [${stream.socket.uuid}]`);
129 | if (!stream.socket.challenge && _doChallenge()) {
130 | // Challenge
131 | try {
132 | stream.write(JSON.stringify({
133 | error: null,
134 | ...JSON.parse(data)
135 | }));
136 | } catch(err) {
137 | console.log(`bad payload[${data}]`);
138 | }
139 | console.log("Challenge done!");
140 | stream.socket.challenge = true;
141 | } else {
142 | // Filters
143 | try{
144 | stream.socket.filter = JSON.parse(data).filter.where;
145 | }catch(err){
146 | console.log(`Invalid filter received from ${stream.socket.uuid}: ${data}`);
147 | };
148 | }
149 | });
150 | stream.on('close',function(){
151 | console.log(`client [${stream.socket.uuid}] disconnected`);
152 | serverRef.clients.delete(stream.socket);
153 | stream.end();
154 | });
155 |
156 | let pipeline = chain([
157 | ...defaultPipeline({ geo : obj.geo, service : obj.service}),
158 | data => {
159 | return stream.socket.hasOwnProperty("filter")
160 | ? streamServerFilter(data.value,stream.socket.filter)
161 | ? data
162 | : null
163 | : data;
164 | },
165 | data => JSON.stringify(data.value)
166 | ]);
167 |
168 | pipeline.on("error", function(err){
169 | console.error(err);
170 | })
171 |
172 | obj.pullStream
173 | .pipe(pipeline)
174 | .pipe(stream)
175 | }
176 | }
177 |
178 | function start(cfg){
179 | // First some Plumbing...
180 | let conf = _setup(cfg);
181 | let avoidGeo = false;
182 | let {latField,lonField} = _guessGeoFields(conf.service.info.fields);
183 |
184 | if (latField === null || lonField === null ) {
185 | avoidGeo = true;
186 | console.warn("Unable to find field candidates on payload. Skipping Re-Projecction");
187 | } else {
188 | console.log(`Found spatial information in fields [${lonField},${latField}]`);
189 | }
190 |
191 | let HTTPServer = _setupHTTPServer(conf.service);
192 | let wsRemoteClient = websocket(conf.ws.client.wsUrl, {
193 | perMessageDeflate: false
194 | });
195 |
196 | var fieldGeo = avoidGeo
197 | ? null
198 | : {
199 | lat : latField,
200 | lon : lonField
201 | };
202 |
203 | var wss = websocket.createServer({
204 | server: HTTPServer,
205 | path : `${conf.service.base_url}/subscribe`,
206 | binary: false },
207 | _setupSource({
208 | pullStream : wsRemoteClient,
209 | service: conf.service,
210 | geo : fieldGeo
211 | }))
212 | }
213 |
214 | module.exports = {
215 | start: start
216 | };
217 |
--------------------------------------------------------------------------------
/streamserver/utils/esri_types.js:
--------------------------------------------------------------------------------
1 | // Got & adapted from from https://github.com/koopjs/FeatureServer/blob/master/src/utils.js
2 |
3 | const moment = require('moment')
4 | const DATE_FORMATS = [moment.ISO_8601]
5 |
6 | function convertToEsriFields (obj) {
7 | return Object.entries(obj).map(([key,value]) => getField(key,value));
8 | }
9 |
10 | function getField (k,v) {
11 | return {
12 | name: k,
13 | type: esriTypeMap(detectType(v)),
14 | alias: k,
15 | nullable: true
16 | }
17 | }
18 |
19 | function detectType (value) {
20 | var type = typeof value
21 |
22 | if (type === 'number') {
23 | return Number.isInteger(value) ? 'Integer' : 'Double'
24 | } else if (type && moment(value, DATE_FORMATS, true).isValid()) {
25 | return 'Date'
26 | } else {
27 | return 'String'
28 | }
29 | }
30 |
31 | function esriTypeMap (type) {
32 | switch (type.toLowerCase()) {
33 | case 'double':
34 | return 'esriFieldTypeDouble'
35 | case 'integer':
36 | return 'esriFieldTypeInteger'
37 | case 'date':
38 | return 'esriFieldTypeDate'
39 | case 'blob':
40 | return 'esriFieldTypeBlob'
41 | case 'geometry':
42 | return 'esriFieldTypeGeometry'
43 | case 'globalid':
44 | return 'esriFieldTypeGlobalID'
45 | case 'guid':
46 | return 'esriFieldTypeGUID'
47 | case 'raster':
48 | return 'esriFieldTypeRaster'
49 | case 'single':
50 | return 'esriFieldTypeSingle'
51 | case 'smallinteger':
52 | return 'esriFieldTypeSmallInteger'
53 | case 'xml':
54 | return 'esriFieldTypeXML'
55 | case 'string':
56 | default:
57 | return 'esriFieldTypeString'
58 | }
59 | }
60 |
61 | module.exports = {
62 | convertToEsriFields: convertToEsriFields
63 | }
64 |
--------------------------------------------------------------------------------
/streamserver/utils/filter_utils.js:
--------------------------------------------------------------------------------
1 | var opsExt = '(AND|OR)';
2 | var opsInt = '(NOT)?(=|<>|LIKE|IS)(NOT)?';
3 | var reOpsExt = new RegExp(`\\)\\s${opsExt}\\s\\(`,"gi");
4 | var reOpsInt = new RegExp(`\\s${opsInt}\\s`,"gi");
5 | var str = `(ciudadanos = '1') AND (ciudadanos <> '1') AND (ciudadanos LIKE '1%') AND (ciudadanos LIKE '%1') AND (ciudadanos LIKE '%1%') AND (ciudadanos NOT LIKE '%1%') AND (ciudadanos IS NULL) AND (ciudadanos IS NOT NULL)`;
6 | const util = require("util");
7 |
8 | function buildQuery(field, op, value) {
9 | return `(${field} ${op} ${value})`;
10 | }
11 |
12 | function normalizeValue (str) {
13 |
14 | let safestr = str.replace(/'/g,"").replace("%","");
15 | let result = safestr;
16 | if (/(true|false)/i.test(safestr)) {
17 | result = safestr.replace(/'/g,"").toLowerCase() === "false" ? false : true;
18 | }
19 | if (/null/i.test(safestr)) {
20 | result = null;
21 | }
22 | if (/\b[0-9]+\b/.test(safestr)) {
23 | result = parseInt(safestr);
24 | }
25 | if (/\b[0-9\.]+\b/.test(safestr)) {
26 | result = parseFloat(safestr);
27 | }
28 | return result;
29 | }
30 |
31 | var operators = {
32 | "=" : function(data,op1,op2) {
33 | let op2fixed = normalizeValue(op2);
34 | return data.hasOwnProperty(op1) && data[op1] === op2fixed;
35 | },
36 | "IS" : function(data,op1,op2) {
37 | let op2fixed = normalizeValue(op2);
38 | let cond = data[op1] === false && op2fixed === null
39 | ? true
40 | : data[op1] == op2fixed;
41 | return data.hasOwnProperty(op1) && cond;
42 | },
43 | "<>" : function(data,op1,op2) {
44 | let op2fixed = normalizeValue(op2);
45 | return data.hasOwnProperty(op1) && data[op1] !== op2
46 | },
47 | "IS NOT" : function(data,op1,op2) {
48 | let op2fixed = normalizeValue(op2);
49 | let cond = data[op1] === false && op2fixed === null
50 | ? true
51 | : data[op1] == op2fixed;
52 | return data.hasOwnProperty(op1) && data[op1] !== op2fixed
53 | },
54 | "LIKE" : function(data,op1,op2) {
55 | let op2fixed = normalizeValue(op2);
56 | let re = new RegExp(op2fixed,"gi");
57 | return data.hasOwnProperty(op1) && re.test(data[op1])
58 | },
59 | "NOT LIKE" : function(data,op1,op2) {
60 | let op2fixed = normalizeValue(op2);
61 | let re = new RegExp(op2fixed,"gi");
62 | return data.hasOwnProperty(op1) && !re.test(data[op1])
63 | },
64 | "CONTAINS" : function(data,op1,op2) {
65 | let op2fixed = normalizeValue(op2);
66 | let re = new RegExp(op2fixed, "gi");
67 | return data.hasOwnProperty(op1) && re.test(data[op1])
68 | },
69 | "NOT CONTAINS" : function(data,op1,op2) {
70 | let op2fixed = normalizeValue(op2);
71 | let re = new RegExp(op2fixed, "gi");
72 | return data.hasOwnProperty(op1) && !re.test(data[op1])
73 | }
74 | }
75 |
76 | function translate (d,arr) {
77 | // ["ciudadanos", "=", "true"]
78 | //console.log(`filter : ${arr.join(" ")}`);
79 | return operators[arr[1]](d,arr[0],arr[2]);
80 |
81 | }
82 |
83 |
84 |
85 | function evaluateQuery(d, queryStr) {
86 | let operatorChain = queryStr.split(/[^(AND|OR)]/).filter(el => /(AND|OR)/.test(el));
87 | var lista = queryStr.split(reOpsExt)
88 | .map(exp => exp.replace(/(\(|\))/g,""))
89 | .filter(el => !/(AND|OR)/.test(el.replace(/"/g, "")))
90 | .map(exp => exp.split(/(NOT LIKE|LIKE|IS NOT|IS|NOT CONTAINS|CONTAINS|=|<>)/))
91 | .map(exp => exp.map(el => el.trim().replace("%","")))
92 | .map(exp => translate(d,exp));
93 |
94 |
95 | let result = lista.length === 1
96 | ? lista[0]
97 | : lista.reduce((old,cur,i,arr) => {
98 | if(i < operatorChain.length) {
99 | old = i < arr.length
100 | ? operatorChain[i] === "AND"
101 | ? arr[i] && arr[i+1]
102 | : arr[i] || arr[i+1]
103 | : old;
104 | return old;
105 | } else {
106 | return old;
107 | }
108 | },true);
109 |
110 | //console.log(`data : [${util.inspect(d, { compact: true, depth: 5, breakLength: 80 })}]\nquery [${queryStr}] -> ${lista} -> ${result}` );
111 | return result;
112 |
113 | }
114 |
115 | module.exports = evaluateQuery;
116 |
--------------------------------------------------------------------------------
/templates/service.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": null,
3 | "objectIdField": null,
4 | "displayField": "id_str",
5 | "timeInfo": {
6 | "trackIdField": "id_str",
7 | "startTimeField": "MsgTime",
8 | "endTimeField": null
9 | },
10 | "geometryType": "esriGeometryPoint",
11 | "geometryField": "Location",
12 | "spatialReference": {
13 | "wkid": 4326,
14 | "latestWkid": 4326
15 | },
16 | "drawingInfo": {
17 | "renderer": {
18 | "type": "simple",
19 | "description": "",
20 | "symbol": {
21 | "color": [
22 | 0,
23 | 169,
24 | 230,
25 | 131
26 | ],
27 | "size": 9,
28 | "angle": 0,
29 | "xoffset": 0,
30 | "yoffset": 0,
31 | "type": "esriSMS",
32 | "style": "esriSMSCircle",
33 | "outline": {
34 | "color": [
35 | 255,
36 | 255,
37 | 255,
38 | 255
39 | ],
40 | "width": 1.125
41 | }
42 | }
43 | }
44 |
45 | },
46 | "fields": null,
47 | "currentVersion": "10.5",
48 | "streamUrls": null,
49 | "capabilities": "broadcast,subscribe"
50 | }
51 |
--------------------------------------------------------------------------------
/utils/test2.json:
--------------------------------------------------------------------------------
1 | {"category":null,"icon_url":"https://assets.chucknorris.host/img/avatar/chuck-norris.png","id":"TSKPCFDcTe-bJxrcrVNHkQ","url":"https://api.chucknorris.io/jokes/TSKPCFDcTe-bJxrcrVNHkQ","value":"Jesus wears a bracelet that says wwcnd (what would Chuck Norris do?)"}
2 |
--------------------------------------------------------------------------------
/utils/test3.json:
--------------------------------------------------------------------------------
1 | {"a": 1, "b": "a", "c": [], "d": {}, "e": true}
--------------------------------------------------------------------------------
/utils/test_json_ws.js:
--------------------------------------------------------------------------------
1 | const websocket = require('websocket-stream');
2 | const http = require('http');
3 | const through2 = require('through2');
4 | const {parser} = require('stream-json/Parser');
5 | const {streamValues} = require('stream-json/streamers/StreamValues');
6 | const {chain} = require('stream-chain');
7 | const fs = require('fs');
8 |
9 | var server = http.createServer(function(request, response) {
10 | response.writeHead(404);
11 | response.end();
12 | });
13 |
14 | server.listen(9000, function() {
15 | console.log(`${(new Date())} Server is listening on port 9000`);
16 | });
17 |
18 |
19 |
20 |
21 | var wss = websocket.createServer({server: server}, handle)
22 | function handle(stream, request) {
23 | // `request` is the upgrade request sent by the client.
24 |
25 | const pipeline = chain([
26 | fs.createReadStream('test2.json'),
27 | parser({packValues: true}),
28 | streamValues(),
29 | data => JSON.stringify(data.value)
30 | ]);
31 |
32 | pipeline.on('data', (data) => console.log(data));
33 | pipeline
34 | .pipe(stream);
35 | }
36 |
--------------------------------------------------------------------------------
/utils/websocket_utils.js:
--------------------------------------------------------------------------------
1 | const websocket = require('websocket-stream');
2 | const streamjson = require('stream-json');
3 | const {parser} = require('stream-json/Parser');
4 | const {streamValues} = require('stream-json/streamers/StreamValues');
5 | const {chain} = require('stream-chain');
6 |
7 |
8 | module.exports = function(wsUrl) {
9 | return new Promise((resolve, reject) => {
10 | try {
11 | let {hostname,port,protocol} = new URL(wsUrl);
12 | let ws = websocket(wsUrl);
13 | const pipemod = chain([
14 | parser({packValues: true}),
15 | streamValues(),
16 | data => {
17 | ws.unpipe();
18 | resolve({
19 | payload : data.value,
20 | client : {
21 | host : hostname,
22 | port : port,
23 | protocol : protocol,
24 | wsUrl : wsUrl
25 | }
26 | });
27 | }
28 | ]);
29 |
30 | ws.on("error", function(err){
31 | reject(`Cannot connect to [${wsUrl}]`);
32 | })
33 |
34 | pipemod
35 | .on("error", function(err){
36 | reject(err);
37 | });
38 |
39 | ws.pipe(pipemod);
40 | } catch(err) {
41 | reject(err);
42 | }
43 |
44 | })
45 | }
46 |
--------------------------------------------------------------------------------