diff --git a/mapserver/Dockerfile b/mapserver/Dockerfile
index 77ecdd8ee29840873e21552b1be0f0291c830fc4..4982922f612c61545f4c07e9763f387df8934e28 100644
--- a/mapserver/Dockerfile
+++ b/mapserver/Dockerfile
@@ -1,57 +1,29 @@
-FROM ubuntu:eoan
+FROM tiledb/tiledb-geospatial:latest
+
+WORKDIR /work
+
+# FIXME: Remove once added to parent image
+RUN pip3 install shapely
 
 RUN apt-get -y update && \
     apt-get -y upgrade && \
     apt-get -y install gcc g++ cmake sqlite3 libsqlite3-dev pkg-config bash-completion curl subversion && \
     apt-get -y clean
 
-# proj 6.1.1
-RUN mkdir -p /build/proj && \
-    cd /build/proj && \
-    curl -O https://download.osgeo.org/proj/proj-6.1.1.tar.gz && \
-    tar xf proj-6.1.1.tar.gz && \
-    cd proj-6.1.1 && \
-    ./configure && \
-    make -j $(nproc) && \
-    make install && \
-    rm -rf /build/proj
-
-#    mkdir build && \
-#    cd build && \
-#    cmake .. && \
-#    cmake --build . && \
 
-# gdal 3.0.1
-RUN mkdir -p /build/gdal && \
-    cd /build/gdal && \
-    curl -O http://download.osgeo.org/gdal/3.0.1/gdal-3.0.1.tar.gz && \
-    tar xf gdal-3.0.1.tar.gz && \
-    cd gdal-3.0.1 && \
-    ./configure && \
-    make -j$(nproc) && \
-    make install && \
-    rm -rf /build/gdal
-
-# mapserver
-RUN mkdir -p /build/mapserver && \
-    apt-get -y update && \
-    DEBIAN_FRONTEND=noninteractive apt-get install -y libprotobuf17 zlib1g-dev libpng-dev libjpeg-turbo8 libfreetype6 libfribidi-dev libharfbuzz-dev libcairo2-dev libfcgi-dev libgeos++-dev postgresql postgis libxml2-dev libgif-dev libjpeg-turbo8-dev libprotobuf-dev protobuf-compiler libprotobuf-c-dev libprotobuf-c1 libprotobuf-dev protobuf-c-compiler && \
-    apt-get -y clean && \
-    curl -O http://download.osgeo.org/mapserver/mapserver-7.4.2.tar.gz && \
-    tar xf mapserver-7.4.2.tar.gz && \
-    cd mapserver-7.4.2 && \
-    mkdir build && \
-    cd build && \
-    cmake .. -DWITH_POSTGIS=0 && \
-    make -j$(nproc) && \
-    make install
-
-# postgres
-#service postgresql start
-
-# postgis
-
-# pgbouncer
+# FIXME: Remove if/when upstream image builds with fcgi
+# Install Mapserver (unstable)
+RUN mkdir -p /build_deps && cd /build_deps \
+  && apt-get -y update \
+#  && apt-get install -y libprotobuf17 zlib1g-dev libpng-dev libjpeg-turbo8 libfreetype6 libfribidi-dev libharfbuzz-dev libcairo2-dev libfcgi-dev libgeos++-dev postgresql postgis libxml2-dev libgif-dev libjpeg-turbo8-dev libprotobuf-dev protobuf-compiler libprotobuf-c-dev libprotobuf-c1 libprotobuf-dev protobuf-c-compiler \
+  && apt-get install -y zlib1g-dev libpng-dev libjpeg-turbo8 libfreetype6 libfribidi-dev libharfbuzz-dev libcairo2-dev libfcgi-dev libgeos++-dev postgresql postgis libxml2-dev libgif-dev libjpeg-turbo8-dev libprotobuf-dev protobuf-compiler libprotobuf-c-dev libprotobuf-c1 libprotobuf-dev protobuf-c-compiler \
+  && apt-get -y clean \
+  && git clone https://github.com/mapserver/mapserver.git && cd mapserver \
+  && git checkout 0fcc810f0b559c800f950db78a79fa6574799f23 \
+  && mkdir -p build && cd build \
+  && cmake .. -DWITH_PROTOBUFC=OFF -DWITH_POSTGIS=OFF \
+  && make -j$(nproc) \
+  && make install
 
 # apache
 # http://www.inanzzz.com/index.php/post/rhsb/running-apache-server-as-foreground-on-ubuntu-with-dockerfile
@@ -66,35 +38,36 @@ ENV APACHE_SERVER_NAME localhost
 
 # install httpd runtime dependencies
 # https://httpd.apache.org/docs/2.4/install.html#requirements
-RUN apt-get -y install apache2 libapache2-mod-fcgid && \
+RUN cd /build_deps && apt-get -y install apache2 libapache2-mod-fcgid && \
     ls /etc/apache2/mods-available && \
     ls /etc/apache2/mods-enabled && \
-    apt-get -y install libapache2-mod-php7.3 php7.3-common php7.3-cli php7.3-fpm php7.3 && \
-    a2enmod actions proxy_fcgi setenvif cgi fcgid && \
-    a2enconf php7.3-fpm serve-cgi-bin && \
+    apt-get -y install libapache2-mod-php7.2 php7.2-common php7.2-cli php7.2-fpm php7.2 && \
+    a2enmod actions proxy_fcgi setenvif cgi fcgid rewrite && \
+    a2enconf php7.2-fpm serve-cgi-bin && \
     apt-get -y clean && \
     rm -rf /var/lib/apt/lists/*
 
-COPY apache-conf /etc/apache2/apache2.conf
+COPY site-conf /etc/apache2/sites-available/cspp_geo.conf
+# disable the default which would conflict with our custom
+RUN a2ensite cspp_geo && a2dissite 000-default
+COPY cgi-bin/* /usr/lib/cgi-bin/
 
 # Point apache to the mapserver binary
 RUN ln -s /usr/local/bin/mapserv /usr/lib/cgi-bin/mapserv && \
-    chown ${APACHE_RUN_USER}:${APACHE_RUN_GROUP} /usr/lib/cgi-bin/mapserv && \
-    chown -h ${APACHE_RUN_USER}:${APACHE_RUN_GROUP} /usr/lib/cgi-bin/mapserv
+    chown ${APACHE_RUN_USER}:${APACHE_RUN_GROUP} /usr/lib/cgi-bin/* && \
+    chown -h ${APACHE_RUN_USER}:${APACHE_RUN_GROUP} /usr/lib/cgi-bin/*
+
+COPY mapfiles/ /work/mapfiles/
+COPY html/ /var/www/html/
+
+# Add our own custom EPSG codes (HACK)
+# GOES-16 ABI Full Disk = EPSG:930916
+# GOES-17 ABI Full Disk = EPSG:930917
+COPY sql/ /work/sql/
+RUN sqlite3 -init /work/sql/goesr_crs.sql /usr/local/share/proj/proj.db
 
 # https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
 STOPSIGNAL WINCH
 
 EXPOSE 80
 CMD ["/usr/sbin/apache2ctl", "-DFOREGROUND"]
-
-##############################################################
-
-
-
-
-
-# php
-
-# python? (for shp2csv type stuff)
-
diff --git a/mapserver/README.md b/mapserver/README.md
index 3bc0a906305e80f48d8e60e5f7539f7861413308..7361c7f4a16ccdd6fa4854c5ed4fcdf4007097d9 100644
--- a/mapserver/README.md
+++ b/mapserver/README.md
@@ -3,9 +3,23 @@
 ## Usage
 
 ```bash
-docker run -p 8888:80 -d gitlab.ssec.wisc.edu:5555/cspp_geo/cspp-geo-web-viewer/mapserver:latest
+docker run -p 8888:80 -d --rm --name cspp-geo-mapserver -v cspp-geo-abi-l1b-geotiffs:/data gitlab.ssec.wisc.edu:5555/cspp_geo/cspp-geo-web-viewer/mapserver:latest
 ```
 
 Then the main mapserv CGI script can be accessed with:
 
 http://localhost:8888/cgi-bin/mapserv
+
+## Special Notes
+
+This image has had its installation of the PROJ library modified to include
+new non-standard EPSG codes to support the projections of certain satellite
+instruments. These additions are part of the Dockerfile build process and
+include the following definitions:
+
+1. EPSG:930916 - The GOES-16 ABI Fixed Grid geostationary coordinate reference
+   system. The numbers are meant to represent "GEOG16" while also attempting
+   to avoid conflicts with future additions to the official EPSG database.
+2. EPSG:930917 - The GOES-17 ABI Fixed Grid geostationary coordinate reference
+   system. The numbers are meant to represent "GEOG17" while also attempting
+   to avoid conflicts with future additions to the official EPSG database.
diff --git a/mapserver/apache-conf b/mapserver/apache-conf
deleted file mode 100644
index 2560da5bb9090684b7a263dd90d2ce668cb72c4d..0000000000000000000000000000000000000000
--- a/mapserver/apache-conf
+++ /dev/null
@@ -1,227 +0,0 @@
-# This is the main Apache server configuration file.  It contains the
-# configuration directives that give the server its instructions.
-# See http://httpd.apache.org/docs/2.4/ for detailed information about
-# the directives and /usr/share/doc/apache2/README.Debian about Debian specific
-# hints.
-#
-#
-# Summary of how the Apache 2 configuration works in Debian:
-# The Apache 2 web server configuration in Debian is quite different to
-# upstream's suggested way to configure the web server. This is because Debian's
-# default Apache2 installation attempts to make adding and removing modules,
-# virtual hosts, and extra configuration directives as flexible as possible, in
-# order to make automating the changes and administering the server as easy as
-# possible.
-
-# It is split into several files forming the configuration hierarchy outlined
-# below, all located in the /etc/apache2/ directory:
-#
-#       /etc/apache2/
-#       |-- apache2.conf
-#       |       `--  ports.conf
-#       |-- mods-enabled
-#       |       |-- *.load
-#       |       `-- *.conf
-#       |-- conf-enabled
-#       |       `-- *.conf
-#       `-- sites-enabled
-#               `-- *.conf
-#
-#
-# * apache2.conf is the main configuration file (this file). It puts the pieces
-#   together by including all remaining configuration files when starting up the
-#   web server.
-#
-# * ports.conf is always included from the main configuration file. It is
-#   supposed to determine listening ports for incoming connections which can be
-#   customized anytime.
-#
-# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/
-#   directories contain particular configuration snippets which manage modules,
-#   global configuration fragments, or virtual host configurations,
-#   respectively.
-#
-#   They are activated by symlinking available configuration files from their
-#   respective *-available/ counterparts. These should be managed by using our
-#   helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See
-#   their respective man pages for detailed information.
-#
-# * The binary is called apache2. Due to the use of environment variables, in
-#   the default configuration, apache2 needs to be started/stopped with
-#   /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not
-#   work with the default configuration.
-
-
-# Global configuration
-#
-
-#
-# ServerRoot: The top of the directory tree under which the server's
-# configuration, error, and log files are kept.
-#
-# NOTE!  If you intend to place this on an NFS (or otherwise network)
-# mounted filesystem then please read the Mutex documentation (available
-# at <URL:http://httpd.apache.org/docs/2.4/mod/core.html#mutex>);
-# you will save yourself a lot of trouble.
-#
-# Do NOT add a slash at the end of the directory path.
-#
-#ServerRoot "/etc/apache2"
-
-#
-# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
-#
-#Mutex file:${APACHE_LOCK_DIR} default
-
-#
-# The directory where shm and other runtime files will be stored.
-#
-
-DefaultRuntimeDir ${APACHE_RUN_DIR}
-
-#
-# PidFile: The file in which the server should record its process
-# identification number when it starts.
-# This needs to be set in /etc/apache2/envvars
-#
-PidFile ${APACHE_PID_FILE}
-
-#
-# Timeout: The number of seconds before receives and sends time out.
-#
-Timeout 300
-
-#
-# KeepAlive: Whether or not to allow persistent connections (more than
-# one request per connection). Set to "Off" to deactivate.
-#
-KeepAlive On
-
-#
-# MaxKeepAliveRequests: The maximum number of requests to allow
-# during a persistent connection. Set to 0 to allow an unlimited amount.
-# We recommend you leave this number high, for maximum performance.
-#
-MaxKeepAliveRequests 100
-
-#
-# KeepAliveTimeout: Number of seconds to wait for the next request from the
-# same client on the same connection.
-#
-KeepAliveTimeout 5
-
-
-# These need to be set in /etc/apache2/envvars
-User ${APACHE_RUN_USER}
-Group ${APACHE_RUN_GROUP}
-
-#
-# HostnameLookups: Log the names of clients or just their IP addresses
-# e.g., www.apache.org (on) or 204.62.129.132 (off).
-# The default is off because it'd be overall better for the net if people
-# had to knowingly turn this feature on, since enabling it means that
-# each client request will result in AT LEAST one lookup request to the
-# nameserver.
-#
-HostnameLookups Off
-
-# ErrorLog: The location of the error log file.
-# If you do not specify an ErrorLog directive within a <VirtualHost>
-# container, error messages relating to that virtual host will be
-# logged here.  If you *do* define an error logfile for a <VirtualHost>
-# container, that host's errors will be logged there and not here.
-#
-ErrorLog ${APACHE_LOG_DIR}/error.log
-
-#
-# LogLevel: Control the severity of messages logged to the error_log.
-# Available values: trace8, ..., trace1, debug, info, notice, warn,
-# error, crit, alert, emerg.
-# It is also possible to configure the log level for particular modules, e.g.
-# "LogLevel info ssl:warn"
-#
-LogLevel warn
-
-# Include module configuration:
-IncludeOptional mods-enabled/*.load
-IncludeOptional mods-enabled/*.conf
-
-# Include list of ports to listen on
-Include ports.conf
-
-
-# Sets the default security model of the Apache2 HTTPD server. It does
-# not allow access to the root filesystem outside of /usr/share and /var/www.
-# The former is used by web applications packaged in Debian,
-# the latter may be used for local directories served by the web server. If
-# your system is serving content from a sub-directory in /srv you must allow
-# access here, or in any related virtual host.
-<Directory />
-        Options FollowSymLinks
-        AllowOverride None
-        Require all denied
-</Directory>
-
-<Directory /usr/share>
-        AllowOverride None
-        Require all granted
-</Directory>
-
-<Directory /var/www/>
-        Options Indexes FollowSymLinks
-        AllowOverride None
-        Require all granted
-</Directory>
-
-#<Directory /srv/>
-#       Options Indexes FollowSymLinks
-#       AllowOverride None
-#       Require all granted
-#</Directory>
-
-
-
-
-# AccessFileName: The name of the file to look for in each directory
-# for additional configuration directives.  See also the AllowOverride
-# directive.
-#
-AccessFileName .htaccess
-
-#
-# The following lines prevent .htaccess and .htpasswd files from being
-# viewed by Web clients.
-#
-<FilesMatch "^\.ht">
-        Require all denied
-</FilesMatch>
-
-
-#
-# The following directives define some format nicknames for use with
-# a CustomLog directive.
-#
-# These deviate from the Common Log Format definitions in that they use %O
-# (the actual bytes sent including headers) instead of %b (the size of the
-# requested file), because the latter makes it impossible to detect partial
-# requests.
-#
-# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended.
-# Use mod_remoteip instead.
-#
-LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
-LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
-LogFormat "%h %l %u %t \"%r\" %>s %O" common
-LogFormat "%{Referer}i -> %U" referer
-LogFormat "%{User-agent}i" agent
-
-# Include of directories ignores editors' and dpkg's backup files,
-# see README.Debian for details.
-
-# Include generic snippets of statements
-IncludeOptional conf-enabled/*.conf
-
-# Include the virtual host configurations:
-IncludeOptional sites-enabled/*.conf
-
-# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
\ No newline at end of file
diff --git a/mapserver/cgi-bin/layer_times.py b/mapserver/cgi-bin/layer_times.py
new file mode 100755
index 0000000000000000000000000000000000000000..8666f0bf65e2d060afb56c9d6a89c8556f090887
--- /dev/null
+++ b/mapserver/cgi-bin/layer_times.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+import cgi
+import json
+import fiona
+
+form = cgi.FieldStorage()
+layer = form['layer'].value
+with fiona.open(layer, 'r') as shp_file:
+    times = [x['properties']['time'] for x in shp_file]
+
+print("Content-Type: application/json")
+print("Access-Control-Allow-Origin: *")
+print()  # blank line, end of headers
+print(json.dumps(times))
diff --git a/mapserver/html/index.html b/mapserver/html/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..e6ed5388e3338d35984e82cbf86ed938e093f8c6
--- /dev/null
+++ b/mapserver/html/index.html
@@ -0,0 +1,21 @@
+<html>
+<head></head>
+<body>
+<h1>CSPP Geo Geo2Grid Tile Server</h1>
+
+This tile server for the CSPP Geo Geo2Grid project.
+<br/>
+<br/>
+To access tiles using a standard WMS protocol:
+<br/>
+<br/>
+http://localhost/wms/goes16/abi/fldk/l1b?...
+<br/>
+<br/>
+Where "fldk" stands for Full Disk and "..." represents all remaining WMS
+parameters. Other sectors available are "m01", "m02", and "conus".
+<br/>
+<br/>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/mapserver/mapfiles/g16_abi_radf_l1b.map b/mapserver/mapfiles/g16_abi_radf_l1b.map
new file mode 100644
index 0000000000000000000000000000000000000000..d989dc4c112317afecca7c6e324e3ec384f5784b
--- /dev/null
+++ b/mapserver/mapfiles/g16_abi_radf_l1b.map
@@ -0,0 +1,109 @@
+MAP
+  IMAGETYPE      PNG
+  SIZE           256 256
+  EXTENT         -180 -90 180 90
+  
+  PROJECTION
+    "init=epsg:930916"
+  END
+
+  WEB
+    METADATA
+      "wms_title" "Weather data"
+      "wms_onlineresource" "https://myhost/mapserv"
+      "wms_enable_request" "*"
+    END
+  END
+
+
+  ##############
+  # True Color #
+  ##############
+
+  LAYER
+    NAME "true_color_index"
+    TYPE TILEINDEX
+    DATA "/data/tiles/g16/abi/radf/true_color/true_color"
+  END
+
+  LAYER
+    NAME    "true_color"
+    TYPE    RASTER
+    TILEITEM "location"
+    TILEINDEX "true_color_index"
+    # Comment below to default to transparency
+    # OFFSITE 0 0 0
+    PROJECTION
+      "init=epsg:930916"
+    END
+    METADATA
+      "wms_title" "GOES-16 ABI True Color"
+      "wms_extent" "-180 -90 180 90"
+      "wms_timeextent" "2017-01-01/2020-12-31"
+      "wms_timeformat" "YYYY-MM-DDTHH:MM:SS"
+      "wms_timeitem" "time" # time is a metadata item
+      "wms_timedefault" "2019-12-12T19:20:18"
+      "wms_enable_request" "*"
+    END
+    FILTER (`[time]` = `2019-12-12T19:20:18`)
+  END # goes raster layer ends here
+
+  ######################
+  # True Color Lat/Lon #
+  ######################
+
+  LAYER
+    NAME "true_color_index_ll"
+    TYPE TILEINDEX
+    DATA "/data/tiles/g16/abi/radf_ll/true_color_ll/true_color_ll"
+  END
+
+  LAYER
+    NAME    "true_color_ll"
+    TYPE    RASTER
+    TILEITEM "location"
+    TILEINDEX "true_color_index_ll"
+    # Comment below to default to transparency
+    # OFFSITE 0 0 0
+    PROJECTION
+      "init=epsg:4326"
+    END
+    METADATA
+      "wms_title" "GOES-16 ABI True Color"
+      "wms_extent" "-180 -90 180 90"
+      "wms_timeextent" "2017-01-01/2020-12-31"
+      "wms_timeformat" "YYYY-MM-DDTHH:MM:SS"
+      "wms_timeitem" "time" # time is a metadata item
+      "wms_timedefault" "2019-12-12T19:20:18"
+      "wms_enable_request" "*"
+    END
+    FILTER (`[time]` = `2019-12-12T19:20:18`)
+  END # goes raster layer ends here
+
+  ###################
+  # True Color Fake #
+  ###################
+
+  LAYER
+    NAME    "true_color_test"
+    TYPE    RASTER
+    TILEITEM "location"
+    TILEINDEX "true_color_index"
+    # Comment below to default to transparency
+    # OFFSITE 0 0 0
+    PROJECTION
+        "init=epsg:930916"
+    END
+    METADATA
+      "wms_title" "GOES-16 ABI True Color"
+      "wms_extent" "-180 -90 180 90"
+      "wms_timeextent" "2017-01-01/2020-12-31"
+      "wms_timeformat" "YYYY-MM-DDTHH:MM:SS"
+      "wms_timeitem" "time" # time is a metadata item
+      "wms_timedefault" "2019-12-12T19:20:18"
+      "wms_enable_request" "*"
+    END
+    FILTER (`[time]` = `2019-12-12T19:20:18`)
+  END # goes raster layer ends here
+
+END # end of map file
diff --git a/mapserver/site-conf b/mapserver/site-conf
new file mode 100644
index 0000000000000000000000000000000000000000..0c812d2e18e49146c1dfb537f702802b4f65e8f5
--- /dev/null
+++ b/mapserver/site-conf
@@ -0,0 +1,43 @@
+<VirtualHost *:80>
+        # The ServerName directive sets the request scheme, hostname and port that
+        # the server uses to identify itself. This is used when creating
+        # redirection URLs. In the context of virtual hosts, the ServerName
+        # specifies what hostname must appear in the request's Host: header to
+        # match this virtual host. For the default virtual host (this file) this
+        # value is not decisive as it is used as a last resort host regardless.
+        # However, you must set it for any further virtual host explicitly.
+        #ServerName www.example.com
+
+        ServerAdmin david.hoese@ssec.wisc.edu
+        DocumentRoot /var/www/html
+
+        # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
+        # error, crit, alert, emerg.
+        # It is also possible to configure the loglevel for particular
+        # modules, e.g.
+        #LogLevel info ssl:warn
+
+        ErrorLog ${APACHE_LOG_DIR}/error.log
+        CustomLog ${APACHE_LOG_DIR}/access.log combined
+
+        # For most configuration files from conf-available/, which are
+        # enabled or disabled at a global level, it is possible to
+        # include a line for only one particular virtual host. For example the
+        # following line enables the CGI configuration for this host only
+        # after it has been globally disabled with "a2disconf".
+        #Include conf-available/serve-cgi-bin.conf
+
+        # Custom CSPP Geo
+        # LoadModule rewrite_module modules/mod_rewrite.so
+        RewriteEngine on
+        # /wms/goes16/abi/fldk/
+        RewriteRule   "^/wms/([^/]+)/([^/]+)/([^/]+)/l1b?(.*)" "/cgi-bin/mapserv?map=/work/mapfiles/$1_$2_$3_l1b.map&$4" [PT,QSA]
+        # /wms_times/g16/abi/radf/true_color
+        RewriteRule   "^/wms_times/([^/]+)/([^/]+)/([^/]+)/([^/]+)" "/cgi-bin/layer_times.py?layer=/data/tiles/$1/$2/$3/$4/$4.shp" [PT,QSA]
+        # FIXME: We need to include the sector
+        # /data/goes/grb/goes16/2020/2020_01_21_021/abi/L1b/RadF/GOES-16_ABI_RadF_C01_20200121_000016_GOES-East.tif
+        # "/data/tiles/g16/abi/radf/true_color/true_color"
+        LogLevel alert rewrite:trace6
+</VirtualHost>
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
\ No newline at end of file
diff --git a/mapserver/sql/goesr_crs.sql b/mapserver/sql/goesr_crs.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0cbf495a70ae4aa8f38bd1da6fc4c3a9cd036e3e
--- /dev/null
+++ b/mapserver/sql/goesr_crs.sql
@@ -0,0 +1,2 @@
+INSERT INTO projected_crs (auth_name, code, name, description, scope, coordinate_system_auth_name, coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, conversion_auth_name, conversion_code, area_of_use_auth_name, area_of_use_code, text_definition, deprecated) VALUES ('EPSG', '930917', 'GOES-17 ABI Fixed Grid', 'GOES-17 (GOES-WEST) ABI Fixed Grid in the Geostationary projection', null, null, null, 'EPSG', '4269', null, null, null, null, 'PROJCRS["GOES-17 ABI Fixed Grid",BASEGEOGCRS["GOES-17 ABI Fixed Grid",DATUM["North American Datum 1983",ELLIPSOID["GRS 1980",6378137,298.257222101,LENGTHUNIT["metre",1]],ID["EPSG",6269]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],CONVERSION["unknown",METHOD["Geostationary Satellite (Sweep X)"],PARAMETER["Longitude of natural origin",-137,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Satellite Height",35786023,LENGTHUNIT["metre",1,ID["EPSG",9001]]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]', 0);
+INSERT INTO projected_crs (auth_name, code, name, description, scope, coordinate_system_auth_name, coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, conversion_auth_name, conversion_code, area_of_use_auth_name, area_of_use_code, text_definition, deprecated) VALUES ('EPSG', '930916', 'GOES-16 ABI Fixed Grid', 'GOES-16 (GOES-WEST) ABI Fixed Grid in the Geostationary projection', null, null, null, 'EPSG', '4269', null, null, null, null, 'PROJCRS["GOES-16 ABI Fixed Grid",BASEGEOGCRS["GOES-16 ABI Fixed Grid",DATUM["North American Datum 1983",ELLIPSOID["GRS 1980",6378137,298.257222101,LENGTHUNIT["metre",1]],ID["EPSG",6269]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],CONVERSION["unknown",METHOD["Geostationary Satellite (Sweep X)"],PARAMETER["Longitude of natural origin",-75,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Satellite Height",35786023,LENGTHUNIT["metre",1,ID["EPSG",9001]]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]', 0);
\ No newline at end of file
diff --git a/mapserver/tiledb_geospatial_Dockerfile b/mapserver/tiledb_geospatial_Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..f80985ec2029f49535e75bbf5a7d205e5528b913
--- /dev/null
+++ b/mapserver/tiledb_geospatial_Dockerfile
@@ -0,0 +1,159 @@
+# Based on
+
+FROM ubuntu:18.04
+LABEL maintainer="support@tiledb.io"
+
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=GMT
+ENV LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH}
+ENV LANG=C.UTF-8
+ENV LC_ALL=C.UTF-8
+
+RUN apt-get update && apt-get install -y \
+  gosu \
+  pwgen \
+  tzdata \
+  gcc \
+  g++ \
+  build-essential \
+  cmake \
+  sqlite \
+  libsqlite3-dev \
+  libxml2-dev \
+  libjpeg-dev \
+  libpng-dev \
+  libfreetype6-dev \
+  libzstd-dev \
+  python3-pip \
+  git \
+  wget \
+  && rm -rf /var/lib/apt/lists/*
+
+# Install tiledb using 1.7.2 release
+RUN mkdir -p /build_deps && cd /build_deps \
+  && git clone https://github.com/TileDB-Inc/TileDB.git -b 1.7.2 && cd TileDB \
+  && mkdir -p build && cd build \
+  && cmake -DTILEDB_VERBOSE=ON -DTILEDB_S3=ON -DTILEDB_SERIALIZATION=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local .. \
+  && make -j$(nproc) \
+  && make -C tiledb install
+
+# Install curl after building tiledb
+RUN apt-get update && apt-get install -y \
+  libcurl4 \
+  libcurl4-openssl-dev \
+  && rm -rf /var/lib/apt/lists/*
+
+# Install OpenJPEG
+RUN cd /build_deps \
+  && git clone https://github.com/uclouvain/openjpeg.git -b v2.2.0 && cd openjpeg \
+  && mkdir -p build && cd build \
+  && cmake .. \
+  && make -j$(nproc) \
+  && make install
+
+# Install libtiff
+RUN cd /build_deps \
+  && wget --no-check-certificate https://download.osgeo.org/libtiff/tiff-4.1.0.tar.gz \
+  && tar -zxf tiff-4.1.0.tar.gz \
+  && cd tiff-4.1.0 \
+  && ./configure \
+  && make \
+  && make install
+
+# Install Proj
+RUN cd /build_deps \
+  && git clone https://github.com/OSGeo/PROJ.git -b 6.2.1 && cd PROJ \
+  && mkdir -p build && cd build \
+  && cmake .. \
+  && make -j$(nproc) \
+  && make install
+
+# Install libgeotiff
+RUN cd /build_deps \
+  && wget --no-check-certificate https://download.osgeo.org/geotiff/libgeotiff/libgeotiff-1.5.1.tar.gz \
+  && tar -zxf libgeotiff-1.5.1.tar.gz \
+  && cd libgeotiff-1.5.1 \
+  && mkdir -p build && cd build \
+  && cmake .. \
+  && make \
+  && make install
+
+# Install GDAL
+RUN cd /build_deps \
+  && git clone https://github.com/OSGeo/gdal.git && cd gdal/gdal \
+  && git checkout c99a871a7bdedc751c503bb8cf508d9016510fe0 \
+  && ./configure --with-crypto=no --with-curl=no \
+  && make -j$(nproc) \
+  && make install
+
+## Install TileDB-Py
+RUN cd /build_deps \
+  && pip3 install numpy \
+  && git clone https://github.com/TileDB-Inc/TileDB-Py.git -b 0.5.3 \
+  && cd TileDB-Py && python3 setup.py install
+
+## Install XArray
+RUN cd /build_deps && pip3 install xarray
+
+## Install Dask
+RUN cd /build_deps \
+  && pip3 install toolz && pip3 install dask_image \
+  && git clone https://github.com/dask/dask.git && cd dask \
+  && git checkout 807f3225cf840f28ce7cf89b88fea63d473889e7 \
+  && python3 setup.py install \
+  && pip3 install dask distributed --upgrade \
+  && pip3 install dask-image
+
+# Install Rasterio
+RUN cd /build_deps && pip3 install cython
+RUN cd /build_deps \
+  && git clone https://github.com/mapbox/rasterio.git -b 1.1.0 && cd rasterio \
+  && python3 setup.py install
+
+# Install Fiona
+RUN cd /build_deps \
+  && git clone https://github.com/Toblerity/Fiona.git && cd Fiona \
+  && python3 setup.py install
+
+# Install TileDB-SAR
+RUN cd /build_deps \
+  && git clone https://github.com/TileDB-Inc/TileDB-SAR.git && cd TileDB-SAR \
+  && git checkout 888059a15d87ae95fff6dc01c8bd4343ee4eaee1 \
+  && python3 setup.py install
+
+# Install Mapserver
+RUN cd /build_deps \
+  && git clone https://github.com/mapserver/mapserver.git && cd mapserver \
+  && git checkout 0fcc810f0b559c800f950db78a79fa6574799f23 \
+  && mkdir -p build && cd build \
+  && cmake .. -DWITH_GIF=OFF -DWITH_HARFBUZZ=OFF -DWITH_PROTOBUFC=OFF -DWITH_FRIBIDI=OFF -DWITH_POSTGIS=OFF -DWITH_GEOS=OFF -DWITH_FCGI=OFF -DWITH_CAIRO=OFF \
+  && make \
+  && make install
+
+# Install LasZIP
+RUN cd /build_deps \
+  && wget https://github.com/LASzip/LASzip/releases/download/3.4.1/laszip-src-3.4.1.tar.gz \
+  && tar -zxf laszip-src-3.4.1.tar.gz \
+  && cd laszip-src-3.4.1 \
+  && mkdir -p build && cd build \
+  && cmake .. \
+  && make \
+  && make install
+
+# Install PDAL
+RUN cd /build_deps \
+  && git clone https://github.com/PDAL/PDAL.git -b 2.0.1 && cd PDAL \
+  && mkdir -p build && cd build \
+  && cmake .. \
+  && make \
+  && make install
+
+# Install PDAL Python
+RUN pip3 install packaging \
+  && git clone https://github.com/PDAL/python pdalextension \
+  && cd pdalextension \
+  && python3 setup.py build \
+  && python3 setup.py install
+
+# Clean up
+RUN cd /tmp && rm -r /build_deps
diff --git a/tile_gen/Dockerfile b/tile_gen/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..125540c43684370b59e725755a438fbf2982bb40
--- /dev/null
+++ b/tile_gen/Dockerfile
@@ -0,0 +1,16 @@
+FROM tiledb/tiledb-geospatial:latest
+
+WORKDIR /work
+
+# TODO may need the unzip command to be installed if not already
+RUN apt-get update && apt-get install -y unzip && \
+    wget http://ssec.wisc.edu/~rayg/pub/amqpfind.zip && \
+    unzip amqpfind.zip && \
+    rm amqpfind.zip && \
+    rm -rf /var/lib/apt/lists/*
+
+# FIXME: Remove once added to parent image
+RUN pip3 install shapely
+COPY tile_index.py .
+COPY generate_tiles.py .
+COPY run.sh .
diff --git a/tile_gen/README.md b/tile_gen/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ac6fa4d140449483cfe400b8ab3699813fab4f5f
--- /dev/null
+++ b/tile_gen/README.md
@@ -0,0 +1,31 @@
+# Tile Generation
+
+## Build
+
+```bash
+docker build -t gitlab.ssec.wisc.edu:5555/cspp_geo/cspp-geo-web-viewer/tile_gen:latest tile_gen/
+docker push gitlab.ssec.wisc.edu:5555/cspp_geo/cspp-geo-web-viewer/tile_gen:latest
+```
+
+## Usage
+
+```bash
+docker run -d --rm --network cspp-geo-rabbit --cpus 2 --name cspp-geo-tilegen-g16-radf -e AMQPFIND_TOPIC="data.goes.g16.abi.radf.l1b.geotiff.complete" -v cspp-geo-abi-l1b-geotiffs:/data gitlab.ssec.wisc.edu:5555/cspp_geo/cspp-geo-web-viewer/tile_gen:latest ./run.sh
+```
+
+To run the version of tile generation that remaps to EPSG:4326:
+
+```bash
+docker run -d --rm --network cspp-geo-rabbit --cpus 6 --name cspp-geo-tilegen-g16-radf-ll -e AMQPFIND_TOPIC="data.goes.g16.abi.radf.l1b.geotiff.complete" -e G2G_PRODUCTS="true_color" -e TILE_ARGS="--remap --shape-file {product}_ll.shp" -v cspp-geo-abi-l1b-geotiffs:/data gitlab.ssec.wisc.edu:5555/cspp_geo/cspp-geo-web-viewer/tile_gen:latest ./run.sh
+```
+
+NOTE: For full disk data, remapping all products can take a really long
+time and fall behind. The above command limits processing to "true_color"
+only. To attempt to process all products remove the below portion of the
+command.
+
+```
+-e G2G_PRODUCTS="true_color"
+```
+
+And adding more CPUs.
\ No newline at end of file
diff --git a/tile_gen/generate_tiles.py b/tile_gen/generate_tiles.py
new file mode 100644
index 0000000000000000000000000000000000000000..2312f6b1c800bf047d9eab36a0b3a8306e004793
--- /dev/null
+++ b/tile_gen/generate_tiles.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import warnings
+import logging
+import subprocess
+import shutil
+from glob import glob
+
+import tile_index
+
+LOG = logging.getLogger(__name__)
+
+
+def group_files(products, input_files):
+    """Group input geotiff files by product."""
+    groups = {}
+    for prod in products:
+        prods_files = [f for f in input_files if prod in f]
+        if prods_files:
+            groups[prod] = prods_files
+    if len(groups) != len(products):
+        warnings.warn("Not all product geotiffs were provided.")
+    return groups
+
+
+def remap_to_lonlat(itif, otif):
+    """Remap a single geotiff by calling gdalwarp."""
+    try:
+        subprocess.run(['gdalwarp', '-multi', '-wo', 'NUM_THREADS=ALL_CPUS', '-t_srs', 'EPSG:4326', itif, otif], check=True)
+    except subprocess.CalledProcessError:
+        LOG.error("Could not remap geotiff %s -> %s" % (itif, otif))
+        return None
+    return otif
+
+
+def remap_tifs(input_tifs, out_dir, remap_suffix):
+    """Remap all input geotiffs to EPSG:4326."""
+    for itif in input_tifs:
+        ifn = os.path.basename(itif)
+        otif = os.path.join(out_dir, ifn.replace('.tif', remap_suffix))
+        otif = remap_to_lonlat(itif, otif)
+        if otif is not None:
+            yield otif
+
+
+def link_or_copy(input_tifs, out_dir):
+    """Hardlink input tifs to output directory."""
+    for prod_file in input_tifs:
+        out_file = os.path.join(out_dir, os.path.basename(prod_file))
+        try:
+            os.link(prod_file, out_file)
+        except OSError:
+            # on different mounts probably?
+            shutil.copy2(prod_file, out_file)
+        yield out_file
+
+
+def main():
+    import argparse
+    parser = argparse.ArgumentParser(description="Take input geotiffs and generate mapserver compatible tiles.")
+    parser.add_argument('--remap', action='store_true',
+                        help="Remap input geotiffs to EPSG:4326")
+    parser.add_argument('--remap-suffix', default='_ll.tif',
+                        help="Replace 'tif' with provided suffix when geotiffs are remapped.")
+    parser.add_argument('-p', '--products', nargs="*",
+                        help="Product names to group together in each "
+                             "'layer'. Product name must be in the filename.")
+    parser.add_argument('--shape-file', default='{product}.shp',
+                        help="Shapefile filename pattern to use and placed "
+                             "in the output directory. "
+                             "(default: '{product}.shp')")
+    parser.add_argument('out_dir',
+                        help="Output path to save tile information to (ex. '/data/tiles/{product}')")
+    parser.add_argument('input_files', nargs="+",
+                        help="Input geotiffs to generate tiles for (separate from product lists with '--')")
+    args = parser.parse_args()
+
+    groups = group_files(args.products, args.input_files)
+    for prod, prod_files in groups.items():
+        out_dir = args.out_dir.format(product=prod)
+        os.makedirs(out_dir, exist_ok=True)
+        shp_fn = args.shape_file.format(product=prod)
+        shp_pathname = os.path.join(out_dir, shp_fn)
+
+        if args.remap:
+            # remap if needed
+            prod_files = list(remap_tifs(prod_files, out_dir, args.remap_suffix))
+        else:
+            # hardlink if needed
+            prod_files = list(link_or_copy(prod_files, out_dir))
+
+        # get all products in the current directory
+        ext = os.path.splitext(prod_files[-1])[-1]
+        all_prod_files = sorted(glob(os.path.join(out_dir, '*' + prod + '*' + ext)))
+
+        # create shape file
+        LOG.info("Rebuilding shapefile index with:\n\t{}".format(", ".join(all_prod_files)))
+        tile_index.index(all_prod_files, shp_pathname)
+
+
+
+if __name__ == "__main__":
+    sys.exit(main())
\ No newline at end of file
diff --git a/tile_gen/run.sh b/tile_gen/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..449169bbe594746a7cd09bca1f245ccb7579816b
--- /dev/null
+++ b/tile_gen/run.sh
@@ -0,0 +1,81 @@
+#!/bin/bash -le
+# Usage: run.sh
+# Environment variables used for configuration:
+# AMQPFIND_ARGS: Arguments to pass to amqpfind when listening for new input
+#     events. Should not include the "-C" topic flag (see AMQPFIND_TOPIC).
+#     Default: "-H cspp-geo-rabbit -X satellite -u guest -p guest"
+# AMQPFIND_TOPIC: Topic to use for incoming data events.
+#     Default: "data.goes.*.abi.*.l1b.netcdf.complete"
+#     The first asterisk (3rd element) can limit processing to a particular
+#     satellite (ex. `g16`). The second asterisk (5th element) can be used
+#     to limit to a particular sector (choices: radf, radc, radm1, radm2)
+# AMQPSEND_ARGS: Arguments to pass to amqpsend when sending out new data
+#     events. Default: "-H cspp-geo-rabbit -X satellite -u guest -p guest"
+
+# Verify that the data mount is available
+test -d "/data"
+
+export AMQPFIND_ARGS=${AMQPFIND_ARGS:-"-H cspp-geo-rabbit -X satellite -u guest -p guest"}
+export AMQPSEND_ARGS=${AMQPSEND_ARGS:-"-H cspp-geo-rabbit -X satellite -u guest -p guest"}
+export AMQPFIND_TOPIC=${AMQPFIND_TOPIC:-'data.goes.*.abi.*.l1b.geotiff.complete'}
+export G2G_PRODUCTS=${G2G_PRODUCTS:-"C01 C02 C03 C04 C05 C06 C07 C08 C09 C10 C11 C12 C13 C14 C15 C16 true_color"}
+export TILE_ARGS=${TILE_ARGS:-""}
+export TMPDIR=${TMPDIR:-"/dst/tmp"}
+
+run_tile_gen() {
+    if [[ $# -ne 5 ]]; then
+        echo "Unexpected number of arguments (expected 5): $#"
+        return 1
+    fi
+
+    satellite_family=${1,,}
+    satellite_id=${2,,}
+    instrument=${3,,}
+    data_type=${4,,}
+    path="$5"
+    echo "Starting tile generation processing for ${path}"
+    # convert path from a relative path to an absolute path
+    path="/data/${path}"
+
+    # if /dst isn't defined then use /data
+    if [[ -d "/dst" ]]; then
+        dst_dir="/dst"
+    else
+        dst_dir="/data"
+    fi
+
+    # update shapefile in a temporary directory
+    # and resample geotiff if necessary
+    # FUTURE: TileDB will be updated in-place
+    # generate_tiles.py will make a temporary directory
+    out_dir="${dst_dir}/tiles/${satellite_id}/${instrument}/${data_type}"
+    # if we are going to remap, separate the geotiffs and shapefile from unremapped
+    if [[ ${TILE_ARGS} == *"--remap"* ]]; then
+        echo "Adding '_ll' prefix to tile output directory because of remapping"
+        out_dir="${out_dir}_ll"
+        mkdir -p ${out_dir}
+        # add string formatting portion to separate add 'product' sub-directory
+        out_dir="${out_dir}/{product}_ll"
+    else
+        mkdir -p ${out_dir}
+        # add string formatting portion to separate add 'product' sub-directory
+        out_dir="${out_dir}/{product}"
+    fi
+    echo "Generating tiles in directory: ${out_dir}"
+    python3 generate_tiles.py ${TILE_ARGS} -p ${G2G_PRODUCTS} -- ${out_dir} ${path}
+    # OUT/<product>/<product>.shp
+    glob_pattern="${out_dir}/*/*.shp"
+    # Remove the /data prefix
+    glob_pattern="${glob_pattern/${dst_dir}\//}"
+
+    amqpsend_topic="data.${satellite_family}.${satellite_id}.${instrument}.${data_type}.l1b.tiledb.complete"
+#    json_info="{path: ${glob_pattern}, satellite_family: ${satellite_family}, satellite_ID: ${satellite_id}, instrument: ${instrument}, data_type: ${data_type}}"
+    json_info="{\"path\": \"${glob_pattern}\", \"satellite_family\": \"${satellite_family}\", \"satellite_ID\": \"${satellite_id}\", \"instrument\": \"${instrument}\", \"data_type\": \"${data_type}\"}"
+    echo -e "[[\"$amqpsend_topic\", $json_info]]" | python3 /work/amqpfind/amqpsend.py ${AMQPSEND_ARGS}
+    echo "Done generating tiles for ${path}"
+}
+
+export -f run_tile_gen
+echo "Listening to AMQP messages with topic \"$AMQPFIND_TOPIC\""
+python3 amqpfind/amqpfind.py ${AMQPFIND_ARGS} -C "${AMQPFIND_TOPIC}" -j "{satellite_family} {satellite_ID} {instrument} {data_type} \'{path}\'" | xargs -I{} -P3 -n1 bash -c "run_tile_gen {}"
+
diff --git a/tile_gen/tile_index.py b/tile_gen/tile_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ff1769ef093322586201601cdc4432279c55e6f
--- /dev/null
+++ b/tile_gen/tile_index.py
@@ -0,0 +1,160 @@
+import sys
+import fiona
+from fiona.crs import from_epsg
+import rasterio
+from shapely.geometry import mapping, box
+
+import argparse
+import datetime
+import glob
+import logging
+import os
+import re
+import shutil
+
+# Remap geostationary to EPSG 4326
+# gdalwarp -t_srs EPSG:4326 in.tif out.tif
+
+
+# Note if using docker tiledb-geospatial image then requires shapely - `pip3 install shapely`
+# export CPL_DEBUG=ON - GDAL
+# export MS_DEBUGLEVEL=6 - MAPSERVER
+# python3 tile_index.py -dir data
+# sample mapserver queries
+# mapserv -nh "QUERY_STRING=map=goes.map&request=GetCapabilities&service=WMS&version=1.1.1"
+# mapserv -nh "QUERY_STRING=map=goes.map&request=GetMap&service=WMS&version=1.1.1&layers=goes_abi&srs=EPSG:4326&bbox=-180,-90,180,90&format=image/jpeg&WIDTH=1000&HEIGHT=1000&TIME=2019-12-12T19:10:18" > out.jpg
+# TODO add overview example to mapserver
+
+logger = logging.getLogger(__name__)
+
+temporal_schema = {
+    'geometry': 'Polygon',
+    'properties': {
+        'location': 'str',
+        'time': 'str:19'
+    }
+}
+
+possible_time_regex = (
+    (re.compile(r'\d{4}\d{2}\d{2}_\d{2}\d{2}\d{2}'), '%Y%m%d_%H%M%S'),
+    (re.compile(r'\d{4}\d{2}\d{2}T\d{2}\d{2}\d{2}'), '%Y%m%dT%H%M%S'),
+)
+
+
+def get_file_time(fn):
+    for regex, time_fmt in possible_time_regex:
+        matches = regex.findall(fn)
+        if matches:
+            return datetime.datetime.strptime(matches[-1], time_fmt)
+    else:
+        raise ValueError("Unknown filename scheme, can't determine file time.")
+
+
+def index(input_files, output_shapefile):
+    """Create shapefile for location and times of provided geotiffs or tileDB arrays.
+
+    Note: All layers to be included in the shapefile must be provided all at
+          once. Repeated calls to this function will overwrite existing
+          shapefile information.
+
+    """
+    import tempfile
+    out_dir, shp_fn = os.path.split(output_shapefile)
+    tmp_dir = tempfile.mkdtemp("_tile_index")
+    tmp_shapefile = os.path.join(tmp_dir, shp_fn)
+    with fiona.open(tmp_shapefile, 'w', driver='ESRI Shapefile',
+                    schema=temporal_schema) as output:
+        for f in input_files:
+            try:
+                dt = get_file_time(f)
+            except ValueError:
+                logger.error(f"Can't time for file {f}")
+                continue
+
+            logger.info(f"Indexing {f} {dt.isoformat()}")
+            with rasterio.open(f) as src:
+                g = box(*src.bounds)
+
+                output.write(
+                    {
+                        'geometry': mapping(g),
+                        'properties': {
+                            'location': f,
+                            'time': dt.isoformat()
+                        }
+                    })
+
+    # move the shapefile contents to the final destination
+    for fn in os.listdir(tmp_dir):
+        shutil.move(os.path.join(tmp_dir, fn), os.path.join(out_dir, fn))
+    # we don't need the temporary directory anymore
+    shutil.rmtree(tmp_dir, ignore_errors=True)
+
+
+# def index(src_dir, output):
+#   files = glob.glob(os.path.join(src_dir, '*.tif'))
+#   folders = [os.path.join(src_dir, o) for o in os.listdir(src_dir) if os.path.isdir(os.path.join(src_dir, o))]
+#
+#     with fiona.open(output, 'w', driver='ESRI Shapefile',
+#                     schema=temporal_schema) as output:
+#         # simple toggle between indexing tiff files or tiledb arrays
+#         if len(files) > 0:
+#             it = files
+#         else:
+#             it = folders
+#
+#         for f in it:
+#             parts = f.split('_')
+#             tstamp = parts[5] + parts[6]
+#             dt = datetime.datetime.strptime(tstamp, '%Y%m%d%H%M%S')
+#             logger.info(f"Indexing {f} {dt.isoformat()}")
+#             with rasterio.open(f) as src:
+#                 g = box(*src.bounds)
+#
+#                 output.write(
+#                     {
+#                         'geometry': mapping(g),
+#                         'properties': {
+#                             'location': f,
+#                             'time': dt.isoformat()
+#                         }
+#                     })
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-d', '--directory',
+                        help='Single directory to search for .tif files or TileDB array directories.')
+    parser.add_argument('-o', '--output', default='img_index.shp')
+    parser.add_argument('input_files', nargs='*',
+                        help='TileDB directories or GeoTIFF files to ingest.')
+    args = parser.parse_args()
+
+    logging.basicConfig(
+        level=logging.INFO,
+        format="%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s"
+    )
+
+    if args.input_files:
+        logger.info("Using input files...")
+        it = args.input_files
+    else:
+        src_dir = args.directory
+        files = glob.glob(os.path.join(src_dir, '*.tif'))
+        folders = [os.path.join(src_dir, o) for o in os.listdir(src_dir) if os.path.isdir(os.path.join(src_dir, o))]
+        # simple toggle between indexing tiff files or tiledb arrays
+        if len(files) > 0:
+            it = files
+        else:
+            it = folders
+
+    if not it:
+        raise ValueError("No valid inputs provided.")
+
+    if args.directory:
+        logger.info('Indexer starting')
+        index(it, args.output)
+
+
+if __name__ == "__main__":
+    sys.exit(main())