├── .gitignore
├── LICENSE
├── README.md
├── assets
├── arch.png
├── dockerps.png
├── prepconfig.png
├── pyconfig.png
├── roles.png
└── video-thumbnail.png
├── src
├── Dockerfile
├── deploy
│ ├── .gitignore
│ ├── Pulumi.yaml
│ ├── README.md
│ ├── index.js
│ ├── package-lock.json
│ └── package.json
├── geocode.py
├── prepForward.py
├── prepReverse.py
├── startup.sh
└── tabpy.conf
└── test.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | .DS_Store
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 | *.pid.lock
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # Bower dependency directory (https://bower.io/)
28 | bower_components
29 |
30 | # node-waf configuration
31 | .lock-wscript
32 |
33 | # Compiled binary addons (https://nodejs.org/api/addons.html)
34 | build/Release
35 |
36 | # Dependency directories
37 | node_modules/
38 | jspm_packages/
39 |
40 | # TypeScript v1 declaration files
41 | typings/
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional eslint cache
47 | .eslintcache
48 |
49 | # Optional REPL history
50 | .node_repl_history
51 |
52 | # Output of 'npm pack'
53 | *.tgz
54 |
55 | # Yarn Integrity file
56 | .yarn-integrity
57 |
58 | # dotenv environment variables file
59 | .env
60 |
61 | # next.js build output
62 | .next
63 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, Mapbox
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tableau Geocoding with Mapbox
2 |
3 |
4 |
5 | This solution allows you to use Tableau Prep for performing forward and reverse geocoding, integrated via [TabPy](https://github.com/tableau/TabPy).
6 |
7 | *You will need to have Permanent Geocoding enabled on your Mapbox account in order to use this solution. Contact [Mapbox](https://www.mapbox.com/) to get this enabled for your account.*
8 |
9 | This repository also includes instructions for launching your own TabPy instance, there is a sample script for deploying this on AWS for testing and use at scale.
10 |
11 |
12 |
13 | - [Tableau Geocoding with Mapbox](#tableau-geocoding-with-mapbox)
14 | - [Geocoding](#geocoding)
15 | - [Usage](#usage)
16 | - [Prerequisites](#prerequisites)
17 | - [Geocoding in Prep](#geocoding-in-prep)
18 | - [Forward Geocoding Requirements](#forward-geocoding-requirements)
19 | - [Reverse Geocoding Requirements](#reverse-geocoding-requirements)
20 | - [Customization](#customization)
21 | - [Geocoding Customizations](#geocoding-customizations)
22 | - [Build and Connect](#build-and-connect)
23 | - [Customization Example](#customization-example)
24 | - [Rate Limiting](#rate-limiting)
25 | - [Production](#production)
26 |
27 |
28 |
29 | ## Geocoding
30 |
31 | Tableau Desktop and Prep already geocode for a specific set of geographies (known as Geographic Roles).
32 |
33 | 
34 |
35 | There are times users require either more granular data, or additional context about a location-related data point. You can enrich your data through geocoding:
36 |
37 | - Forward: Query with text and receive a coordinate pair
38 | - Reverse: Query with a coordinate pair and receive a location.
39 |
40 | The Mapbox Geocoding API provides global scale and data coverage. It is also available to all users (no Enterprise Agreement required). For more information, consult the [Mapbox pricing page](https://www.mapbox.com/pricing/#geocode).
41 |
42 | ## Usage
43 |
44 | To simplify setup and deployment, this solution wraps [TabPy](https://github.com/tableau/TabPy) in Docker. This enables cross-platform usage with minimal setup, while also reducing the amount of code a Tableau Desktop user must write.
45 |
46 | The architecture is displayed below
47 |
48 | 
49 |
50 | ### Prerequisites
51 |
52 | You need to have Docker installed on your platform: [Mac](https://docs.docker.com/docker-for-mac/install/) / [Windows](https://docs.docker.com/docker-for-windows/)
53 |
54 | ### Geocoding in Prep
55 |
56 | To get started run the following command
57 |
58 | ```bash
59 | docker run -d -p 80:80 mbxsolutions/tableaugeocode
60 | ```
61 |
62 | This will bring down the [fully built image from Docker Hub](https://hub.docker.com/r/mbxsolutions/tableaugeocode) and start it running on port 80.
63 |
64 | The next step is to configure your geocoding script with your Mapbox token.
65 |
66 | There are a sample scripts for forward and reverse geocoding found in this repository. You can access after cloning, or simply copy and paste from the snippet below.
67 |
68 | `prepForward.py` is for forward geocoding while `prepReverse.py` is for reverse geocoding. Before you begin, if you would like more details about the response object read the [Geocoding API documentation](https://docs.mapbox.com/api/search/#geocoding-response-object).
69 |
70 | Paste your Mapbox token to replace "token" and save.
71 |
72 | ```python
73 | # This allows your script to access to published function
74 | import sys
75 | sys.path.append('/TabPy')
76 | from geocode import geocodeForward
77 |
78 | # This is the function you reference in Tableau Prep
79 | def prepGeo(input):
80 | # YOUR TOKEN GOES HERE
81 | prepData = geocodeForward(input,"token")
82 | return prepData
83 |
84 | # This is the output schema that is passed to Tableau Prep when geocoding is complete
85 | def get_output_schema():
86 | return pd.DataFrame(
87 | {
88 | "InitialQuery": prep_string(),
89 | "ReturnAddress": prep_string(),
90 | "Accuracy": prep_string(),
91 | "Relevance": prep_decimal(),
92 | "Longitude": prep_decimal(),
93 | "Latitude": prep_decimal(),
94 | }
95 | )
96 | ```
97 |
98 | After configuring your Python script, it is time to connect Tableau Prep.
99 |
100 | - Server: localhost
101 | - Port: 80
102 | - File Name: prepForward.py (if that is what you called it)
103 | - Function Name: prepGeo
104 |
105 | This configuation configuration should look like this:
106 |
107 | 
108 |
109 | Once input, your geocoding process will complete and return new data to your flow.
110 |
111 | ### Forward Geocoding Requirements
112 |
113 | Your data must contain a column titled `Address`. For the most accurate geocode, you should include as much geographic information (City, State, ZIP) in your Address.
114 |
115 | ### Reverse Geocoding Requirements
116 |
117 | Your data contain two columns, `Longitude` and `Latitude`. These columns can be of type float or string.
118 |
119 | ## Customization
120 |
121 | If you wish to alter the scripts or Docker image in any way, you can use the files found in `src`.
122 |
123 | ### Geocoding Customizations
124 |
125 | The forward and reverse scripts default to a global search and a limit of one. This means that the geocoder will find the *optimal single result* for your query.
126 |
127 | If you would like to refine your results, the Mapbox Geocoding API supports the following set of options. Edits to the default geocoding can be made at `src/geocode.py`. After edits, you must [rebuild the image](#build-and-connect) to validate.
128 |
129 | | Type | Filter | Description |
130 | |---------------------|-------------------|------------------------------------------------------------------------|
131 | | Forward and Reverse | Country Filtering | Specify country or list of countries to restrict results |
132 | | Forward and Reverse | Limit | Upper bound on number of results (max 10, default 1) |
133 | | Reverse | Type | Specify the type of result to return (country, postcode, address, etc) |
134 | | Forward | Bbox | Include only results that are within a given bounding box |
135 | | Forward | Proximity | Bias results that are closer to a given location |
136 |
137 | For more details on these options, consult the [Geocoding API documentation](https://docs.mapbox.com/api/search/).
138 |
139 | ### Build and Connect
140 |
141 | Once you have made your updates, run the following commands from within `src`.
142 |
143 | ```bash
144 | docker build --tag tabpy .
145 | docker run -d -p 80:80 tabpy
146 | ```
147 |
148 | - docker build --tag tabpy: Build and tag your image as `tabpy` for running
149 | - docker run -d -p 80:80 tabpy: Run the imaged `tabpy` in detached (`d`) mode, mapping the container port 80 to localhost 80 (`p 80:80`).
150 |
151 | This will build and run the TabPy instance on your machine. To confirm that the image is running, either run `curl localhost/info` from your terminal or:
152 |
153 | ```bash
154 | docker ps
155 | ```
156 |
157 | 
158 |
159 | Once you have confirmed that your image is running, open Tableau Prep 2019.3 (and above) and add a [Script node](https://help.tableau.com/current/prep/en-us/prep_scripts_TabPy.htm#add-a-script-to-your-flow).
160 |
161 | This will connect Prep to TabPy as indicated above.
162 |
163 | ### Customization Example
164 |
165 | For reverse geocoding, if you wish to filter to specific countries, you will either need to include a column in your data called "countries" (or similar) or hard code it into your script. All edits would need to be made to the [core geocoding script](src/geocode.py).
166 |
167 | **Column-based approach**
168 |
169 | ```python
170 | def geocodeReverse(input,token):
171 | url = "https://api.mapbox.com/geocoding/v5/mapbox.places-permanent/%s.json?access_token=%s&types=address&country=%s&limit=1"
172 | access_token = token
173 | input['coords'] = input['longitude'].map(str)+","+input['latitude'].map(str)
174 | coordinates = list(input["coords"])
175 | countries = list(input["countries"])
176 | urls = [url % (quote(list_item), access_token,countries) for list_item in coordinates]
177 | ```
178 |
179 | **Hard code approach**
180 |
181 | ```python
182 | def geocodeReverse(input,token):
183 | url = "https://api.mapbox.com/geocoding/v5/mapbox.places-permanent/%s.json?access_token=%s&types=address&country=%s&limit=1"
184 | access_token = token
185 | input['coords'] = input['longitude'].map(str)+","+input['latitude'].map(str)
186 | coordinates = list(input["coords"])
187 | countries = 'us'
188 | urls = [url % (quote(list_item), access_token,countries) for list_item in coordinates]
189 | ```
190 |
191 | ### Rate Limiting
192 |
193 | The default rate limit for Geocoding API requsts is 600/minute. The included [Python scripts](src/geocode.py) enforce this limit with the [ratelimit](https://github.com/tomasbasham/ratelimit) package. If you exceed your rate limit, your results will fail. If you would like to know more about rate limiting, read the [Mapbox API documentation](https://docs.mapbox.com/api/#rate-limits).
194 |
195 | ```python
196 | # This is the rate limit period in seconds
197 | # Mapbox rate limits on a per-minute basis, with geocoding set at 600 requests per minute
198 | RATE_LIMIT = 600
199 | LIMIT_DURATION = 60
200 |
201 | # Set up rate limiting for API calls
202 | # If rate limit is exceeded, sleep the thread and wait until the LIMIT_DURATION has lapsed
203 | # If you request an increased rate limit, update RATE_LIMIT above
204 | @sleep_and_retry
205 | @limits(calls=RATE_LIMIT, period=LIMIT_DURATION)
206 | def req(url):
207 | try:
208 | with urlopen(url, context=ctx) as conn:
209 | return conn.read()
210 | except error.HTTPError as e:
211 | print(url)
212 | ```
213 |
214 | `RATE_LIMIT` is the number of requests. `LIMIT_DURATION` is the amount of time that `RATE_LIMIT` is measured against.
215 |
216 | Our estimate is that a single thread will approach `1200` records per minute, meaning that any `RATE_LIMIT` above that value will be redundant as the script cannot process that much data. You can achieve better performance with threading. This is achieved in the following line of code:
217 |
218 | ```python
219 | with ThreadPoolExecutor(max_workers=1) as executor:
220 | ```
221 |
222 | The parameter `max_workers=1` is how to control scale. So if you have a `RATE_LIMIT` of `2400`, you can set `max_workers` to 2 split the work across two parallel threads leading to roughly half the time to completion. Parallelism will scale linearly, but there are diminishing returns as thread values increase.
223 |
224 | ## Production
225 |
226 | If you will need geocoding at a scale beyond a single machine - you can deploy this solution into production. We provide you with a script to do this in `src/deploy`.
227 |
228 | ```bash
229 | cd src/deploy
230 | npm ci
231 | pulumi up -y
232 | ```
233 |
234 | We use [Pulumi](https://www.pulumi.com/docs/index.html) to deploy our version of TabPy into AWS and wrap it behind a load balancer. This will give you a stable URL for sharing in your organization, and the ability to scale it up and down based on load.
235 |
236 | When deploying, Pulumi will build from the Dockerfile included in this repository, not the published one.
237 |
238 | **WARNING**: Mapbox rate limits are applied at the token level, so if you have a rate limit of 600 and deploy 4 instances of TabPy, you could quickly go over your limit without knowing.
239 |
--------------------------------------------------------------------------------
/assets/arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mapbox/tableauGeocode/67f35dc8ea36e4a59a98cf43fae262e47b6e2ba3/assets/arch.png
--------------------------------------------------------------------------------
/assets/dockerps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mapbox/tableauGeocode/67f35dc8ea36e4a59a98cf43fae262e47b6e2ba3/assets/dockerps.png
--------------------------------------------------------------------------------
/assets/prepconfig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mapbox/tableauGeocode/67f35dc8ea36e4a59a98cf43fae262e47b6e2ba3/assets/prepconfig.png
--------------------------------------------------------------------------------
/assets/pyconfig.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mapbox/tableauGeocode/67f35dc8ea36e4a59a98cf43fae262e47b6e2ba3/assets/pyconfig.png
--------------------------------------------------------------------------------
/assets/roles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mapbox/tableauGeocode/67f35dc8ea36e4a59a98cf43fae262e47b6e2ba3/assets/roles.png
--------------------------------------------------------------------------------
/assets/video-thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mapbox/tableauGeocode/67f35dc8ea36e4a59a98cf43fae262e47b6e2ba3/assets/video-thumbnail.png
--------------------------------------------------------------------------------
/src/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile for TabPy
2 | # Setup
3 | FROM centos:7.6.1810
4 | RUN yum -y update
5 | RUN yum install git yum-utils groupinstall development which https://centos7.iuscommunity.org/ius-release.rpm -y
6 | RUN yum install python36u python36u-pip tmux -y
7 |
8 | # Python dependencies
9 | RUN python3 -m pip install --upgrade pip
10 | RUN python3 -m pip install pandas tabpy ratelimit
11 |
12 | # Where everything lives
13 | WORKDIR /TabPy
14 | COPY ./geocode.py ./geocode.py
15 | COPY ./startup.sh ./startup.sh
16 | COPY ./tabpy.conf ./tabpy.conf
17 |
18 | # Expose port 9004
19 | EXPOSE 80
20 | # Start TabPy
21 | CMD [ "./startup.sh" ]
--------------------------------------------------------------------------------
/src/deploy/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 |
--------------------------------------------------------------------------------
/src/deploy/Pulumi.yaml:
--------------------------------------------------------------------------------
1 | name: deployGeocode
2 | runtime: nodejs
3 | description: A small script to deploy TabPy with Geocoding to AWS
4 |
--------------------------------------------------------------------------------
/src/deploy/README.md:
--------------------------------------------------------------------------------
1 | # Deploying TabPy to AWS
2 |
3 | The code in this sub-folder will deploy TabPy to Amazon Web Services Elastic Container Service (ECS). It will also place a load balancer in front of your instances to provide users with a single URL for use.
4 |
5 | ## Install
6 |
7 | You must have Docker and Pulumi installed on your machine in order to use this script. You must also have an [AWS account](https://aws.amazon.com/getting-started/) with credentials available to your environment.
8 |
9 | To install:
10 |
11 | Mac
12 |
13 | ```bash
14 | brew install pulumi
15 | ```
16 |
17 | PC
18 |
19 | ```bash
20 | @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; iex ((New-Object System.Net.WebClient).DownloadString('https://get.pulumi.com/install.ps1'))" && SET "PATH=%PATH%;%USERPROFILE%\.pulumi\bin"
21 | ```
22 |
23 | To install Docker, follow their [Getting Started](https://docs.docker.com/install/) guide.
24 |
25 | ## Deploy
26 |
27 | After cloning the repository
28 |
29 | ```bash
30 | cd src/deploy
31 | npm ci
32 | pulumi config set aws:region
33 | pulumi up -y
34 | ```
35 |
36 | This will begin a Docker build and deployment to AWS. At the end of the process you will be shown a URL. That is the URL you will use in Tableau for geocoding (or any other TabPy needs).
37 |
38 | ## Details
39 |
40 | This script works as follows
41 |
42 | 1. Create AWS Fargate cluster. This is a cluster to place your TabPy container. Fargate manages scale and size for you, so you do not have to manage core resources.
43 | 2. Create Load Balancer. Your cluster will have between 1-N containers running on it, and the ALB will route requests to those containers as needed.
44 | 3. TabPy Service. The normal TabPy application is wrapped in Docker to make it simpler to deploy. It is configured to use port 80 for all communications. Fargate allows you to run as many replicas of your container as needed for scale.
45 |
46 | ```javascript
47 | const appService = new awsx.ecs.FargateService("tabpy-service", {
48 | cluster,
49 | taskDefinitionArgs: {
50 | container: {
51 | image: img,
52 | cpu: 1024 /*100% of 1024*/,
53 | memory: 2048 /*MB*/,
54 | portMappings: [web]
55 | }
56 | },
57 | ///This is where you define how many replicas you want running at all times.
58 | desiredCount: 5
59 | });
60 | ```
61 |
62 | ## Teardown
63 |
64 | If you wish to remove the stack, run `pulumi destroy -y`.
65 |
--------------------------------------------------------------------------------
/src/deploy/index.js:
--------------------------------------------------------------------------------
1 | const awsx = require("@pulumi/awsx");
2 | // Step 1: Create an ECS Fargate cluster.
3 | const cluster = new awsx.ecs.Cluster("cluster");
4 | // Step 2: Define the Networking for our service.
5 | const alb = new awsx.lb.ApplicationLoadBalancer("tabPyLb", {
6 | external: true,
7 | securityGroup: cluster.securityGroup
8 | });
9 | const web = alb.createListener("web", { port: 80, external: true });
10 | // Step 3: Build and publish a Docker image to a private ECR registry.
11 | const img = awsx.ecs.Image.fromPath("tabpy", "../");
12 | // Step 4: Create a Fargate service task that can scale out.
13 | const appService = new awsx.ecs.FargateService("tabpy-service", {
14 | cluster,
15 | taskDefinitionArgs: {
16 | container: {
17 | image: img,
18 | cpu: 1024 /*100% of 1024*/,
19 | memory: 2048 /*MB*/,
20 | portMappings: [web]
21 | }
22 | },
23 | desiredCount: 5
24 | });
25 | // Step 5: Export the Internet address for the service.
26 | exports.url = web.endpoint.hostname;
27 |
--------------------------------------------------------------------------------
/src/deploy/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-javascript",
3 | "requires": true,
4 | "lockfileVersion": 1,
5 | "dependencies": {
6 | "@protobufjs/aspromise": {
7 | "version": "1.1.2",
8 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
9 | "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78="
10 | },
11 | "@protobufjs/base64": {
12 | "version": "1.1.2",
13 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
14 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
15 | },
16 | "@protobufjs/codegen": {
17 | "version": "2.0.4",
18 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
19 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
20 | },
21 | "@protobufjs/eventemitter": {
22 | "version": "1.1.0",
23 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
24 | "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A="
25 | },
26 | "@protobufjs/fetch": {
27 | "version": "1.1.0",
28 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
29 | "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
30 | "requires": {
31 | "@protobufjs/aspromise": "^1.1.1",
32 | "@protobufjs/inquire": "^1.1.0"
33 | }
34 | },
35 | "@protobufjs/float": {
36 | "version": "1.0.2",
37 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
38 | "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E="
39 | },
40 | "@protobufjs/inquire": {
41 | "version": "1.1.0",
42 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
43 | "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik="
44 | },
45 | "@protobufjs/path": {
46 | "version": "1.1.2",
47 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
48 | "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0="
49 | },
50 | "@protobufjs/pool": {
51 | "version": "1.1.0",
52 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
53 | "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q="
54 | },
55 | "@protobufjs/utf8": {
56 | "version": "1.1.0",
57 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
58 | "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
59 | },
60 | "@pulumi/aws": {
61 | "version": "1.7.0",
62 | "resolved": "https://registry.npmjs.org/@pulumi/aws/-/aws-1.7.0.tgz",
63 | "integrity": "sha512-mDgGsQeS8YxP6RFmbgtCNdM+dyq/aFzEz54NAyICCW41nCzyx5R7Lpp3kPGKMqgpmDRkdfq60/75h8+SGJGnUQ==",
64 | "requires": {
65 | "@pulumi/pulumi": "^1.0.0",
66 | "aws-sdk": "^2.0.0",
67 | "builtin-modules": "3.0.0",
68 | "mime": "^2.0.0",
69 | "read-package-tree": "^5.2.1",
70 | "resolve": "^1.7.1"
71 | }
72 | },
73 | "@pulumi/awsx": {
74 | "version": "0.18.12",
75 | "resolved": "https://registry.npmjs.org/@pulumi/awsx/-/awsx-0.18.12.tgz",
76 | "integrity": "sha512-Do45Ort9d0RryJSADRtUtQqaF+4cbcLkeDA+bCwXdQBZaJqbFFVqoJIQHWYhU00P6CVPU0linV+ADqsUoCy3FQ==",
77 | "requires": {
78 | "@pulumi/aws": "^1.0.0",
79 | "@pulumi/docker": "^0.17.3",
80 | "@pulumi/pulumi": "^1.0.0",
81 | "@types/aws-lambda": "^8.10.23",
82 | "deasync": "^0.1.15",
83 | "mime": "^2.0.0"
84 | }
85 | },
86 | "@pulumi/docker": {
87 | "version": "0.17.4",
88 | "resolved": "https://registry.npmjs.org/@pulumi/docker/-/docker-0.17.4.tgz",
89 | "integrity": "sha512-uGnt9VEGXxCFTpIkCrULP2XTbSEJMH3vQrOq9Q+ATTnIxswlG0z/LkpuktEB6YmvnQqEUtTPPEM+p/RmLkqLrA==",
90 | "requires": {
91 | "@pulumi/pulumi": "^1.0.0",
92 | "semver": "^5.4.0"
93 | },
94 | "dependencies": {
95 | "semver": {
96 | "version": "5.7.1",
97 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
98 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
99 | }
100 | }
101 | },
102 | "@pulumi/pulumi": {
103 | "version": "1.4.0",
104 | "resolved": "https://registry.npmjs.org/@pulumi/pulumi/-/pulumi-1.4.0.tgz",
105 | "integrity": "sha512-BGFIwjMCaJCXLwZDB4PoCtkTc40cGvL99pgNg1KRmr6GccYw98nA70vZzqktYsyFuZOX6CzzFRo8Tgigx8+awQ==",
106 | "requires": {
107 | "@pulumi/query": "^0.3.0",
108 | "deasync": "^0.1.15",
109 | "google-protobuf": "^3.5.0",
110 | "grpc": "1.21.1",
111 | "minimist": "^1.2.0",
112 | "normalize-package-data": "^2.4.0",
113 | "protobufjs": "^6.8.6",
114 | "read-package-tree": "^5.3.1",
115 | "require-from-string": "^2.0.1",
116 | "semver": "^6.1.0",
117 | "source-map-support": "^0.4.16",
118 | "ts-node": "^7.0.0",
119 | "typescript": "^3.0.0",
120 | "upath": "^1.1.0"
121 | }
122 | },
123 | "@pulumi/query": {
124 | "version": "0.3.0",
125 | "resolved": "https://registry.npmjs.org/@pulumi/query/-/query-0.3.0.tgz",
126 | "integrity": "sha512-xfo+yLRM2zVjVEA4p23IjQWzyWl1ZhWOGobsBqRpIarzLvwNH/RAGaoehdxlhx4X92302DrpdIFgTICMN4P38w=="
127 | },
128 | "@types/aws-lambda": {
129 | "version": "8.10.34",
130 | "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.34.tgz",
131 | "integrity": "sha512-ewsBa0SjQYywUpDwck+4n89T0pXcRtIfIXi0aK/3qIQJNBoGvX7AKqFya9FWKdoG7ZcK3Iarl4QjlJiAW7jgPg=="
132 | },
133 | "@types/long": {
134 | "version": "4.0.0",
135 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz",
136 | "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q=="
137 | },
138 | "@types/node": {
139 | "version": "10.17.0",
140 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.0.tgz",
141 | "integrity": "sha512-wuJwN2KV4tIRz1bu9vq5kSPasJ8IsEjZaP1ZR7KlmdUZvGF/rXy8DmXOVwUD0kAtvtJ7aqMKPqUXC0NUTDbrDg=="
142 | },
143 | "ansi-regex": {
144 | "version": "2.1.1",
145 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
146 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
147 | },
148 | "arrify": {
149 | "version": "1.0.1",
150 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
151 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
152 | },
153 | "asap": {
154 | "version": "2.0.6",
155 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
156 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
157 | },
158 | "ascli": {
159 | "version": "1.0.1",
160 | "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz",
161 | "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=",
162 | "requires": {
163 | "colour": "~0.7.1",
164 | "optjs": "~3.2.2"
165 | }
166 | },
167 | "aws-sdk": {
168 | "version": "2.558.0",
169 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.558.0.tgz",
170 | "integrity": "sha512-L1cqcOLuL4FG93JKQIh/5u/ks9zj3ErahzfAb7vjuSLVZgEXx8mNgdiyIgcWj5VDOBEa6ygzwV1f72rDig359A==",
171 | "requires": {
172 | "buffer": "4.9.1",
173 | "events": "1.1.1",
174 | "ieee754": "1.1.13",
175 | "jmespath": "0.15.0",
176 | "querystring": "0.2.0",
177 | "sax": "1.2.1",
178 | "url": "0.10.3",
179 | "uuid": "3.3.2",
180 | "xml2js": "0.4.19"
181 | }
182 | },
183 | "balanced-match": {
184 | "version": "1.0.0",
185 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
186 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
187 | },
188 | "base64-js": {
189 | "version": "1.3.1",
190 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
191 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
192 | },
193 | "bindings": {
194 | "version": "1.2.1",
195 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
196 | "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
197 | },
198 | "brace-expansion": {
199 | "version": "1.1.11",
200 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
201 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
202 | "requires": {
203 | "balanced-match": "^1.0.0",
204 | "concat-map": "0.0.1"
205 | }
206 | },
207 | "buffer": {
208 | "version": "4.9.1",
209 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
210 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
211 | "requires": {
212 | "base64-js": "^1.0.2",
213 | "ieee754": "^1.1.4",
214 | "isarray": "^1.0.0"
215 | }
216 | },
217 | "buffer-from": {
218 | "version": "1.1.1",
219 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
220 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
221 | },
222 | "builtin-modules": {
223 | "version": "3.0.0",
224 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz",
225 | "integrity": "sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg=="
226 | },
227 | "bytebuffer": {
228 | "version": "5.0.1",
229 | "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz",
230 | "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=",
231 | "requires": {
232 | "long": "~3"
233 | }
234 | },
235 | "camelcase": {
236 | "version": "2.1.1",
237 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
238 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
239 | },
240 | "cliui": {
241 | "version": "3.2.0",
242 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
243 | "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
244 | "requires": {
245 | "string-width": "^1.0.1",
246 | "strip-ansi": "^3.0.1",
247 | "wrap-ansi": "^2.0.0"
248 | }
249 | },
250 | "code-point-at": {
251 | "version": "1.1.0",
252 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
253 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
254 | },
255 | "colour": {
256 | "version": "0.7.1",
257 | "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz",
258 | "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g="
259 | },
260 | "concat-map": {
261 | "version": "0.0.1",
262 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
263 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
264 | },
265 | "deasync": {
266 | "version": "0.1.15",
267 | "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.15.tgz",
268 | "integrity": "sha512-pxMaCYu8cQIbGkA4Y1R0PLSooPIpH1WgFBLeJ+zLxQgHfkZG86ViJSmZmONSjZJ/R3NjwkMcIWZAzpLB2G9/CA==",
269 | "requires": {
270 | "bindings": "~1.2.1",
271 | "node-addon-api": "^1.6.0"
272 | }
273 | },
274 | "debuglog": {
275 | "version": "1.0.1",
276 | "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
277 | "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI="
278 | },
279 | "decamelize": {
280 | "version": "1.2.0",
281 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
282 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
283 | },
284 | "define-properties": {
285 | "version": "1.1.3",
286 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
287 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
288 | "requires": {
289 | "object-keys": "^1.0.12"
290 | }
291 | },
292 | "dezalgo": {
293 | "version": "1.0.3",
294 | "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
295 | "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
296 | "requires": {
297 | "asap": "^2.0.0",
298 | "wrappy": "1"
299 | }
300 | },
301 | "diff": {
302 | "version": "3.5.0",
303 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
304 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
305 | },
306 | "es-abstract": {
307 | "version": "1.16.0",
308 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz",
309 | "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==",
310 | "requires": {
311 | "es-to-primitive": "^1.2.0",
312 | "function-bind": "^1.1.1",
313 | "has": "^1.0.3",
314 | "has-symbols": "^1.0.0",
315 | "is-callable": "^1.1.4",
316 | "is-regex": "^1.0.4",
317 | "object-inspect": "^1.6.0",
318 | "object-keys": "^1.1.1",
319 | "string.prototype.trimleft": "^2.1.0",
320 | "string.prototype.trimright": "^2.1.0"
321 | }
322 | },
323 | "es-to-primitive": {
324 | "version": "1.2.0",
325 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
326 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
327 | "requires": {
328 | "is-callable": "^1.1.4",
329 | "is-date-object": "^1.0.1",
330 | "is-symbol": "^1.0.2"
331 | }
332 | },
333 | "events": {
334 | "version": "1.1.1",
335 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
336 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
337 | },
338 | "fs.realpath": {
339 | "version": "1.0.0",
340 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
341 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
342 | },
343 | "function-bind": {
344 | "version": "1.1.1",
345 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
346 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
347 | },
348 | "glob": {
349 | "version": "7.1.5",
350 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
351 | "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==",
352 | "requires": {
353 | "fs.realpath": "^1.0.0",
354 | "inflight": "^1.0.4",
355 | "inherits": "2",
356 | "minimatch": "^3.0.4",
357 | "once": "^1.3.0",
358 | "path-is-absolute": "^1.0.0"
359 | }
360 | },
361 | "google-protobuf": {
362 | "version": "3.10.0",
363 | "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.10.0.tgz",
364 | "integrity": "sha512-d0cMO8TJ6xtB/WrVHCv5U81L2ulX+aCD58IljyAN6mHwdHHJ2jbcauX5glvivi3s3hx7EYEo7eUA9WftzamMnw=="
365 | },
366 | "graceful-fs": {
367 | "version": "4.2.3",
368 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
369 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
370 | },
371 | "grpc": {
372 | "version": "1.21.1",
373 | "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.21.1.tgz",
374 | "integrity": "sha512-PFsZQazf62nP05a0xm23mlImMuw5oVlqF/0zakmsdqJgvbABe+d6VThY2PfhqJmWEL/FhQ6QNYsxS5EAM6++7g==",
375 | "requires": {
376 | "lodash.camelcase": "^4.3.0",
377 | "lodash.clone": "^4.5.0",
378 | "nan": "^2.13.2",
379 | "node-pre-gyp": "^0.13.0",
380 | "protobufjs": "^5.0.3"
381 | },
382 | "dependencies": {
383 | "abbrev": {
384 | "version": "1.1.1",
385 | "bundled": true
386 | },
387 | "ansi-regex": {
388 | "version": "2.1.1",
389 | "bundled": true
390 | },
391 | "aproba": {
392 | "version": "1.2.0",
393 | "bundled": true
394 | },
395 | "are-we-there-yet": {
396 | "version": "1.1.5",
397 | "bundled": true,
398 | "requires": {
399 | "delegates": "^1.0.0",
400 | "readable-stream": "^2.0.6"
401 | }
402 | },
403 | "balanced-match": {
404 | "version": "1.0.0",
405 | "bundled": true
406 | },
407 | "brace-expansion": {
408 | "version": "1.1.11",
409 | "bundled": true,
410 | "requires": {
411 | "balanced-match": "^1.0.0",
412 | "concat-map": "0.0.1"
413 | }
414 | },
415 | "chownr": {
416 | "version": "1.1.1",
417 | "bundled": true
418 | },
419 | "code-point-at": {
420 | "version": "1.1.0",
421 | "bundled": true
422 | },
423 | "concat-map": {
424 | "version": "0.0.1",
425 | "bundled": true
426 | },
427 | "console-control-strings": {
428 | "version": "1.1.0",
429 | "bundled": true
430 | },
431 | "core-util-is": {
432 | "version": "1.0.2",
433 | "bundled": true
434 | },
435 | "deep-extend": {
436 | "version": "0.6.0",
437 | "bundled": true
438 | },
439 | "delegates": {
440 | "version": "1.0.0",
441 | "bundled": true
442 | },
443 | "detect-libc": {
444 | "version": "1.0.3",
445 | "bundled": true
446 | },
447 | "fs-minipass": {
448 | "version": "1.2.5",
449 | "bundled": true,
450 | "requires": {
451 | "minipass": "^2.2.1"
452 | }
453 | },
454 | "fs.realpath": {
455 | "version": "1.0.0",
456 | "bundled": true
457 | },
458 | "gauge": {
459 | "version": "2.7.4",
460 | "bundled": true,
461 | "requires": {
462 | "aproba": "^1.0.3",
463 | "console-control-strings": "^1.0.0",
464 | "has-unicode": "^2.0.0",
465 | "object-assign": "^4.1.0",
466 | "signal-exit": "^3.0.0",
467 | "string-width": "^1.0.1",
468 | "strip-ansi": "^3.0.1",
469 | "wide-align": "^1.1.0"
470 | }
471 | },
472 | "has-unicode": {
473 | "version": "2.0.1",
474 | "bundled": true
475 | },
476 | "iconv-lite": {
477 | "version": "0.4.23",
478 | "bundled": true,
479 | "requires": {
480 | "safer-buffer": ">= 2.1.2 < 3"
481 | }
482 | },
483 | "ignore-walk": {
484 | "version": "3.0.1",
485 | "bundled": true,
486 | "requires": {
487 | "minimatch": "^3.0.4"
488 | }
489 | },
490 | "inflight": {
491 | "version": "1.0.6",
492 | "bundled": true,
493 | "requires": {
494 | "once": "^1.3.0",
495 | "wrappy": "1"
496 | }
497 | },
498 | "inherits": {
499 | "version": "2.0.3",
500 | "bundled": true
501 | },
502 | "ini": {
503 | "version": "1.3.5",
504 | "bundled": true
505 | },
506 | "is-fullwidth-code-point": {
507 | "version": "1.0.0",
508 | "bundled": true,
509 | "requires": {
510 | "number-is-nan": "^1.0.0"
511 | }
512 | },
513 | "isarray": {
514 | "version": "1.0.0",
515 | "bundled": true
516 | },
517 | "minimatch": {
518 | "version": "3.0.4",
519 | "bundled": true,
520 | "requires": {
521 | "brace-expansion": "^1.1.7"
522 | }
523 | },
524 | "minimist": {
525 | "version": "1.2.0",
526 | "bundled": true
527 | },
528 | "minipass": {
529 | "version": "2.3.5",
530 | "bundled": true,
531 | "requires": {
532 | "safe-buffer": "^5.1.2",
533 | "yallist": "^3.0.0"
534 | }
535 | },
536 | "minizlib": {
537 | "version": "1.2.1",
538 | "bundled": true,
539 | "requires": {
540 | "minipass": "^2.2.1"
541 | }
542 | },
543 | "mkdirp": {
544 | "version": "0.5.1",
545 | "bundled": true,
546 | "requires": {
547 | "minimist": "0.0.8"
548 | },
549 | "dependencies": {
550 | "minimist": {
551 | "version": "0.0.8",
552 | "bundled": true
553 | }
554 | }
555 | },
556 | "needle": {
557 | "version": "2.3.1",
558 | "bundled": true,
559 | "requires": {
560 | "debug": "^4.1.0",
561 | "iconv-lite": "^0.4.4",
562 | "sax": "^1.2.4"
563 | },
564 | "dependencies": {
565 | "debug": {
566 | "version": "4.1.1",
567 | "bundled": true,
568 | "requires": {
569 | "ms": "^2.1.1"
570 | }
571 | },
572 | "ms": {
573 | "version": "2.1.1",
574 | "bundled": true
575 | }
576 | }
577 | },
578 | "node-pre-gyp": {
579 | "version": "0.13.0",
580 | "bundled": true,
581 | "requires": {
582 | "detect-libc": "^1.0.2",
583 | "mkdirp": "^0.5.1",
584 | "needle": "^2.2.1",
585 | "nopt": "^4.0.1",
586 | "npm-packlist": "^1.1.6",
587 | "npmlog": "^4.0.2",
588 | "rc": "^1.2.7",
589 | "rimraf": "^2.6.1",
590 | "semver": "^5.3.0",
591 | "tar": "^4"
592 | }
593 | },
594 | "nopt": {
595 | "version": "4.0.1",
596 | "bundled": true,
597 | "requires": {
598 | "abbrev": "1",
599 | "osenv": "^0.1.4"
600 | }
601 | },
602 | "npm-bundled": {
603 | "version": "1.0.6",
604 | "bundled": true
605 | },
606 | "npm-packlist": {
607 | "version": "1.4.1",
608 | "bundled": true,
609 | "requires": {
610 | "ignore-walk": "^3.0.1",
611 | "npm-bundled": "^1.0.1"
612 | }
613 | },
614 | "npmlog": {
615 | "version": "4.1.2",
616 | "bundled": true,
617 | "requires": {
618 | "are-we-there-yet": "~1.1.2",
619 | "console-control-strings": "~1.1.0",
620 | "gauge": "~2.7.3",
621 | "set-blocking": "~2.0.0"
622 | }
623 | },
624 | "number-is-nan": {
625 | "version": "1.0.1",
626 | "bundled": true
627 | },
628 | "object-assign": {
629 | "version": "4.1.1",
630 | "bundled": true
631 | },
632 | "once": {
633 | "version": "1.4.0",
634 | "bundled": true,
635 | "requires": {
636 | "wrappy": "1"
637 | }
638 | },
639 | "os-homedir": {
640 | "version": "1.0.2",
641 | "bundled": true
642 | },
643 | "os-tmpdir": {
644 | "version": "1.0.2",
645 | "bundled": true
646 | },
647 | "osenv": {
648 | "version": "0.1.5",
649 | "bundled": true,
650 | "requires": {
651 | "os-homedir": "^1.0.0",
652 | "os-tmpdir": "^1.0.0"
653 | }
654 | },
655 | "path-is-absolute": {
656 | "version": "1.0.1",
657 | "bundled": true
658 | },
659 | "process-nextick-args": {
660 | "version": "2.0.0",
661 | "bundled": true
662 | },
663 | "protobufjs": {
664 | "version": "5.0.3",
665 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz",
666 | "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==",
667 | "requires": {
668 | "ascli": "~1",
669 | "bytebuffer": "~5",
670 | "glob": "^7.0.5",
671 | "yargs": "^3.10.0"
672 | }
673 | },
674 | "rc": {
675 | "version": "1.2.8",
676 | "bundled": true,
677 | "requires": {
678 | "deep-extend": "^0.6.0",
679 | "ini": "~1.3.0",
680 | "minimist": "^1.2.0",
681 | "strip-json-comments": "~2.0.1"
682 | }
683 | },
684 | "readable-stream": {
685 | "version": "2.3.6",
686 | "bundled": true,
687 | "requires": {
688 | "core-util-is": "~1.0.0",
689 | "inherits": "~2.0.3",
690 | "isarray": "~1.0.0",
691 | "process-nextick-args": "~2.0.0",
692 | "safe-buffer": "~5.1.1",
693 | "string_decoder": "~1.1.1",
694 | "util-deprecate": "~1.0.1"
695 | }
696 | },
697 | "rimraf": {
698 | "version": "2.6.3",
699 | "bundled": true,
700 | "requires": {
701 | "glob": "^7.1.3"
702 | },
703 | "dependencies": {
704 | "glob": {
705 | "version": "7.1.4",
706 | "bundled": true,
707 | "requires": {
708 | "fs.realpath": "^1.0.0",
709 | "inflight": "^1.0.4",
710 | "inherits": "2",
711 | "minimatch": "^3.0.4",
712 | "once": "^1.3.0",
713 | "path-is-absolute": "^1.0.0"
714 | }
715 | }
716 | }
717 | },
718 | "safe-buffer": {
719 | "version": "5.1.2",
720 | "bundled": true
721 | },
722 | "safer-buffer": {
723 | "version": "2.1.2",
724 | "bundled": true
725 | },
726 | "sax": {
727 | "version": "1.2.4",
728 | "bundled": true
729 | },
730 | "semver": {
731 | "version": "5.7.0",
732 | "bundled": true
733 | },
734 | "set-blocking": {
735 | "version": "2.0.0",
736 | "bundled": true
737 | },
738 | "signal-exit": {
739 | "version": "3.0.2",
740 | "bundled": true
741 | },
742 | "string-width": {
743 | "version": "1.0.2",
744 | "bundled": true,
745 | "requires": {
746 | "code-point-at": "^1.0.0",
747 | "is-fullwidth-code-point": "^1.0.0",
748 | "strip-ansi": "^3.0.0"
749 | }
750 | },
751 | "string_decoder": {
752 | "version": "1.1.1",
753 | "bundled": true,
754 | "requires": {
755 | "safe-buffer": "~5.1.0"
756 | }
757 | },
758 | "strip-ansi": {
759 | "version": "3.0.1",
760 | "bundled": true,
761 | "requires": {
762 | "ansi-regex": "^2.0.0"
763 | }
764 | },
765 | "strip-json-comments": {
766 | "version": "2.0.1",
767 | "bundled": true
768 | },
769 | "tar": {
770 | "version": "4.4.8",
771 | "bundled": true,
772 | "requires": {
773 | "chownr": "^1.1.1",
774 | "fs-minipass": "^1.2.5",
775 | "minipass": "^2.3.4",
776 | "minizlib": "^1.1.1",
777 | "mkdirp": "^0.5.0",
778 | "safe-buffer": "^5.1.2",
779 | "yallist": "^3.0.2"
780 | }
781 | },
782 | "util-deprecate": {
783 | "version": "1.0.2",
784 | "bundled": true
785 | },
786 | "wide-align": {
787 | "version": "1.1.3",
788 | "bundled": true,
789 | "requires": {
790 | "string-width": "^1.0.2 || 2"
791 | }
792 | },
793 | "wrappy": {
794 | "version": "1.0.2",
795 | "bundled": true
796 | },
797 | "yallist": {
798 | "version": "3.0.3",
799 | "bundled": true
800 | }
801 | }
802 | },
803 | "has": {
804 | "version": "1.0.3",
805 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
806 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
807 | "requires": {
808 | "function-bind": "^1.1.1"
809 | }
810 | },
811 | "has-symbols": {
812 | "version": "1.0.0",
813 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
814 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
815 | },
816 | "hosted-git-info": {
817 | "version": "2.8.5",
818 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
819 | "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg=="
820 | },
821 | "ieee754": {
822 | "version": "1.1.13",
823 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
824 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
825 | },
826 | "inflight": {
827 | "version": "1.0.6",
828 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
829 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
830 | "requires": {
831 | "once": "^1.3.0",
832 | "wrappy": "1"
833 | }
834 | },
835 | "inherits": {
836 | "version": "2.0.4",
837 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
838 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
839 | },
840 | "invert-kv": {
841 | "version": "1.0.0",
842 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
843 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
844 | },
845 | "is-callable": {
846 | "version": "1.1.4",
847 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
848 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
849 | },
850 | "is-date-object": {
851 | "version": "1.0.1",
852 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
853 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
854 | },
855 | "is-fullwidth-code-point": {
856 | "version": "1.0.0",
857 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
858 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
859 | "requires": {
860 | "number-is-nan": "^1.0.0"
861 | }
862 | },
863 | "is-regex": {
864 | "version": "1.0.4",
865 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
866 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
867 | "requires": {
868 | "has": "^1.0.1"
869 | }
870 | },
871 | "is-symbol": {
872 | "version": "1.0.2",
873 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
874 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
875 | "requires": {
876 | "has-symbols": "^1.0.0"
877 | }
878 | },
879 | "isarray": {
880 | "version": "1.0.0",
881 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
882 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
883 | },
884 | "jmespath": {
885 | "version": "0.15.0",
886 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
887 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
888 | },
889 | "json-parse-better-errors": {
890 | "version": "1.0.2",
891 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
892 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
893 | },
894 | "lcid": {
895 | "version": "1.0.0",
896 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
897 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
898 | "requires": {
899 | "invert-kv": "^1.0.0"
900 | }
901 | },
902 | "lodash.camelcase": {
903 | "version": "4.3.0",
904 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
905 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
906 | },
907 | "lodash.clone": {
908 | "version": "4.5.0",
909 | "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
910 | "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y="
911 | },
912 | "long": {
913 | "version": "3.2.0",
914 | "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
915 | "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s="
916 | },
917 | "make-error": {
918 | "version": "1.3.5",
919 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
920 | "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g=="
921 | },
922 | "mime": {
923 | "version": "2.4.4",
924 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
925 | "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
926 | },
927 | "minimatch": {
928 | "version": "3.0.4",
929 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
930 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
931 | "requires": {
932 | "brace-expansion": "^1.1.7"
933 | }
934 | },
935 | "minimist": {
936 | "version": "1.2.0",
937 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
938 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
939 | },
940 | "mkdirp": {
941 | "version": "0.5.1",
942 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
943 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
944 | "requires": {
945 | "minimist": "0.0.8"
946 | },
947 | "dependencies": {
948 | "minimist": {
949 | "version": "0.0.8",
950 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
951 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
952 | }
953 | }
954 | },
955 | "nan": {
956 | "version": "2.14.0",
957 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
958 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
959 | },
960 | "node-addon-api": {
961 | "version": "1.7.1",
962 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.1.tgz",
963 | "integrity": "sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ=="
964 | },
965 | "normalize-package-data": {
966 | "version": "2.5.0",
967 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
968 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
969 | "requires": {
970 | "hosted-git-info": "^2.1.4",
971 | "resolve": "^1.10.0",
972 | "semver": "2 || 3 || 4 || 5",
973 | "validate-npm-package-license": "^3.0.1"
974 | },
975 | "dependencies": {
976 | "semver": {
977 | "version": "5.7.1",
978 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
979 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
980 | }
981 | }
982 | },
983 | "number-is-nan": {
984 | "version": "1.0.1",
985 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
986 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
987 | },
988 | "object-inspect": {
989 | "version": "1.6.0",
990 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
991 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ=="
992 | },
993 | "object-keys": {
994 | "version": "1.1.1",
995 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
996 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
997 | },
998 | "object.getownpropertydescriptors": {
999 | "version": "2.0.3",
1000 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
1001 | "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
1002 | "requires": {
1003 | "define-properties": "^1.1.2",
1004 | "es-abstract": "^1.5.1"
1005 | }
1006 | },
1007 | "once": {
1008 | "version": "1.4.0",
1009 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1010 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1011 | "requires": {
1012 | "wrappy": "1"
1013 | }
1014 | },
1015 | "optjs": {
1016 | "version": "3.2.2",
1017 | "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz",
1018 | "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4="
1019 | },
1020 | "os-locale": {
1021 | "version": "1.4.0",
1022 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
1023 | "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
1024 | "requires": {
1025 | "lcid": "^1.0.0"
1026 | }
1027 | },
1028 | "path-is-absolute": {
1029 | "version": "1.0.1",
1030 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1031 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
1032 | },
1033 | "path-parse": {
1034 | "version": "1.0.6",
1035 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
1036 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
1037 | },
1038 | "protobufjs": {
1039 | "version": "6.8.8",
1040 | "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz",
1041 | "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==",
1042 | "requires": {
1043 | "@protobufjs/aspromise": "^1.1.2",
1044 | "@protobufjs/base64": "^1.1.2",
1045 | "@protobufjs/codegen": "^2.0.4",
1046 | "@protobufjs/eventemitter": "^1.1.0",
1047 | "@protobufjs/fetch": "^1.1.0",
1048 | "@protobufjs/float": "^1.0.2",
1049 | "@protobufjs/inquire": "^1.1.0",
1050 | "@protobufjs/path": "^1.1.2",
1051 | "@protobufjs/pool": "^1.1.0",
1052 | "@protobufjs/utf8": "^1.1.0",
1053 | "@types/long": "^4.0.0",
1054 | "@types/node": "^10.1.0",
1055 | "long": "^4.0.0"
1056 | },
1057 | "dependencies": {
1058 | "long": {
1059 | "version": "4.0.0",
1060 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
1061 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
1062 | }
1063 | }
1064 | },
1065 | "punycode": {
1066 | "version": "1.3.2",
1067 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
1068 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
1069 | },
1070 | "querystring": {
1071 | "version": "0.2.0",
1072 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
1073 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
1074 | },
1075 | "read-package-json": {
1076 | "version": "2.1.0",
1077 | "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.0.tgz",
1078 | "integrity": "sha512-KLhu8M1ZZNkMcrq1+0UJbR8Dii8KZUqB0Sha4mOx/bknfKI/fyrQVrG/YIt2UOtG667sD8+ee4EXMM91W9dC+A==",
1079 | "requires": {
1080 | "glob": "^7.1.1",
1081 | "graceful-fs": "^4.1.2",
1082 | "json-parse-better-errors": "^1.0.1",
1083 | "normalize-package-data": "^2.0.0",
1084 | "slash": "^1.0.0"
1085 | }
1086 | },
1087 | "read-package-tree": {
1088 | "version": "5.3.1",
1089 | "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz",
1090 | "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
1091 | "requires": {
1092 | "read-package-json": "^2.0.0",
1093 | "readdir-scoped-modules": "^1.0.0",
1094 | "util-promisify": "^2.1.0"
1095 | }
1096 | },
1097 | "readdir-scoped-modules": {
1098 | "version": "1.1.0",
1099 | "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
1100 | "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
1101 | "requires": {
1102 | "debuglog": "^1.0.1",
1103 | "dezalgo": "^1.0.0",
1104 | "graceful-fs": "^4.1.2",
1105 | "once": "^1.3.0"
1106 | }
1107 | },
1108 | "require-from-string": {
1109 | "version": "2.0.2",
1110 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
1111 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
1112 | },
1113 | "resolve": {
1114 | "version": "1.12.0",
1115 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
1116 | "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
1117 | "requires": {
1118 | "path-parse": "^1.0.6"
1119 | }
1120 | },
1121 | "sax": {
1122 | "version": "1.2.1",
1123 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
1124 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
1125 | },
1126 | "semver": {
1127 | "version": "6.3.0",
1128 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
1129 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
1130 | },
1131 | "slash": {
1132 | "version": "1.0.0",
1133 | "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
1134 | "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
1135 | },
1136 | "source-map": {
1137 | "version": "0.5.7",
1138 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
1139 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
1140 | },
1141 | "source-map-support": {
1142 | "version": "0.4.18",
1143 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
1144 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
1145 | "requires": {
1146 | "source-map": "^0.5.6"
1147 | }
1148 | },
1149 | "spdx-correct": {
1150 | "version": "3.1.0",
1151 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
1152 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
1153 | "requires": {
1154 | "spdx-expression-parse": "^3.0.0",
1155 | "spdx-license-ids": "^3.0.0"
1156 | }
1157 | },
1158 | "spdx-exceptions": {
1159 | "version": "2.2.0",
1160 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
1161 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA=="
1162 | },
1163 | "spdx-expression-parse": {
1164 | "version": "3.0.0",
1165 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
1166 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
1167 | "requires": {
1168 | "spdx-exceptions": "^2.1.0",
1169 | "spdx-license-ids": "^3.0.0"
1170 | }
1171 | },
1172 | "spdx-license-ids": {
1173 | "version": "3.0.5",
1174 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
1175 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
1176 | },
1177 | "string-width": {
1178 | "version": "1.0.2",
1179 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
1180 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
1181 | "requires": {
1182 | "code-point-at": "^1.0.0",
1183 | "is-fullwidth-code-point": "^1.0.0",
1184 | "strip-ansi": "^3.0.0"
1185 | }
1186 | },
1187 | "string.prototype.trimleft": {
1188 | "version": "2.1.0",
1189 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
1190 | "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
1191 | "requires": {
1192 | "define-properties": "^1.1.3",
1193 | "function-bind": "^1.1.1"
1194 | }
1195 | },
1196 | "string.prototype.trimright": {
1197 | "version": "2.1.0",
1198 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
1199 | "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
1200 | "requires": {
1201 | "define-properties": "^1.1.3",
1202 | "function-bind": "^1.1.1"
1203 | }
1204 | },
1205 | "strip-ansi": {
1206 | "version": "3.0.1",
1207 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
1208 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
1209 | "requires": {
1210 | "ansi-regex": "^2.0.0"
1211 | }
1212 | },
1213 | "ts-node": {
1214 | "version": "7.0.1",
1215 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
1216 | "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
1217 | "requires": {
1218 | "arrify": "^1.0.0",
1219 | "buffer-from": "^1.1.0",
1220 | "diff": "^3.1.0",
1221 | "make-error": "^1.1.1",
1222 | "minimist": "^1.2.0",
1223 | "mkdirp": "^0.5.1",
1224 | "source-map-support": "^0.5.6",
1225 | "yn": "^2.0.0"
1226 | },
1227 | "dependencies": {
1228 | "source-map": {
1229 | "version": "0.6.1",
1230 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1231 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
1232 | },
1233 | "source-map-support": {
1234 | "version": "0.5.15",
1235 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.15.tgz",
1236 | "integrity": "sha512-wYF5aX1J0+V51BDT3Om7uXNn0ct2FWiV4bvwiGVefxkm+1S1o5jsecE5lb2U28DDblzxzxeIDbTVpXHI9D/9hA==",
1237 | "requires": {
1238 | "buffer-from": "^1.0.0",
1239 | "source-map": "^0.6.0"
1240 | }
1241 | }
1242 | }
1243 | },
1244 | "typescript": {
1245 | "version": "3.6.4",
1246 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz",
1247 | "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg=="
1248 | },
1249 | "upath": {
1250 | "version": "1.2.0",
1251 | "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
1252 | "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg=="
1253 | },
1254 | "url": {
1255 | "version": "0.10.3",
1256 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
1257 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
1258 | "requires": {
1259 | "punycode": "1.3.2",
1260 | "querystring": "0.2.0"
1261 | }
1262 | },
1263 | "util-promisify": {
1264 | "version": "2.1.0",
1265 | "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz",
1266 | "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
1267 | "requires": {
1268 | "object.getownpropertydescriptors": "^2.0.3"
1269 | }
1270 | },
1271 | "uuid": {
1272 | "version": "3.3.2",
1273 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
1274 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
1275 | },
1276 | "validate-npm-package-license": {
1277 | "version": "3.0.4",
1278 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
1279 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
1280 | "requires": {
1281 | "spdx-correct": "^3.0.0",
1282 | "spdx-expression-parse": "^3.0.0"
1283 | }
1284 | },
1285 | "window-size": {
1286 | "version": "0.1.4",
1287 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz",
1288 | "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY="
1289 | },
1290 | "wrap-ansi": {
1291 | "version": "2.1.0",
1292 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
1293 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
1294 | "requires": {
1295 | "string-width": "^1.0.1",
1296 | "strip-ansi": "^3.0.1"
1297 | }
1298 | },
1299 | "wrappy": {
1300 | "version": "1.0.2",
1301 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1302 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
1303 | },
1304 | "xml2js": {
1305 | "version": "0.4.19",
1306 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
1307 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
1308 | "requires": {
1309 | "sax": ">=0.6.0",
1310 | "xmlbuilder": "~9.0.1"
1311 | }
1312 | },
1313 | "xmlbuilder": {
1314 | "version": "9.0.7",
1315 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
1316 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
1317 | },
1318 | "y18n": {
1319 | "version": "3.2.1",
1320 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
1321 | "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
1322 | },
1323 | "yargs": {
1324 | "version": "3.32.0",
1325 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
1326 | "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=",
1327 | "requires": {
1328 | "camelcase": "^2.0.1",
1329 | "cliui": "^3.0.3",
1330 | "decamelize": "^1.1.1",
1331 | "os-locale": "^1.4.0",
1332 | "string-width": "^1.0.1",
1333 | "window-size": "^0.1.4",
1334 | "y18n": "^3.2.0"
1335 | }
1336 | },
1337 | "yn": {
1338 | "version": "2.0.0",
1339 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
1340 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo="
1341 | }
1342 | }
1343 | }
1344 |
--------------------------------------------------------------------------------
/src/deploy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-javascript",
3 | "main": "index.js",
4 | "dependencies": {
5 | "@pulumi/pulumi": "^1.0.0",
6 | "@pulumi/aws": "^1.0.0",
7 | "@pulumi/awsx": "^0.18.10"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/geocode.py:
--------------------------------------------------------------------------------
1 | from concurrent.futures import ThreadPoolExecutor
2 | from urllib.request import urlopen
3 | from urllib.parse import quote
4 | import urllib.error as error
5 | import pandas as pd
6 | import ssl
7 | import json
8 | import os
9 | from ratelimit import limits, sleep_and_retry
10 |
11 | ctx = ssl.create_default_context()
12 | ctx.check_hostname = False
13 | ctx.verify_mode = ssl.CERT_NONE
14 |
15 |
16 | # This is the rate limit period in seconds
17 | # Mapbox rate limits on a per-minute basis, with geocoding set at 600 requests per minute
18 | RATE_LIMIT = 600
19 | LIMIT_DURATION = 60
20 |
21 | errReturn = {
22 | "features": []
23 | }
24 |
25 | # Set up rate limiting for API calls
26 | # If rate limit is exceeded, sleep the thread and wait until the LIMIT_DURATION has lapsed
27 | # If you request an increased rate limit, update RATE_LIMIT above
28 | @sleep_and_retry
29 | @limits(calls=RATE_LIMIT, period=LIMIT_DURATION)
30 | def req(url):
31 | try:
32 | with urlopen(url, context=ctx) as conn:
33 | return conn.read()
34 | except error.HTTPError as e:
35 | return json.dumps(errReturn).encode("utf-8")
36 |
37 | def geocodeForward(input,token):
38 | url = "https://api.mapbox.com/geocoding/v5/mapbox.places-permanent/%s.json?pluginName=tableauGeocoder&access_token=%s&types=address&limit=1"
39 | access_token = token
40 | addr = list(input["Address"])
41 | urls = [url % (quote(list_item), access_token) for list_item in addr]
42 | # Once you exceed 1200 req/minute, then consider using more threads to achieve performance
43 | with ThreadPoolExecutor(max_workers=1) as executor:
44 | results = (executor.map(req, urls))
45 | parsed_results = [json.loads(result.decode("utf-8")) for result in results]
46 | long,lat = zip(*list(result["features"][0]["geometry"]["coordinates"]
47 | if len(result["features"]) > 0
48 | else [None,None]
49 | for result in parsed_results))
50 | address = [
51 | result["features"][0].get("place_name", None)
52 | if len(result["features"]) > 0
53 | else None
54 | for result in parsed_results
55 | ]
56 | relevance = [
57 | result["features"][0].get("relevance", None)
58 | if len(result["features"]) > 0
59 | else None
60 | for result in parsed_results
61 | ]
62 | accuracy = [
63 | result["features"][0]["properties"].get("accuracy", None)
64 | if len(result["features"]) > 0
65 | else None
66 | for result in parsed_results
67 | ]
68 | d = {
69 | "InitialQuery": addr,
70 | "ReturnAddress": address,
71 | "Accuracy": accuracy,
72 | "Relevance": relevance,
73 | "Longitude": long,
74 | "Latitude": lat,
75 | }
76 | columns = list(d.keys())
77 | dataOut = pd.DataFrame(d, columns=columns)
78 | return dataOut
79 |
80 | def geocodeReverse(input,token):
81 | url = "https://api.mapbox.com/geocoding/v5/mapbox.places-permanent/%s.json?pluginName=tableauGeocoder&access_token=%s&types=address&limit=1"
82 | access_token = token
83 | input['coords'] = input['Longitude'].map(str)+","+input['Latitude'].map(str)
84 | coordinates = list(input["coords"])
85 | urls = [url % (quote(str(list_item)), access_token) for list_item in coordinates]
86 | with ThreadPoolExecutor(max_workers=1) as executor:
87 | results = (executor.map(req, urls))
88 | parsed_results = (json.loads(result.decode("utf-8")) for result in results)
89 | address = [
90 | result["features"][0].get("place_name", None)
91 | if len(result["features"]) > 0
92 | else None
93 | for result in parsed_results
94 | ]
95 | relevance = [
96 | result["features"][0].get("relevance", None)
97 | if len(result["features"]) > 0
98 | else None
99 | for result in parsed_results
100 | ]
101 | accuracy = [
102 | result["features"][0]["properties"].get("accuracy", None)
103 | if len(result["features"]) > 0
104 | else None
105 | for result in parsed_results
106 | ]
107 | d = {
108 | "InitialQuery": coordinates,
109 | "ReturnAddress": address,
110 | "Accuracy": accuracy,
111 | "Relevance": relevance
112 | }
113 | columns = list(d.keys())
114 | dataOut = pd.DataFrame(d, columns=columns)
115 | return dataOut
--------------------------------------------------------------------------------
/src/prepForward.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('/TabPy')
3 | from geocode import geocodeForward
4 |
5 | def prepGeo(input):
6 | prepData = geocodeForward(input,"token")
7 | return prepData
8 |
9 | def get_output_schema():
10 | return pd.DataFrame(
11 | {
12 | "InitialQuery": prep_string(),
13 | "ReturnAddress": prep_string(),
14 | "Accuracy": prep_string(),
15 | "Relevance": prep_decimal(),
16 | "Longitude": prep_decimal(),
17 | "Latitude": prep_decimal(),
18 | }
19 | )
--------------------------------------------------------------------------------
/src/prepReverse.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('/TabPy')
3 | from geocode import geocodeReverse
4 |
5 | def prepGeo(input):
6 | prepData = geocodeReverse(input,"token")
7 | return prepData
8 |
9 | def get_output_schema():
10 | return pd.DataFrame(
11 | {
12 | "InitialQuery": prep_string(),
13 | "ReturnAddress": prep_string(),
14 | "Accuracy": prep_string(),
15 | "Relevance": prep_decimal()
16 | }
17 | )
--------------------------------------------------------------------------------
/src/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | tabpy --config=./tabpy.conf
--------------------------------------------------------------------------------
/src/tabpy.conf:
--------------------------------------------------------------------------------
1 | [TabPy]
2 | # TABPY_QUERY_OBJECT_PATH = /tmp/query_objects
3 | TABPY_PORT = 80
4 | # TABPY_STATE_PATH = /tabpy/tabpy_server
5 |
6 | # Where static pages live
7 | # TABPY_STATIC_PATH = /tabpy/tabpy_server/static
8 |
9 | # For how to configure TabPy authentication read
10 | # docs/server-config.md.
11 | # TABPY_PWD_FILE = /path/to/password/file.txt
12 |
13 | # To set up secure TabPy uncomment and modify the following lines.
14 | # Note only PEM-encoded x509 certificates are supported.
15 | # TABPY_TRANSFER_PROTOCOL = https
16 | # TABPY_CERTIFICATE_FILE = /path/to/certificate/file.crt
17 | # TABPY_KEY_FILE = /path/to/key/file.key
18 |
19 | # Log additional request details including caller IP, full URL, client
20 | # end user info if provided.
21 | # TABPY_LOG_DETAILS = true
22 |
23 | # Limit request size (in Mb) - any request which size exceeds
24 | # specified amount will be rejected by TabPy.
25 | # Default value is 100 Mb.
26 | # TABPY_MAX_REQUEST_SIZE_MB = 100
27 |
28 | # Configure how long a custom script provided to the /evaluate method
29 | # will run before throwing a TimeoutError.
30 | # The value should be a float representing the timeout time in seconds.
31 | # Default here is 10 minutes. At default Mapbox Rate limits this is good for
32 | # 6000 geocodes.
33 | TABPY_EVALUATE_TIMEOUT = 600
34 |
35 | [loggers]
36 | keys=root
37 |
38 | [handlers]
39 | keys=rootHandler,rotatingFileHandler
40 |
41 | [formatters]
42 | keys=rootFormatter
43 |
44 | [logger_root]
45 | level=DEBUG
46 | handlers=rootHandler,rotatingFileHandler
47 | qualname=root
48 | propagete=0
49 |
50 | [handler_rootHandler]
51 | class=StreamHandler
52 | level=DEBUG
53 | formatter=rootFormatter
54 | args=(sys.stdout,)
55 |
56 | [handler_rotatingFileHandler]
57 | class=handlers.RotatingFileHandler
58 | level=DEBUG
59 | formatter=rootFormatter
60 | args=('tabpy_log.log', 'a', 1000000, 5)
61 |
62 | [formatter_rootFormatter]
63 | format=%(asctime)s [%(levelname)s] (%(filename)s:%(module)s:%(lineno)d): %(message)s
64 | datefmt=%Y-%m-%d,%H:%M:%S
--------------------------------------------------------------------------------
/test.txt:
--------------------------------------------------------------------------------
1 | Address
2 | 50 Beale Street San Francisco CA 94105
3 | 740 15th Street NW Washington DC 20005
--------------------------------------------------------------------------------