<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stefan Wagner</title>
    <description>The latest articles on DEV Community by Stefan Wagner (@waquner).</description>
    <link>https://dev.to/waquner</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F170061%2F4127d23e-6ae0-4be0-b405-15beb3374a78.jpg</url>
      <title>DEV Community: Stefan Wagner</title>
      <link>https://dev.to/waquner</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/waquner"/>
    <language>en</language>
    <item>
      <title>POC Custom Street View for Vienna</title>
      <dc:creator>Stefan Wagner</dc:creator>
      <pubDate>Tue, 04 Apr 2023 20:32:52 +0000</pubDate>
      <link>https://dev.to/waquner/poc-custom-street-view-for-vienna-2hii</link>
      <guid>https://dev.to/waquner/poc-custom-street-view-for-vienna-2hii</guid>
      <description>&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;A POC of a Street View client using open data: &lt;a href="https://webkappa.karten.wien"&gt;https://webkappa.karten.wien&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In 2020 my beautiful hometown - the City of Vienna, Austria ("Stadt Wien") completed mapping the streets with Lidar Scanners and Cameras mounted on cars.&lt;/p&gt;

&lt;p&gt;The good thing: &lt;strong&gt;The collected data is completely Open Data&lt;/strong&gt; and so you can create your own "Street View" based on it, completely independent from Google. Using the laser scanner data even more useful things can be created, the City of Vienna will use it e.g. for traffic planning in the future.&lt;/p&gt;

&lt;p&gt;I tried to create a "Street View" client based on the provided images.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;p&gt;The images where taken by 6 cameras, all in all ~250 megapixels.&lt;/p&gt;

&lt;p&gt;Data can be requested via an online tool - after some hours or days of internal processing, you get a link of a .tar file containing images and meta data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--djYKQbio--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4d86qij2wtngv0mgf7vy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--djYKQbio--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4d86qij2wtngv0mgf7vy.png" alt="directory structure of example tar file" width="323" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The file &lt;code&gt;image_meta.txt&lt;/code&gt; is a TSV of all images taken for the requested set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8ciW8oms--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hgd6kdlwkfum4u8mcm03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8ciW8oms--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hgd6kdlwkfum4u8mcm03.png" alt="image_meta.txt TSV" width="880" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It contains the following columns (unused columns are omitted):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trajectory_id&lt;/code&gt; ID of trajectory (part of folder name)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sensor_id&lt;/code&gt; ID of the camera (essential to get the direction of the camera/part in the cubemap - see later)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x_m/y_m&lt;/code&gt; Latitude and longitude/position of the car (projection EPSG:31256)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rz_rad&lt;/code&gt; Z-Rotation = facing of car in radiants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The photos are stored in way, that a "cubemap" consisting of 6 single photos can be created (negative X,Y,Z and positive X,Y,Z) - the "sensor" = camera used are mapped to the cubemap position as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sensor id ending in 0: positive Y&lt;/li&gt;
&lt;li&gt;ending in 1: positive Z&lt;/li&gt;
&lt;li&gt;ending in 2: positive X&lt;/li&gt;
&lt;li&gt;ending in 3: negative Z&lt;/li&gt;
&lt;li&gt;ending in 4: negative X&lt;/li&gt;
&lt;li&gt;ending in 5: negative Y&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The unfolded cubemap would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HF99n3Oy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/huoes9zkennbq50k1pd8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HF99n3Oy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/huoes9zkennbq50k1pd8.jpg" alt="cubemap positions" width="600" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each photo has a resolution of 7130x7130 and has about 5MB, thus the complete cubemap has up to 60MB. So even about &lt;strong&gt;100 meters&lt;/strong&gt; of street consists of about &lt;strong&gt;2 GB of raw photo data&lt;/strong&gt; (one cubemap every ~ 3 meters). That's why for this POC the images are &lt;strong&gt;scaled down&lt;/strong&gt; to 2048x2048 later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling the data
&lt;/h2&gt;

&lt;p&gt;Enough of theory, let's get physical&lt;/p&gt;

&lt;p&gt;A node.js was created to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve list of images from &lt;code&gt;image_meta.txt&lt;/code&gt; TSV&lt;/li&gt;
&lt;li&gt;Scale images down to 2048x2048 using graphicsmagick and rename to a unique filename (&lt;code&gt;&amp;lt;hash of position&amp;gt;_{p,n}{x,y,z}.jpg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Transform position of cubemap to EPSG:4326&lt;/li&gt;
&lt;li&gt;Create a JSON file for each cubemap to store meta data (e.g. facing)&lt;/li&gt;
&lt;li&gt;Save position, image filenames to postgres/postgis database&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The backend
&lt;/h2&gt;

&lt;p&gt;Now that we have the data in place, there's a need of a small backend to provide the frontend with 2 sets of information&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get all possible positions to mark them on a map&lt;/li&gt;
&lt;li&gt;Get the nearest position based on a given coordinate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we have all the information for that in our database, a small express based backend was set up fast.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One API route just queries &lt;code&gt;SELECT array[st_y(position), st_x(position)] as ll FROM cubemaps&lt;/code&gt; and returns the positions.&lt;/li&gt;
&lt;li&gt;The second route basically does the some, but orders by distance to the given position using &lt;code&gt;st_distance&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The frontend
&lt;/h2&gt;

&lt;p&gt;A full screen map based on &lt;strong&gt;leaflet&lt;/strong&gt; was set up. It initialy retrieves information about all possible positions and displays it as blue circles using &lt;code&gt;L.circle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ddEgSAlX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zzgwh6fraiqkbc11y12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ddEgSAlX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zzgwh6fraiqkbc11y12.png" alt="Base map with positions" width="636" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A click on the map gets the nearest position from the backend and opens a panorama viewer based on &lt;strong&gt;three.js&lt;/strong&gt; and &lt;strong&gt;panolens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eik1uCeQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dokvynvhzxt2oppxdqqe.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eik1uCeQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dokvynvhzxt2oppxdqqe.gif" alt="Panoroma based on Cubemap" width="600" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A small map in the bottom right corner shows the current facing of the panorama viewer and also all other possible positions within 500 meters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0ljMEIoH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxkcl2gjzusrqywshh33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0ljMEIoH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxkcl2gjzusrqywshh33.png" alt="Mini map with positions" width="305" height="308"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jyLy22Qm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l80vxikych25czpkczlz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jyLy22Qm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l80vxikych25czpkczlz.gif" alt="Mini map animated" width="120" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigation is done by clicking the positions there - you can also navigate by clicking inside the cubemap - but that's not done yet and kind of buggy right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where can I see it?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_TCEs_v7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqp1nv58eydw9q3j7elo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_TCEs_v7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqp1nv58eydw9q3j7elo.png" alt="Result" width="880" height="659"&gt;&lt;/a&gt; Here (It's german, sorry): &lt;a href="https://webkappa.karten.wien"&gt;https://webkappa.karten.wien&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Still todo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cleanup, cleanup, refactor, refactor&lt;/li&gt;
&lt;li&gt;Think about mobile version&lt;/li&gt;
&lt;li&gt;Find memory leaks (switching between cubemaps is done pretty dumb)&lt;/li&gt;
&lt;li&gt;Import more Cubemaps (Unfortunately the download tool seems to be overloaded currently)&lt;/li&gt;
&lt;li&gt;Make navigation within the panorama possible (like Google does it...)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thanks!
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Creator and maintainers of &lt;a href="https://github.com/Leaflet/Leaflet"&gt;leaflet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Creator and maintainers of &lt;a href="https://pchen66.github.io/Panolens/"&gt;panolens.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Anita Graser for exploring the sample data of 2020 &lt;a href="https://anitagraser.com/2021/09/25/exploring-viennas-street-level-lidar-kappazunder-data-sample/"&gt;blog article&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The City of Vienna for making this data open!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opendata</category>
      <category>node</category>
    </item>
    <item>
      <title>Archive PostGIS location data and keep it queryable with AWS Athena</title>
      <dc:creator>Stefan Wagner</dc:creator>
      <pubDate>Wed, 23 Nov 2022 20:40:10 +0000</pubDate>
      <link>https://dev.to/waquner/backuparchive-postgis-location-data-and-keep-it-queryable-3hfe</link>
      <guid>https://dev.to/waquner/backuparchive-postgis-location-data-and-keep-it-queryable-3hfe</guid>
      <description>&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;AWS Athena is great :)&lt;/p&gt;

&lt;h2&gt;
  
  
  The task
&lt;/h2&gt;

&lt;p&gt;Backup tens of millions of location data of cars stored in PostgreSQL/PostGIS, but keep it queryable easily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side condition:&lt;/strong&gt; Use as less code as possible&lt;/p&gt;

&lt;h2&gt;
  
  
  The input
&lt;/h2&gt;

&lt;p&gt;A PostgreSQL/PostGIS Table in more or less the following structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id: integer
timestamp: timestamp
location: postgis.geometry(Point, 4326)
speed: double
carid: integer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the time of starting the task, the table was filled with tens of millions of lines, bloating indexes and making querying very slow&lt;/p&gt;

&lt;p&gt;The data is essential, but accessed rarely, especially for data older than a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The postgres query
&lt;/h2&gt;

&lt;p&gt;After some struggeling with Athena data types, I finally got the following query to backup data and created a view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE VIEW v_locations_athena as 
SELECT 
to_char(TIMESTAMP, 'YYYY-MM-DD HH24:MI:ss') /* timestamp in format for Athena* /,
postgis.st_x(LOCATION) AS longitude /* Longitude */, postgis.st_y(LOCATION) AS latitude /* Latitude */,
speed,
carid
FROM locations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The export
&lt;/h2&gt;

&lt;p&gt;A simple bash script was created, doing the query above, export it to CSV, gzip it and transfer it to an AWS S3 bucket&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -c "\copy (SELECT * from v_locations_athena where timestamp &amp;lt; '2022-08-01')  TO '/tmp/locations_2022-08-01.csv' DELIMITER ',' CSV"
gzip /tmp/locations_2022-08-01.csv
aws s3 cp /tmp/locations_2022-08-01.csv s3://bucketname/locations/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Athena table
&lt;/h2&gt;

&lt;p&gt;On Athena, I created a table in the same structure as the exported CSV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create EXTERNAL TABLE IF NOT EXISTS locations_live.locations (
  `timestamp` timestamp,
  `longitude` double,
  `latitude` double,
  `speed` float,
  `carid` int
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = ',',
  'field.delim' = ','
) LOCATION 's3://bucketname/locations/'
TBLPROPERTIES ('has_encrypted_data'='false'); 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--diV93KhU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z03b4nupt1d9314suzaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--diV93KhU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z03b4nupt1d9314suzaz.png" alt="Athena table in Athena mgmt interface" width="358" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note to use the same delimiter in both Athena and CSV Export!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Athena query
&lt;/h2&gt;

&lt;p&gt;Now that everything is in place, we can start querying data and as we use Athena &lt;strong&gt;without setting up any kind of server!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example to query all locations within 100 meters of a specific point within a given time period:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
*
FROM locations_live.locations 
where timestamp &amp;gt;=cast('2022-01-01' as timestamp) and timestamp &amp;lt; cast('2022-02-01' as timestamp)
and st_distance(to_spherical_geography(st_point(longitude, latitude)), to_spherical_geography(st_point(12.3456, 49.12345))) &amp;lt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Athena result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c-qLxSz_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1albrqeeop8n51ndw1hz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c-qLxSz_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1albrqeeop8n51ndw1hz.png" alt="Athena result showing location data" width="880" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, tens of millions of rows where scanned within &amp;lt; 30s without any server setup (forget the speed column, it had the wrong unit :-) ) and all that for about &lt;strong&gt;0,006$&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The cronjob
&lt;/h2&gt;

&lt;p&gt;Now everything I need to do is upload a CSV e.g. every month, delete old data from database and I'm able to query old data at a very low price (~ 0,0245$ per GB per month in EU)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Low pricing for storage (~ &lt;strong&gt;0,0245$ per GB per month&lt;/strong&gt; in EU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No effort or cost to setup server&lt;/strong&gt; / query infrastructure&lt;/li&gt;
&lt;li&gt;Low pricing for query (~ &lt;strong&gt;5$ per TB scanned&lt;/strong&gt;) - so in the example above one query costs only about &lt;strong&gt;0,006$&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bad
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Would it be personal customer data (it's not) - it would cause GDPR troubles - even if EU is selected for S3 and Athena; Amazon is a US company, so unfortunately a no-go in terms of GDPR.&lt;/li&gt;
&lt;li&gt;Query times vary a bit&lt;/li&gt;
&lt;li&gt;Query &lt;strong&gt;results are automatically stored to S3&lt;/strong&gt; again - so pay attention to that!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The learnings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;gzip/compress your data&lt;/strong&gt; - scanning is faster and pricing is lower (you always pay per scanned data, so less data -&amp;gt; lower price). Additionaly S3 upload is faster. So it's a win/win/win situation :)&lt;/li&gt;
&lt;li&gt;pay attention to &lt;strong&gt;timestamp formats&lt;/strong&gt;, bugged me a lot!&lt;/li&gt;
&lt;li&gt;Athena has different SerDe (Serialization/Deserialization) options - I chose a simple CSV, but for future use I will definitely play with one of these: &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/serde-about.html"&gt;https://docs.aws.amazon.com/athena/latest/ug/serde-about.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The outlook
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Leverage Partioning (&lt;a href="https://docs.aws.amazon.com/athena/latest/ug/partitions.html"&gt;https://docs.aws.amazon.com/athena/latest/ug/partitions.html&lt;/a&gt;) by splitting CSV by month/year/day(?) to speed up querying and reduced costs even more&lt;/li&gt;
&lt;li&gt;I started to do the same for a table with heavy jsonb fields usage - interesting new problems here :)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>athena</category>
      <category>postgres</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
