diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..6075598
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,38 @@
+# syntax = docker/dockerfile:1
+
+# Use Node.js base image
+FROM node:20-slim as base
+
+LABEL fly_launch_runtime="Node.js"
+
+# App lives here
+WORKDIR /app
+
+# Set production environment
+ENV NODE_ENV="production"
+
+
+# Throw-away build stage to reduce size of final image
+FROM base as build
+
+# Install packages needed to build node modules
+RUN apt-get update -qq && \
+ apt-get install --no-install-recommends -y build-essential pkg-config python-is-python3
+
+# Install node modules
+COPY package-lock.json package.json ./
+RUN npm install --ignore-scripts
+
+# Copy application code
+COPY . .
+
+
+# Final stage for app image
+FROM base
+
+# Copy built application
+COPY --from=build /app /app
+
+# Start the server by default, this can be overwritten at runtime
+EXPOSE 3000
+CMD [ "node", "./map-selector/index.js" ]
diff --git a/map-selector/index.html b/map-selector/index.html
new file mode 100644
index 0000000..eb48590
--- /dev/null
+++ b/map-selector/index.html
@@ -0,0 +1,329 @@
+
+
+
+
+SMP Downloader
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Click the map to draw a polygon.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/map-selector/index.js b/map-selector/index.js
new file mode 100644
index 0000000..ec54e54
--- /dev/null
+++ b/map-selector/index.js
@@ -0,0 +1,61 @@
+import http from 'http'
+
+import download from '../lib/download.js'
+
+const server = http.createServer(async (req, res) => {
+ if (req.method === 'GET' && req.url === '/') {
+ res.setHeader('Content-Type', 'text/html')
+ res.end(
+ await import('fs/promises').then((fs) =>
+ fs.readFile('./map-selector/index.html'),
+ ),
+ )
+ return
+ }
+ if (req.method === 'POST' && req.url === '/download') {
+ let body = ''
+
+ req.on('data', (chunk) => {
+ body += chunk.toString()
+ })
+
+ req.on('end', async () => {
+ try {
+ const { bbox, maxzoom, styleUrl, accessToken } = JSON.parse(body)
+
+ res.setHeader('Content-Type', 'application/octet-stream')
+ res.setHeader(
+ 'Content-Disposition',
+ 'attachment; filename="map-package.smp"',
+ )
+
+ const downloadStream = download({
+ bbox,
+ maxzoom,
+ styleUrl,
+ accessToken,
+ })
+
+ downloadStream.pipe(res)
+
+ downloadStream.on('error', (error) => {
+ console.error('Download error:', error)
+ res.statusCode = 500
+ res.end()
+ })
+ } catch (error) {
+ console.error('Server error:', error)
+ res.statusCode = 500
+ res.end()
+ }
+ })
+ } else {
+ res.statusCode = 404
+ res.end()
+ }
+})
+
+const PORT = process.env.PORT || 3000
+server.listen(PORT, () => {
+ console.log(`Server running on port ${PORT}`)
+})